// This is a part of the Active Template Library.
// Copyright (C) Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Active Template Library Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Active Template Library product.

#ifndef __ATLSTENCIL_H__
#include <atlstencil.h>
#endif

#ifndef __ATLISAPI_H__
#define __ATLISAPI_H__

#pragma once
#include <atlbase.h>
#include <time.h>   // needed for cookie support
#include <httpext.h>    // needed for ECB and IIS support
#include <atlspriv.h>
#include <atlserr.h>
#include <atlfile.h>
#include <atlstr.h>
#include <atldbcli.h>
#include <atlutil.h>
#include <atlcache.h>
#include <atlsrvres.h>
#include <atlsiface.h>
#include <objbase.h>
#include <atlsecurity.h>
#include <errno.h>
#ifndef ATL_NO_SOAP
	#include <msxml2.h>
#endif
#ifndef ATL_NO_ACLAPI
	#include <aclapi.h>
#endif
#ifndef ATL_NO_MMSYS
#pragma warning(push)
#pragma warning(disable:4201) // nonstandard extension used : nameless struct/union
#include <mmsystem.h>
#pragma warning(pop)
#ifndef _ATL_NO_DEFAULT_LIBS
#pragma comment(lib, "winmm.lib")
#ifndef ATL_NO_SOAP
#pragma comment(lib, "msxml2.lib")
#endif
#endif  // !_ATL_NO_DEFAULT_LIBS
#endif
#include <atlpath.h>

#pragma warning(push)
#pragma warning(disable: 4291) // allow placement new
#pragma warning(disable: 4127) // conditional expression is constant
#pragma warning(disable: 4511) // copy constructor could not be generated
#pragma warning(disable: 4512) // assignment operator could not be generated
#pragma warning(disable: 4625) // copy constructor could not be generated because a base class copy constructor is inaccessible
#pragma warning(disable: 4626) // assignment operator could not be generated because a base class assignment operator is inaccessible
#pragma warning(disable: 4191) // unsafe conversion from 'functionptr1' to 'functionptr2'
#pragma warning(disable: 4702) // unreachable code

#include <initguid.h>
#include <dbgautoattach.h>

#ifndef SESSION_COOKIE_NAME
	#define SESSION_COOKIE_NAME "SESSIONID"
#endif

// override this if you want to use a different CLSID for SAX
#ifndef ATLS_SAXXMLREADER_CLSID
	#define ATLS_SAXXMLREADER_CLSID __uuidof(SAXXMLReader)
#endif // ATLS_SAXXMLREADER_CLSID


// This function is used in CValidateObject to determine if an empty
// request parameter really should be empty. You can 
// specialize this function in your own code such as
// the following specialization for type long:
// template <>
// inline bool IsNullByType<long>(long type) throw()
// {
//   return type == 0;
// }
// You should provide your own specialization for this 
// function if the comparison of type==0 is not adequate
// to discover whether or not your type is 0.
template <class TComp>
inline bool IsNullByType(__in TComp type) throw()
{
	return type == 0;
}


#pragma pack(push,_ATL_PACKING)
namespace ATL {

// Default file extension for server response files
#ifndef ATL_DEFAULT_STENCIL_EXTENSION
	#define ATL_DEFAULT_STENCIL_EXTENSION ".srf"
#endif
extern __declspec(selectany) const char * const c_AtlSRFExtension = ATL_DEFAULT_STENCIL_EXTENSION;
extern __declspec(selectany) const TCHAR * const c_tAtlSRFExtension = _T(ATL_DEFAULT_STENCIL_EXTENSION);
#define ATLS_EXTENSION_LEN (sizeof(ATL_DEFAULT_STENCIL_EXTENSION)-2)

// Default file extension for handler DLLs
#ifndef ATL_DEFAULT_DLL_EXTENSION
	#define ATL_DEFAULT_DLL_EXTENSION ".dll"
#endif
extern __declspec(selectany) const char * const c_AtlDLLExtension = ATL_DEFAULT_DLL_EXTENSION;
extern __declspec(selectany) const TCHAR * const c_tAtlDLLExtension = _T(ATL_DEFAULT_DLL_EXTENSION);
#define ATLS_DLL_EXTENSION_LEN (sizeof(ATL_DEFAULT_DLL_EXTENSION)-2)

// maximum handler name length
#ifndef ATL_MAX_HANDLER_NAME_LEN 
	#define ATL_MAX_HANDLER_NAME_LEN 64
#endif

#ifndef ATL_HANDLER_NAME_DEFAULT
#define ATL_HANDLER_NAME_DEFAULT "Default"
#endif // ATL_DEFAULT_HANDLER_NAME


// maximum timeout for async guard mutex
#ifndef ATLS_ASYNC_MUTEX_TIMEOUT
	#define ATLS_ASYNC_MUTEX_TIMEOUT 10000
#endif

#if defined(_M_IA64) || defined (_M_AMD64)
#define ATLS_FUNCID_INITIALIZEHANDLERS "InitializeAtlHandlers"
#define ATLS_FUNCID_GETATLHANDLERBYNAME "GetAtlHandlerByName"
#define ATLS_FUNCID_UNINITIALIZEHANDLERS "UninitializeAtlHandlers"
#elif defined(_M_IX86)
#define ATLS_FUNCID_INITIALIZEHANDLERS "_InitializeAtlHandlers@8"
#define ATLS_FUNCID_GETATLHANDLERBYNAME "_GetAtlHandlerByName@12"
#define ATLS_FUNCID_UNINITIALIZEHANDLERS "_UninitializeAtlHandlers@0"
#else
#error Unknown Platform.
#endif

#define ATL_MAX_COOKIE_LEN 2048
#define ATL_MAX_COOKIE_ELEM 1024


// Defines a small value used for comparing the equality of floating point numbers.
#ifndef ATL_EPSILON
	#define ATL_EPSILON .0001
#endif

#ifndef ATL_DEFAULT_PRECISION
	#define ATL_DEFAULT_PRECISION 6
#endif

// Call this function to URL-encode a buffer and have the result appended to a CString passed by reference.
//
// A space in the input string is encoded as a plus sign (+).
// Other unsafe characters (as determined by AtlIsUnsafeUrlChar) are encoded as escaped octets.
// An escaped octet is a percent sign (%) followed by two digits representing the hexadecimal code of the character.
//
// string       A CStringA reference to which will be appended the encoded version of szBuf.
//
// szBuf        The string to be URL-encoded.
ATL_NOINLINE inline bool EscapeToCString(__inout CStringA& string, __in LPCSTR szBuf)
{
	ATLENSURE( szBuf != NULL );

	_ATLTRY
	{
		CHAR szEscaped[512];
		LPSTR pszStr = szEscaped;
		DWORD dwLen = 0;

		while (*szBuf)
		{
			if (dwLen+4 >= _countof(szEscaped))
			{
				*pszStr = '\0';
				string.Append(szEscaped, dwLen);
				pszStr = szEscaped;
				dwLen = 0;
			}
			if (AtlIsUnsafeUrlChar(*szBuf))
			{
				if (*szBuf == ' ')
				{
					dwLen++;
					*pszStr++ = '+';
				}
				else
				{
					LPSTR pszTmp = pszStr;
					*pszTmp++ = '%';
					unsigned char ch = (unsigned char)*szBuf;
					if (ch < 16)
					{
						*pszTmp++ = '0';
					}
					Checked::ultoa_s((unsigned char)ch, pszTmp, szEscaped + _countof(szEscaped) - pszTmp, 16);
					pszStr+= sizeof("%FF")-1;
					dwLen+= sizeof("%FF")-1;
				}
			}
			else
			{
				*pszStr++ = *szBuf;
				dwLen++;
			}
			szBuf++;
		}

		*pszStr = '\0';
		string.Append(szEscaped, dwLen);
	}
	_ATLCATCHALL()
	{
		return false;
	}

	return true;
}

// UNICODE overload for EscapeToCString
// follow specifications detailed in RFC document on
// Internationalized Uniform Resource Identifiers (IURI)
inline bool EscapeToCString(__inout CStringA& string, __in_z LPCWSTR wszBuf) throw()
{
	_ATLTRY
	{
		// convert string to UTF8
		CFixedStringT<CStringA, 2048> strConvert;

		// get the required length for conversion
		int nSrcLen = (int) wcslen(wszBuf);
		int nLen = AtlUnicodeToUTF8(wszBuf, nSrcLen, NULL, 0);
		if (!nLen)
		{
			return false;
		}

		// allocate MBCS conversion string
		LPSTR sz = strConvert.GetBuffer(nLen+1);
		if (!sz)
		{
			return false;
		}

		// do the UNICODE to UTF8 conversion
		nLen = AtlUnicodeToUTF8(wszBuf, nSrcLen, sz, nLen);
		if (!nLen)
		{
			return false;
		}

		// null-terminate
		sz[nLen] = '\0';

		// delegate to ANSI version of EscapeToCString
		if (!EscapeToCString(string, sz))
		{
			return false;
		}

		strConvert.ReleaseBuffer(nLen);
	}
	_ATLCATCHALL()
	{
		return false;
	}

	return true;
}

struct CDefaultErrorProvider
{
	struct HTTP_ERROR_TEXT
	{
		UINT uHttpError;    // the Http Error value
		UINT uHttpSubError; // Allows for customization of error text based on srf specific errors.
		LPCSTR szHeader;    // the string that should appear in the http response header
		UINT uResId;        // the resource id of the string to send back as the body
	};


	// GetErrorText retrieves the http response header string
	// and a resource id of the response body for a given
	// http error code
	// uError: Http error code to retrieve information for
	// ppszHeader: pointer to LPCSTR that receives the response header string
	//          ppszHeader is optional
	// puResId: pointer to UINT that receives the response body resource id
	//          puResId is optional
	static BOOL GetErrorText(__in UINT uError, __in UINT uSubErr, __deref_out_opt LPCSTR *ppszHeader, __out_opt UINT *puResId) throw()
	{
		static const HTTP_ERROR_TEXT s_Errors[] = 
		{
			{ 200, SUBERR_NONE, "OK", 0 },
			{ 201, SUBERR_NONE, "Created", 0 },
			{ 202, SUBERR_NONE, "Accepted", 0 },
			{ 203, SUBERR_NONE, "Non-Authoritative Information", 0 },
			{ 204, SUBERR_NONE, "No Content", 0 },
			{ 204, DBG_SUBERR_ALREADY_DEBUGGING, "Already being debugged by another user", 0},
			{ 204, DBG_SUBERR_NOT_DEBUGGING, "Not currently debugging a process", 0},
			{ 204, DBG_SUBERR_INVALID_SESSION, "Requested DebugSessionID does not match current DebugSessionID", 0},
			{ 204, DBG_SUBERR_BAD_ID, "DebugSessionID corrupted or not provided", 0 },
			{ 204, DBG_SUBERR_COCREATE, "Could not CoCreate the debugger", 0 },
			{ 204, DBG_SUBERR_ATTACH, "Could not attach to process", 0 },
			{ 205, SUBERR_NONE, "Reset Content", 0 },
			{ 206, SUBERR_NONE, "Partial Content", 0 },
			{ 300, SUBERR_NONE, "Multiple Choices", 0 },
			{ 301, SUBERR_NONE, "Moved Permanently", 0 },
			{ 302, SUBERR_NONE, "Found", 0 },
			{ 303, SUBERR_NONE, "See Other", 0 },
			{ 304, SUBERR_NONE, "Not Modified", 0 },
			{ 305, SUBERR_NONE, "Use Proxy", 0 },
			{ 306, SUBERR_NONE, "(Unused)", 0 },
			{ 307, SUBERR_NONE, "Temporary Redirect", 0 },
			{ 400, SUBERR_NONE, "Bad Request", IDS_ATLSRV_BAD_REQUEST },
			{ 401, SUBERR_NONE, "Unauthorized", IDS_ATLSRV_AUTH_REQUIRED },
			{ 402, SUBERR_NONE, "Payment Required", 0 },
			{ 403, SUBERR_NONE, "Forbidden", IDS_ATLSRV_FORBIDDEN },
			{ 404, SUBERR_NONE, "Not Found", IDS_ATLSRV_NOT_FOUND },
			{ 405, SUBERR_NONE, "Method Not Allowed", 0 },
			{ 406, SUBERR_NONE, "Not Acceptable", 0 },
			{ 407, SUBERR_NONE, "Proxy Authentication Required", 0 },
			{ 408, SUBERR_NONE, "Request Timeout", 0 },
			{ 409, SUBERR_NONE, "Conflict", 0 },
			{ 410, SUBERR_NONE, "Gone", 0 },
			{ 411, SUBERR_NONE, "Length Required", 0 },
			{ 412, SUBERR_NONE, "Precondition Failed", 0 },
			{ 413, SUBERR_NONE, "Request Entity Too Long", 0 },
			{ 414, SUBERR_NONE, "Request-URI Too Long", 0 },
			{ 415, SUBERR_NONE, "Unsupported Media Type", 0 },
			{ 416, SUBERR_NONE, "Requested Range Not Satisfiable", 0 },
			{ 417, SUBERR_NONE, "Expectation Failed", 0 },
			{ 500, SUBERR_NONE, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR },
			{ 500, ISE_SUBERR_BADSRF, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_BADSRF },
			{ 500, ISE_SUBERR_HNDLFAIL, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_HNDLFAIL },
			{ 500, ISE_SUBERR_SYSOBJFAIL, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_SYSOBJFAIL},
			{ 500, ISE_SUBERR_READFILEFAIL, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_READFILEFAIL},
			{ 500, ISE_SUBERR_LOADFILEFAIL, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_LOADFILEFAIL},
			{ 500, ISE_SUBERR_LOADLIB, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_LOADLIB},
			{ 500, ISE_SUBERR_HANDLERIF, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_HANDLERIF},
			{ 500, ISE_SUBERR_OUTOFMEM, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_OUTOFMEM},
			{ 500, ISE_SUBERR_UNEXPECTED, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_UNEXPECTED},
			{ 500, ISE_SUBERR_STENCIL_PARSE_FAIL, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_STENCILPARSEFAIL},
			{ 500, ISE_SUBERR_STENCIL_LOAD_FAIL, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_STENCILLOADFAIL},
			{ 500, ISE_SUBERR_HANDLER_NOT_FOUND, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_HANDLERNOTFOUND},
			{ 500, ISE_SUBERR_BAD_HANDLER_TAG, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_BADHANDLERTAG},
			{ 500, ISE_SUBERR_LONGMETHODNAME, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_LONGMETHODNAME},
			{ 500, ISE_SUBERR_LONGHANDLERNAME, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_LONGHANDLERNAME},
			{ 500, ISE_SUBERR_NO_HANDLER_TAG, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_NOHANDLERTAG},
			{ 500, ISE_SUBERR_IMPERSONATIONFAILED, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_IMPERSONATIONFAILED},
			{ 500, ISE_SUBERR_ISAPISTARTUPFAILED, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_ISAPISTARTUPFAILED},
			{ 500, ISE_SUBERR_SOAPNOSOAPACTION, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_SOAPNOSOAPACTION},

			{ 501, SUBERR_NONE, "Not Implemented", IDS_ATLSRV_NOT_IMPLEMENTED },
			{ 502, SUBERR_NONE, "Bad Gateway", IDS_ATLSRV_BAD_GATEWAY },
			{ 503, SUBERR_NONE, "Service Unavailable", IDS_ATLSRV_SERVICE_NOT_AVAILABLE },
			{ 504, SUBERR_NONE, "Gateway Timeout", 0 },
			{ 505, SUBERR_NONE, "HTTP Version Not Supported", 0 },
		};

		// look for the error
		for (int i=0; i<sizeof(s_Errors)/sizeof(s_Errors[0]); i++)
		{
			if ((s_Errors[i].uHttpError == uError) && (s_Errors[i].uHttpSubError == uSubErr))
			{
				if (ppszHeader)
					*ppszHeader = s_Errors[i].szHeader;
				if (puResId)
					*puResId = s_Errors[i].uResId;
				return TRUE;
			}
		}

		// not found
		return FALSE;
	}
}; // CDefaultErrorProvider

template<class HttpUserErrorTextProvider>
void GetStatusHeader(__inout CStringA &strStatus, __in DWORD dwStatus, __in DWORD dwSubStatus, __in HttpUserErrorTextProvider* pErrorProvider, __out_opt UINT *puResId = NULL) throw(...)
{	
	ATLENSURE( pErrorProvider != NULL );	

	LPCSTR szHeadErr = NULL;
	// First, we check for the error text in the extension's user error text provider
	BOOL bRet = pErrorProvider->GetErrorText(dwStatus, dwSubStatus, &szHeadErr, puResId);
	if (!bRet)
	{
		szHeadErr = "";
	}

	char szBuf[512];
	Checked::itoa_s(dwStatus, szBuf, _countof(szBuf), 10);

	// add the space after the 3 digit response code
	szBuf[3] = ' ';
	szBuf[4] = '\0';
	strStatus.SetString(szBuf, 4);
	strStatus.Append(szHeadErr);
}

template<class HttpUserErrorTextProvider>
void RenderError(__in IHttpServerContext *pServerContext, __in DWORD dwStatus, __in DWORD dwSubStatus, __in HttpUserErrorTextProvider* pErrorProvider)
{
	ATLENSURE( pServerContext != NULL );
	ATLENSURE( pErrorProvider != NULL );
	_ATLTRY
	{
		UINT uResId = 0;

		CFixedStringT<CStringA, 256> strStatus;
		GetStatusHeader(strStatus, dwStatus, dwSubStatus, pErrorProvider, &uResId);
		pServerContext->SendResponseHeader(NULL, strStatus, FALSE);

		LPCSTR szBody = strStatus;
		DWORD dwBodyLen = strStatus.GetLength();
		CFixedStringT<CStringA, 1024> strBody;
		if (uResId)
		{
			// load the body string from a resource
			if (strBody.LoadString(uResId))
			{
				szBody = strBody;
				dwBodyLen = strBody.GetLength();
			}
		}

		pServerContext->WriteClient((void *) szBody, &dwBodyLen);
	}
	_ATLCATCHALL()
	{
		// last resort message when low on memory
		LPCSTR szError;
		BOOL bRes;
		bRes = CDefaultErrorProvider::GetErrorText(dwStatus, dwSubStatus, &szError, 0);
		if (!bRes)
			bRes = CDefaultErrorProvider::GetErrorText(dwStatus, SUBERR_NONE, &szError, 0);
		if (!bRes)
			bRes = CDefaultErrorProvider::GetErrorText(500, SUBERR_NONE, &szError, 0);
		if(!szError)
		{
			szError="Unknown Error"; // last resort, can't localize 
		}
		DWORD dwBodyLen = (DWORD) strlen(szError);
		pServerContext->WriteClient((void *) szError, &dwBodyLen);
	}
}

// Call this function to retrieve the full canonical physical path 
 // of a file relative to the current script.
//
// Returns TRUE on success, FALSE on error.
//
// szFile           A file path relative to the current script directory for which
//                  you are trying to retrieve the full path.
//
// szFullFileName   A caller-allocated buffer of at least MAX_PATH characters in length.
//                  On success, contains the the full canonical path of szFile.
//
// pServerContext   The context for the current request. The context is used to obtain the
//                  current script directory.
inline BOOL GetScriptFullFileName(
	__in LPCSTR szFile,
	__in_ecount(MAX_PATH) LPSTR szFullFileName,
	__in IHttpServerContext* pServerContext) throw(...)
{
	ATLENSURE( szFile != NULL );
	ATLASSERT( szFullFileName != NULL );
	ATLENSURE( pServerContext != NULL );

	char szTmpScriptPath[MAX_PATH];
	LPCSTR szTmp = pServerContext->GetScriptPathTranslated();

	if (!szTmp)
	{
		return FALSE;
	}

	if (!SafeStringCopy(szTmpScriptPath, szTmp))
	{
		// path is too long
		return FALSE;
	}

	CHAR *szScriptPath = szTmpScriptPath;

	LPSTR szBackslash;
	if (*szFile != '\\')
	{
		szBackslash = strrchr(szScriptPath, '\\');

		ATLASSERT( *(szScriptPath+strlen(szScriptPath)) != '\\');

		if (szBackslash)
			szBackslash++;
	}
	else
	{
		// handle case where szFile is of the form \directory\etc\etc
		szBackslash = strchr(szScriptPath, '\\');
	}

	if (szBackslash)
		*szBackslash = '\0';

	int nScriptPathLen = (int)(szBackslash ? strlen(szScriptPath) : 0);
	int nFileLen = (int) strlen(szFile);
	int newLen = nScriptPathLen + nFileLen;

	if ((newLen < nScriptPathLen) || (newLen < nFileLen) || (newLen > MAX_PATH-1))
	{
		return FALSE;
	}
	CHAR szTemp[MAX_PATH];
	if (nScriptPathLen)
	{
		Checked::memcpy_s(szTemp, MAX_PATH, szScriptPath, nScriptPathLen);
	}
	Checked::memcpy_s(szTemp + nScriptPathLen, MAX_PATH-nScriptPathLen, szFile, nFileLen);
	*(szTemp + newLen) = 0;
	
	return PathCanonicalizeA(szFullFileName, szTemp);
}

enum ATLSRV_STATE
{
	ATLSRV_STATE_BEGIN,     // The request has just arrived, and the type has not been determined
	ATLSRV_STATE_CONTINUE,  // The request is a continuation of an async request
	ATLSRV_STATE_DONE,      // The request is a continuation of an async request, but the server is done with it
	ATLSRV_STATE_CACHE_DONE // The request is the callback of a cached page
};

enum ATLSRV_REQUESTTYPE
{
	ATLSRV_REQUEST_UNKNOWN=-1,  // The request type isn't known yet
	ATLSRV_REQUEST_STENCIL,     // The request is for a .srf file
	ATLSRV_REQUEST_DLL          // The request is for a .dll file
};

// Flags the InitRequest can return in dwStatus
#define  ATLSRV_INIT_USECACHE    1
#define  ATLSRV_INIT_USEASYNC    2
#define  ATLSRV_INIT_USEASYNC_EX 4 // required for use of NOFLUSH status

typedef HTTP_CODE (IRequestHandler::*PFnHandleRequest)(AtlServerRequest *pRequestInfo, IServiceProvider *pProvider);
typedef void (*PFnAsyncComplete)(AtlServerRequest *pRequestInfo, DWORD cbIO, DWORD dwError);

struct AtlServerRequest
{
	DWORD cbSize;							// For future compatibility
	IHttpServerContext *pServerContext;		// Necessary because it wraps the ECB
	ATLSRV_REQUESTTYPE dwRequestType;		// See the ATLSRV variables above
											// Indicates whether it was called through an .srf file or through a .dll file
	ATLSRV_STATE dwRequestState;			// See the ATLSRV variables above
											// Indicates what state of completion the request is in
	IRequestHandler *pHandler;				// Necessary because the callback (for async calls) must know where to
											// route the request
	HINSTANCE hInstDll;						// Necessary in order to release the dll properly (for async calls)
	IIsapiExtension *pExtension;			// Necessary to requeue the request (for async calls)
	IDllCache* pDllCache;					// Necessary to release the dll in async callback

	HANDLE hFile;
	HCACHEITEM hEntry;
	IFileCache* pFileCache;

	HANDLE m_hMutex;						// necessary to syncronize calls to HandleRequest
											// if HandleRequest could potientially make an
											// async call before returning. only used
											// if indicated with ATLSRV_INIT_USEASYNC_EX

	DWORD dwStartTicks;						// Tick count when the request was received
	EXTENSION_CONTROL_BLOCK *pECB;
	PFnHandleRequest pfnHandleRequest;
	PFnAsyncComplete pfnAsyncComplete;
	LPCSTR pszBuffer;						// buffer to be flushed asyncronously
	DWORD dwBufferLen;						// length of data in pszBuffer
	void* pUserData;						// value that can be used to pass user data between parent and child handlers
};

inline void _ReleaseAtlServerRequest(__inout AtlServerRequest* pRequest)
{	
	ATLENSURE(pRequest!=NULL);
	if (pRequest->pHandler)
		pRequest->pHandler->Release();
	if (pRequest->pServerContext)
		pRequest->pServerContext->Release();
	if (pRequest->pDllCache && pRequest->hInstDll)
		pRequest->pDllCache->ReleaseModule(pRequest->hInstDll);
	if (pRequest->m_hMutex)
		CloseHandle(pRequest->m_hMutex);
}

typedef BOOL (__stdcall *GETATLHANDLERBYNAME)(LPCSTR szHandlerName, IIsapiExtension *pExtension, IUnknown **ppHandler);
typedef BOOL (__stdcall *INITIALIZEATLHANDLERS)(IHttpServerContext*, IIsapiExtension*);
typedef void (__stdcall *UNINITIALIZEATLHANDLERS)();

// initial size of thread worker heap (per thread)
// The heap is growable.  The default initial is 16KB
#ifndef ATLS_WORKER_HEAP_SIZE
#define ATLS_WORKER_HEAP_SIZE 16384
#endif

class CIsapiWorker
{
public:
	typedef AtlServerRequest* RequestType;
	HANDLE m_hHeap;
#ifndef ATL_NO_SOAP
	CComPtr<ISAXXMLReader> m_spReader;
#endif

	CIsapiWorker() throw()
	{
		m_hHeap = NULL;
	}

	virtual ~CIsapiWorker() throw()
	{
		ATLASSUME(m_hHeap == NULL);
	}

	virtual BOOL Initialize(__inout __crt_typefix(IIsapiExtension*) void *pvParam)
	{
		IIsapiExtension* pExtension = (IIsapiExtension*) pvParam;
		ATLENSURE(pExtension);
		if (!(pExtension->OnThreadAttach()))
			return FALSE;

		m_hHeap = HeapCreate(HEAP_NO_SERIALIZE, ATLS_WORKER_HEAP_SIZE, 0);
		if (!m_hHeap)
			return FALSE;
#ifndef ATL_NO_SOAP
		if (FAILED(m_spReader.CoCreateInstance(ATLS_SAXXMLREADER_CLSID, NULL, CLSCTX_INPROC_SERVER )))
		{
			ATLASSERT( FALSE );
			ATLTRACE( atlTraceISAPI, 0, _T("MSXML3 is not installed -- web services will not work.") );
		}
#endif
		return pExtension->SetThreadWorker(this);
	}

	virtual void Terminate(__inout_opt __crt_typefix(IIsapiExtension*) void* pvParam) throw()
	{
		if (m_hHeap)
		{
			if (HeapDestroy(m_hHeap))
				m_hHeap = NULL;
			else
			{
				ATLASSERT(FALSE);
			}
		}

#ifndef ATL_NO_SOAP
		m_spReader.Release();
#endif
		if (pvParam != NULL)
			(static_cast<IIsapiExtension*>(pvParam))->OnThreadTerminate();
	}

	void Execute(__inout AtlServerRequest *pRequestInfo, __inout __crt_typefix(IIsapiExtension*) void *pvParam, __reserved OVERLAPPED *pOverlapped)
	{
		ATLENSURE(pRequestInfo != NULL);
		ATLENSURE(pvParam != NULL);
		(pOverlapped);    // unused
		ATLASSUME(m_hHeap != NULL);
		// any exceptions thrown at this point should have been caught in an
		// override of DispatchStencilCall. They will not be thrown out of this
		// function.
		_ATLTRY
		{
			(static_cast<IIsapiExtension*>(pvParam))->DispatchStencilCall(pRequestInfo);
		}
		_ATLCATCHALL()
		{
			ATLTRACE(_T("Warning. An uncaught exception was thrown from DispatchStencilCall\n"));
			ATLASSERT(FALSE);
		}
	}

	virtual BOOL GetWorkerData(DWORD /*dwParam*/, void ** /*ppvData*/) throw()
	{
		return FALSE;
	}
};

inline void _AtlGetScriptPathTranslated(
	__in LPCSTR szPathTranslated, 
	__inout CFixedStringT<CStringA, MAX_PATH>& strScriptPathTranslated)
{
	ATLENSURE(szPathTranslated!=NULL);
	LPCSTR szEnd = szPathTranslated;

	while (TRUE)
	{
		while (*szEnd != '.' && *szEnd != '\0')
			szEnd++;
		if (*szEnd == '\0')
			break;

		szEnd++;

		size_t nLen(0);
		if (!AsciiStrnicmp(szEnd, c_AtlDLLExtension+1, ATLS_DLL_EXTENSION_LEN))
			nLen = ATLS_DLL_EXTENSION_LEN;
		else if (!AsciiStrnicmp(szEnd, c_AtlSRFExtension+1, ATLS_EXTENSION_LEN))
			nLen = ATLS_EXTENSION_LEN;

		if (nLen)
		{
			szEnd += nLen;
			if (!*szEnd || *szEnd == '/' || *szEnd == '\\' || *szEnd == '?' || *szEnd == '#')
				break;
		}
	}

	DWORD dwResult = (DWORD)(szEnd - szPathTranslated);
	char *szScriptPathTranslated = NULL;
	ATLTRY(szScriptPathTranslated = strScriptPathTranslated.GetBuffer(dwResult));
	if (szScriptPathTranslated)
	{
		Checked::memcpy_s(szScriptPathTranslated, dwResult, szPathTranslated, dwResult);
		szScriptPathTranslated[dwResult] = '\0';
		strScriptPathTranslated.ReleaseBuffer(dwResult);
	}
}


struct CStencilState
{
	CStencilState() throw()
	{
		dwIndex = 0;
		locale = CP_ACP;
		pIncludeInfo = NULL;
		pParentInfo = NULL;
	}

	DWORD dwIndex;
	LCID locale;
	AtlServerRequest* pIncludeInfo;
	AtlServerRequest* pParentInfo;
};

class CWrappedServerContext:
	public IHttpServerContext
{
public:
	CComPtr<IHttpServerContext> m_spParent;

	CWrappedServerContext() throw()
	{
	}

	virtual ~CWrappedServerContext() throw()
	{
	}

	CWrappedServerContext(__in IHttpServerContext *pParent) throw()
	{
		m_spParent = pParent;
	}

	LPCSTR GetRequestMethod()
	{
		ATLENSURE(m_spParent);
		return m_spParent->GetRequestMethod();
	}

	LPCSTR GetQueryString()
	{
		ATLENSURE(m_spParent);
		return m_spParent->GetQueryString();
	}

	LPCSTR GetPathInfo()
	{
		ATLENSURE(m_spParent);
		return m_spParent->GetPathInfo();
	}

	LPCSTR GetScriptPathTranslated()
	{
		ATLENSURE(m_spParent);
		return m_spParent->GetScriptPathTranslated();
	}

	LPCSTR GetPathTranslated()
	{
		ATLENSURE(m_spParent);
		return m_spParent->GetPathTranslated();
	}

	DWORD GetTotalBytes()
	{
		ATLENSURE(m_spParent);
		return m_spParent->GetTotalBytes();
	}

	DWORD GetAvailableBytes()
	{
		ATLENSURE(m_spParent);
		return m_spParent->GetAvailableBytes();
	}

	BYTE *GetAvailableData()
	{
		ATLENSURE(m_spParent);
		return m_spParent->GetAvailableData();
	}

	LPCSTR GetContentType()
	{
		ATLENSURE(m_spParent);
		return m_spParent->GetContentType();
	}

	__checkReturn BOOL GetServerVariable(__in_z LPCSTR pszVariableName, __out_ecount_part(*pdwSize,*pdwSize) LPSTR pvBuffer, __inout DWORD *pdwSize)
	{
		ATLENSURE(m_spParent);
		return m_spParent->GetServerVariable(pszVariableName, pvBuffer, pdwSize);
	}

	__checkReturn BOOL WriteClient(__in_bcount(*pdwBytes) void *pvBuffer, __inout DWORD *pdwBytes)
	{ 
		ATLENSURE(m_spParent);
		return m_spParent->WriteClient(pvBuffer, pdwBytes);
	}

	__checkReturn BOOL AsyncWriteClient(__in_bcount(*pdwBytes) void * pvBuffer, __inout DWORD * pdwBytes)
	{
		ATLENSURE(m_spParent);
		return m_spParent->AsyncWriteClient(pvBuffer, pdwBytes);
	}

	__checkReturn BOOL ReadClient(__out_bcount_part(*pdwSize,*pdwSize) void * pvBuffer, __inout DWORD * pdwSize)
	{
		ATLENSURE(m_spParent);
		return m_spParent->ReadClient(pvBuffer, pdwSize);
	}

	__checkReturn BOOL AsyncReadClient(__out_bcount_part(*pdwSize,*pdwSize) void * pvBuffer, __inout DWORD * pdwSize)
	{
		ATLENSURE(m_spParent);
		return m_spParent->AsyncReadClient(pvBuffer, pdwSize);
	}

	__checkReturn BOOL SendRedirectResponse(__in LPCSTR pszRedirectUrl)
	{
		ATLENSURE(m_spParent);
		return m_spParent->SendRedirectResponse(pszRedirectUrl);
	}

	__checkReturn BOOL GetImpersonationToken(__out HANDLE * pToken)
	{
		ATLENSURE(m_spParent);
		return m_spParent->GetImpersonationToken(pToken);
	}

	__checkReturn BOOL SendResponseHeader(__in LPCSTR pszHeader, __in LPCSTR pszStatusCode, __in BOOL fKeepConn)
	{
		ATLENSURE(m_spParent);
		return m_spParent->SendResponseHeader(pszHeader, pszStatusCode, fKeepConn);
	}

	__checkReturn BOOL DoneWithSession(__in DWORD dwHttpStatusCode)
	{
		ATLENSURE(m_spParent);
		return m_spParent->DoneWithSession(dwHttpStatusCode);
	}

	__checkReturn BOOL RequestIOCompletion(__in PFN_HSE_IO_COMPLETION pfn, DWORD * pdwContext)
	{
		ATLENSURE(m_spParent);
		return m_spParent->RequestIOCompletion(pfn, pdwContext);
	}

	BOOL TransmitFile(__in HANDLE hFile, __in_opt PFN_HSE_IO_COMPLETION pfn, void * pContext,
		__in LPCSTR szStatusCode, __in DWORD dwBytesToWrite, __in DWORD dwOffset, __in_bcount_opt(dwHeadLen) void * pvHead,
		__in DWORD dwHeadLen, __in_bcount_opt(dwTailLen) void * pvTail, __in DWORD dwTailLen, __in DWORD dwFlags)
	{
		ATLENSURE(m_spParent);
		return m_spParent->TransmitFile(hFile, pfn, pContext, szStatusCode,
			dwBytesToWrite, dwOffset, pvHead, dwHeadLen, pvTail, dwTailLen,
			dwFlags);
	}

	BOOL AppendToLog(__in LPCSTR szMessage, __in_opt DWORD* pdwLen)
	{
		ATLENSURE(m_spParent);
		return m_spParent->AppendToLog(szMessage, pdwLen);
	}

	BOOL MapUrlToPathEx(__in_bcount(dwLen) LPCSTR szLogicalPath, __in DWORD dwLen, __out HSE_URL_MAPEX_INFO *pumInfo)
	{
		ATLENSURE(m_spParent);
		return m_spParent->MapUrlToPathEx(szLogicalPath, dwLen, pumInfo);
	}
}; // class CWrappedServerContext

// Wraps the EXTENSION_CONTROL_BLOCK structure used by IIS to provide
// an ISAPI extension with information about the current request and
// access to the web server's functionality.
class CServerContext :
	public CComObjectRootEx<CComMultiThreadModel>,
	public IHttpServerContext
{
public:
	BEGIN_COM_MAP(CServerContext)
		COM_INTERFACE_ENTRY(IHttpServerContext)
	END_COM_MAP()

	CServerContext() throw()
	{
		m_pECB = NULL;
		m_bHeadersHaveBeenSent = false;
	}
	virtual ~CServerContext() throw()
	{
	}

	void Initialize(__in EXTENSION_CONTROL_BLOCK *pECB)
	{
		ATLENSURE(pECB);
		m_pECB = pECB;

		// Initialize the translated script path
		_AtlGetScriptPathTranslated(GetPathTranslated(), m_strScriptPathTranslated);
	}

	// Returns a nul-terminated string that contains the HTTP method of the current request.
	// Examples of common HTTP methods include "GET" and "POST".
	// Equivalent to the REQUEST_METHOD server variable or EXTENSION_CONTROL_BLOCK::lpszMethod.
	LPCSTR GetRequestMethod()
	{
		ATLENSURE(m_pECB);
		return m_pECB->lpszMethod;
	}

	// Returns a nul-terminated string that contains the query information.
	// This is the part of the URL that appears after the question mark (?). 
	// Equivalent to the QUERY_STRING server variable or EXTENSION_CONTROL_BLOCK::lpszQueryString.
	LPCSTR GetQueryString()
	{
		ATLENSURE(m_pECB);
		return m_pECB->lpszQueryString;
	}

	// Returns a nul-terminated string that contains the path of the current request.
	// This is the part of the URL that appears after the server name, but before the query string.
	// Equivalent to the PATH_INFO server variable or EXTENSION_CONTROL_BLOCK::lpszPathInfo.
	LPCSTR GetPathInfo()
	{
		ATLENSURE(m_pECB);
		return m_pECB->lpszPathInfo;
	}

	// Call this function to retrieve a nul-terminated string containing the physical path of the script.
	//
	// Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the 
	// buffer (including the nul-terminating byte).
	// The script path is the same as GetPathTranslated up to the first .srf or .dll.
	// For example, if GetPathTranslated returns "c:\inetpub\vcisapi\hello.srf\goodmorning",
	// then this function returns "c:\inetpub\vcisapi\hello.srf".
	LPCSTR GetScriptPathTranslated()
	{
		ATLASSUME(m_pECB);
		return m_strScriptPathTranslated;
	}


	// Returns a nul-terminated string that contains the translated path of the requested resource.
	// This is the path of the resource on the local server.
	// Equivalent to the PATH_TRANSLATED server variable or EXTENSION_CONTROL_BLOCK::lpszPathTranslated.
	LPCSTR GetPathTranslated()
	{
		ATLENSURE(m_pECB);
		return m_pECB->lpszPathTranslated;
	}

	// Returns the total number of bytes to be received from the client.
	// If this value is 0xffffffff, then there are four gigabytes or more of available data.
	// In this case, ReadClient or AsyncReadClient should be called until no more data is returned.
	// Equivalent to the CONTENT_LENGTH server variable or EXTENSION_CONTROL_BLOCK::cbTotalBytes. 
	DWORD GetTotalBytes()
	{
		ATLENSURE(m_pECB);
		return m_pECB->cbTotalBytes;
	}

	// Returns the number of bytes available in the request buffer accessible via GetAvailableData.
	// If GetAvailableBytes returns the same value as GetTotalBytes, the request buffer contains the whole request.
	// Otherwise, the remaining data should be read from the client using ReadClient or AsyncReadClient.
	// Equivalent to EXTENSION_CONTROL_BLOCK::cbAvailable.
	DWORD GetAvailableBytes()
	{
		ATLENSURE(m_pECB);
		return m_pECB->cbAvailable;
	}

	// Returns a pointer to the request buffer containing the data sent by the client.
	// The size of the buffer can be determined by calling GetAvailableBytes.
	// Equivalent to EXTENSION_CONTROL_BLOCK::lpbData
	BYTE *GetAvailableData()
	{
		ATLENSURE(m_pECB);
		return m_pECB->lpbData;
	}

	// Returns a nul-terminated string that contains the content type of the data sent by the client.
	// Equivalent to the CONTENT_TYPE server variable or EXTENSION_CONTROL_BLOCK::lpszContentType.
	LPCSTR GetContentType()
	{
		ATLENSURE(m_pECB);
		return m_pECB->lpszContentType;
	}

	// Call this function to retrieve a nul-terminated string containing the value of the requested server variable.
	// Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	// Equivalent to  EXTENSION_CONTROL_BLOCK::GetServerVariable.
	__checkReturn BOOL GetServerVariable(
		__in LPCSTR pszVariableName,
		__out_ecount_part(*pdwSize,*pdwSize) LPSTR pvBuffer,
		__inout DWORD *pdwSize)
	{
		ATLENSURE(m_pECB);
		ATLASSERT(pszVariableName);
		ATLASSERT(pdwSize);

		if (pszVariableName && pdwSize)
		{
			return m_pECB->GetServerVariable(m_pECB->ConnID, (LPSTR) pszVariableName,
							pvBuffer, pdwSize);
		}
		return FALSE;
	}

	// Synchronously sends the data present in the given buffer to the client that made the request.
	// Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
	// Equivalent to EXTENSION_CONTROL_BLOCK::WriteClient(..., HSE_IO_SYNC).
	__checkReturn BOOL WriteClient(__in_bcount(*pdwBytes) void *pvBuffer, __inout DWORD *pdwBytes)
	{
		ATLENSURE(m_pECB);
		ATLASSERT(pvBuffer);
		ATLASSERT(pdwBytes);

		if (pvBuffer && pdwBytes)
		{
			return m_pECB->WriteClient(m_pECB->ConnID, pvBuffer, pdwBytes, HSE_IO_SYNC | HSE_IO_NODELAY);
		}
		return FALSE;
	}

	// Asynchronously sends the data present in the given buffer to the client that made the request.
	// Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
	// Equivalent to EXTENSION_CONTROL_BLOCK::WriteClient(..., HSE_IO_ASYNC).
	__checkReturn BOOL AsyncWriteClient(__in_bcount(*pdwBytes) void *pvBuffer, __inout DWORD *pdwBytes)
	{
		ATLENSURE(m_pECB);
		ATLASSERT(pvBuffer);
		ATLASSERT(pdwBytes);

		if (pvBuffer && pdwBytes)
		{
			return m_pECB->WriteClient(m_pECB->ConnID, pvBuffer, pdwBytes, HSE_IO_ASYNC | HSE_IO_NODELAY);
		}
		return FALSE;
	}

	// Call this function to synchronously read information from the body of the web client's HTTP request into the buffer supplied by the caller.
	// Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
	// Equivalent to EXTENSION_CONTROL_BLOCK::ReadClient.
	__checkReturn BOOL ReadClient(__out_ecount_part(*pdwSize,*pdwSize) void *pvBuffer, __inout DWORD *pdwSize)
	{
		ATLENSURE(m_pECB);
		ATLASSERT(pvBuffer);
		ATLASSERT(pdwSize);

		if (pvBuffer && pdwSize)
		{
			return m_pECB->ReadClient(m_pECB->ConnID, pvBuffer, pdwSize);
		}
		return FALSE;
	}

	// Call this function to asynchronously read information from the body of the web client's HTTP request into the buffer supplied by the caller.
	// Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
	// Equivalent to the HSE_REQ_ASYNC_READ_CLIENT server support function.
	__checkReturn BOOL AsyncReadClient(__out_bcount_part(*pdwSize,*pdwSize) void *pvBuffer, __inout DWORD *pdwSize)
	{
		// To call this function successfully someone has to have already
		// called RequestIOCompletion specifying the callback function
		// to be used for IO completion.
		ATLENSURE(m_pECB);
		ATLASSERT(pvBuffer);
		ATLASSERT(pdwSize);

		if (pvBuffer && pdwSize)
		{
			DWORD dwFlag = HSE_IO_ASYNC;
			return m_pECB->ServerSupportFunction(m_pECB->ConnID,
				HSE_REQ_ASYNC_READ_CLIENT, pvBuffer, pdwSize,
				&dwFlag);
		}
		return FALSE;
	}

	// Call this function to redirect the client to the specified URL.
	// The client receives a 302 (Found) HTTP status code.
	// Returns TRUE on success, and FALSE on failure.
	// Equivalent to the HSE_REQ_SEND_URL_REDIRECT_RESP server support function.
	__checkReturn BOOL SendRedirectResponse(__in LPCSTR pszRedirectUrl)
	{
		ATLENSURE(m_pECB);
		ATLENSURE(pszRedirectUrl);

		if (pszRedirectUrl)
		{
			DWORD dwSize = (DWORD) strlen(pszRedirectUrl);
			return m_pECB->ServerSupportFunction(m_pECB->ConnID,
				HSE_REQ_SEND_URL_REDIRECT_RESP,
				(void *) pszRedirectUrl, &dwSize, NULL);
		}
		return FALSE;
	}

	// Call this function to retrieve a handle to the impersonation token for this request.
	// An impersonation token represents a user context. You can use the handle in calls to ImpersonateLoggedOnUser or SetThreadToken.
	// Do not call CloseHandle on the handle.
	// Returns TRUE on success, and FALSE on failure.
	// Equivalent to the HSE_REQ_GET_IMPERSONATION_TOKEN server support function.
	__checkReturn BOOL GetImpersonationToken(__out HANDLE * pToken)
	{
		ATLENSURE(m_pECB);
		if (pToken)
		{
			return m_pECB->ServerSupportFunction(m_pECB->ConnID,
				HSE_REQ_GET_IMPERSONATION_TOKEN, pToken,
				NULL, NULL);
		}
		return FALSE;
	}

	// Call this function to send an HTTP response header to the client including the HTTP status, server version, message time, and MIME version.
	// Returns TRUE on success, and FALSE on failure.
	// Equivalent to the HSE_REQ_SEND_RESPONSE_HEADER_EX server support function.
	__checkReturn BOOL SendResponseHeader(
		__in LPCSTR pszHeader = "Content-Type: text/html\r\n\r\n",
		__in LPCSTR pszStatusCode = "200 OK",
		__in BOOL fKeepConn=FALSE)
	{
		ATLENSURE(m_pECB);

		if (m_bHeadersHaveBeenSent)
			return TRUE;

		HSE_SEND_HEADER_EX_INFO hex;
		hex.pszStatus = pszStatusCode;
		hex.pszHeader = pszHeader;
		hex.cchStatus = (DWORD)(pszStatusCode ? strlen(pszStatusCode) : 0);
		hex.cchHeader = (DWORD)(pszHeader ? strlen(pszHeader) : 0);
		hex.fKeepConn = fKeepConn;

		m_bHeadersHaveBeenSent = true;

		return m_pECB->ServerSupportFunction(m_pECB->ConnID,
			HSE_REQ_SEND_RESPONSE_HEADER_EX,
			&hex, NULL, NULL);
	}

	// Call this function to terminate the session for the current request.
	// Returns TRUE on success, and FALSE on failure.
	// Equivalent to the HSE_REQ_DONE_WITH_SESSION server support function.
	__checkReturn BOOL DoneWithSession(__in DWORD dwHttpStatusCode)
	{
		ATLENSURE(m_pECB);

		m_pECB->dwHttpStatusCode = dwHttpStatusCode;

		DWORD dwStatusCode = (dwHttpStatusCode >= 400) ? HSE_STATUS_ERROR : HSE_STATUS_SUCCESS;

		return m_pECB->ServerSupportFunction(m_pECB->ConnID,
			HSE_REQ_DONE_WITH_SESSION, &dwStatusCode, NULL, NULL);
	}

	// Call this function to set a special callback function that will be used for handling the completion of asynchronous I/O operations.
	// Returns TRUE on success, and FALSE on failure.
	// Equivalent to the HSE_REQ_IO_COMPLETION server support function.
	__checkReturn BOOL RequestIOCompletion(__in PFN_HSE_IO_COMPLETION pfn, DWORD *pdwContext)
	{
		ATLENSURE(m_pECB);
		ATLASSERT(pfn);

		if (pfn)
		{
			return m_pECB->ServerSupportFunction(m_pECB->ConnID,
				HSE_REQ_IO_COMPLETION, pfn, NULL, pdwContext);
		}
		return FALSE;
	}

	// Call this function to transmit a file asynchronously to the client.
	// Returns TRUE on success, and FALSE on failure.
	// Equivalent to the HSE_REQ_TRANSMIT_FILE server support function.
	BOOL TransmitFile(
		__in HANDLE hFile,
		__in_opt PFN_HSE_IO_COMPLETION pfn,
		void *pContext,
		__in LPCSTR szStatusCode,
		__in DWORD dwBytesToWrite,
		__in DWORD dwOffset,
		__in_bcount_opt(dwHeadLen) void *pvHead,
		__in DWORD dwHeadLen,
		__in_bcount_opt(dwTailLen) void *pvTail,
		__in DWORD dwTailLen,
		__in DWORD dwFlags)
	{
		ATLENSURE(m_pECB);

		HSE_TF_INFO tf;
		tf.hFile = hFile;
		tf.BytesToWrite = dwBytesToWrite;
		tf.Offset = dwOffset;
		tf.pContext = pContext;
		tf.pfnHseIO = pfn;
		tf.pHead = pvHead;
		tf.HeadLength = dwHeadLen;
		tf.pTail = pvTail;
		tf.TailLength = dwTailLen;
		tf.pszStatusCode = szStatusCode;
		tf.dwFlags = dwFlags;
		return m_pECB->ServerSupportFunction(m_pECB->ConnID,
			HSE_REQ_TRANSMIT_FILE, &tf, NULL, NULL);
	}

	// Appends the string szMessage to the web server log for the current
	// request.
	// Returns TRUE on success, FALSE on failure.
	// Equivalent to the HSE_APPEND_LOG_PARAMETER server support function.
	BOOL AppendToLog(__in LPCSTR szMessage, __in_opt DWORD *pdwLen)
	{
		DWORD dwLen = 0;
		if (!pdwLen)
		{
			if(!szMessage)
			{
				return FALSE;
			}
			dwLen = (DWORD)strlen(szMessage);
		}
		else
		{
			dwLen = *pdwLen;
		}

		return m_pECB->ServerSupportFunction(m_pECB->ConnID,
			HSE_APPEND_LOG_PARAMETER, (void *)szMessage, 
			&dwLen, NULL);
	}

	// Maps a logical Url Path to a physical path
	// Returns TRUE on success, FALSE on failure.
	// Equivalent to the HSE_REQ_MAP_URL_TO_PATH_EX server support function.
	// you can pass 0 for dwLen if szLogicalPath is null terminated
	BOOL MapUrlToPathEx(__in_bcount(dwLen) LPCSTR szLogicalPath, __in DWORD dwLen, __out HSE_URL_MAPEX_INFO *pumInfo)
	{
		ATLENSURE(m_pECB!=NULL);
		if (dwLen == 0)
			dwLen = (DWORD) strlen(szLogicalPath);
		return m_pECB->ServerSupportFunction(m_pECB->ConnID, HSE_REQ_MAP_URL_TO_PATH_EX, (void *) szLogicalPath,
			&dwLen, (DWORD *) pumInfo);
	}

protected:
	// The pointer to the extension control block provided by IIS.
	EXTENSION_CONTROL_BLOCK *m_pECB;
	bool m_bHeadersHaveBeenSent;

	// The translated script path
	CFixedStringT<CStringA, MAX_PATH> m_strScriptPathTranslated;

}; // class CServerContext

class CPageCachePeer
{
public:

	struct PeerInfo
	{
		CStringA strHeader;
		CStringA strStatus;
	};

	static BOOL Add(__inout PeerInfo * pDest, __in PeerInfo * pSrc) throw()
	{
		_ATLTRY
		{
			PeerInfo *pIn = (PeerInfo *)pSrc;
			pDest->strHeader = pIn->strHeader;
			pDest->strStatus = pIn->strStatus;
			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

	static BOOL Remove(const PeerInfo * /*pDest*/) throw()
	{
		return TRUE;
	}
};


class CCacheServerContext :
	public CComObjectRootEx<CComMultiThreadModel>,
	public CWrappedServerContext,
	public IPageCacheControl
{
private:

	CAtlTemporaryFile m_cacheFile;
	CComPtr<IFileCache> m_spCache;
	char m_szFullUrl[ATL_URL_MAX_URL_LENGTH + 1];
	FILETIME m_ftExpiration;
	BOOL m_bIsCached;
	CPageCachePeer::PeerInfo m_Headers;

public:

	BEGIN_COM_MAP(CCacheServerContext)
		COM_INTERFACE_ENTRY(IHttpServerContext)
		COM_INTERFACE_ENTRY(IPageCacheControl)
	END_COM_MAP()

	CCacheServerContext() throw()
	{
	}
	virtual ~CCacheServerContext() throw()
	{
	}

	BOOL Initialize(__in IHttpServerContext *pParent, __in IFileCache *pCache) throw()
	{
		ATLASSERT(pParent);
		ATLASSERT(pCache);

		if (pParent == NULL || pCache == NULL)
			return FALSE;

		m_spParent = pParent;
		m_spCache = pCache;

		if (FAILED(m_cacheFile.Create()))
			return FALSE;

		LPCSTR szPathInfo = pParent->GetPathInfo();
		LPCSTR szQueryString = pParent->GetQueryString();
		if ( szPathInfo == NULL || szQueryString == NULL)
			 return FALSE;

		LPSTR szTo = m_szFullUrl;
		int nSize = 0;
		while (*szPathInfo && nSize < ATL_URL_MAX_URL_LENGTH)
		{
			*szTo++ = *szPathInfo++;
			nSize++;
		}
		if (nSize >= ATL_URL_MAX_URL_LENGTH)
		{
			return FALSE;
		}
		*szTo++ = '?';
		nSize++;
		while (*szQueryString && nSize < ATL_URL_MAX_URL_LENGTH)
		{
			*szTo++ = *szQueryString++;
			nSize++;
		}
		if (nSize >= ATL_URL_MAX_URL_LENGTH)
		{
			return FALSE;
		}
		*szTo = '\0';

		memset(&m_ftExpiration, 0x00, sizeof(FILETIME));
		m_bIsCached = TRUE;

		return TRUE;
	}

	// IPageCacheControl methods
	HRESULT GetExpiration(__out FILETIME *pftExpiration) throw()
	{
		ATLASSERT(pftExpiration);
		if (!pftExpiration)
			return E_INVALIDARG;

		*pftExpiration = m_ftExpiration;

		return S_OK;
	}

	HRESULT SetExpiration(__in FILETIME ftExpiration) throw()
	{
		m_ftExpiration = ftExpiration;

		return S_OK;
	}

	__checkReturn BOOL IsCached() throw()
	{
		return m_bIsCached;
	}

	BOOL Cache(__in BOOL bCache) throw()
	{
		BOOL bRet = m_bIsCached;
		m_bIsCached = bCache;
		return bRet;
	}

	__checkReturn BOOL WriteClient(__in_bcount(*pdwBytes) void *pvBuffer, __inout DWORD *pdwBytes)
	{ 
		ATLENSURE(pvBuffer);
		ATLENSURE(pdwBytes);
		
		if (S_OK != m_cacheFile.Write(pvBuffer, *pdwBytes))
			return FALSE;
		ATLENSURE(m_spParent);
		return m_spParent->WriteClient(pvBuffer, pdwBytes);
	}

	__checkReturn BOOL DoneWithSession(__in DWORD dwHttpStatusCode)
	{
		ATLENSURE(m_spParent);

		_ATLTRY
		{
		   if (m_bIsCached)
		   {
			   CT2CA strFileName(m_cacheFile.TempFileName());
			   m_cacheFile.HandsOff();
			   m_spCache->AddFile(m_szFullUrl, strFileName, &m_ftExpiration, &m_Headers, NULL);
		   }
		   else
			   m_cacheFile.Close();
		}
		_ATLCATCHALL()
		{
			m_cacheFile.Close();
		}

		return m_spParent->DoneWithSession(dwHttpStatusCode);
	}

	__checkReturn BOOL GetImpersonationToken(__out HANDLE * pToken)
	{
		ATLTRACE(atlTraceISAPI, 0, _T("Getting impersonation token for cached page")
			_T(" -- Caching a page that requires special privileges to build is a possible security problem. ")
			_T("Future hits may get a cached page without going through the security checks done during the page creation process"));
		ATLENSURE(m_spParent);
		ATLASSERT(pToken);
		return m_spParent->GetImpersonationToken(pToken);
	}

	__checkReturn BOOL AppendToLog(__in LPCSTR szMessage, __in_opt DWORD* pdwLen)
	{
		ATLTRACE(atlTraceISAPI, 0, _T("Logging on cached page -- future hits will not log"));
		ATLENSURE(m_spParent);
		return m_spParent->AppendToLog(szMessage, pdwLen);
	}

	__checkReturn BOOL SendResponseHeader(
		__in LPCSTR pszHeader = "Content-Type: text/html\r\n\r\n",
		__in LPCSTR pszStatusCode = "200 OK",
		__in BOOL fKeepConn=FALSE)
	{
		ATLENSURE(m_spParent);

		m_Headers.strHeader = pszHeader;
		m_Headers.strStatus = pszStatusCode;

		return m_spParent->SendResponseHeader(pszHeader, pszStatusCode, fKeepConn);
	}

	// The methods below this point are actions that should not be performed on cached
	// pages, as they will not behave correctly.
	__checkReturn BOOL AsyncWriteClient(void * /*pvBuffer*/, DWORD * /*pdwBytes*/)
	{
		// Asynchronous calls will not work
		ATLASSERT(FALSE);
		return FALSE;
	}

	__checkReturn BOOL ReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/)
	{
		// Nobody should be reading from this client if the page is being cached
		// Also, only GET's are cached anyway
		ATLASSERT(FALSE);
		return FALSE;
	}

	__checkReturn BOOL AsyncReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/)
	{
		ATLASSERT(FALSE);
		return FALSE;
	}

	__checkReturn BOOL SendRedirectResponse(LPCSTR /*pszRedirectUrl*/)
	{
		ATLASSERT(FALSE);
		return FALSE;
	}


	__checkReturn BOOL RequestIOCompletion(PFN_HSE_IO_COMPLETION /*pfn*/, DWORD * /*pdwContext*/)
	{
		ATLASSERT(FALSE);
		return FALSE;
	}

	__checkReturn BOOL TransmitFile(
		HANDLE /*hFile*/,
		PFN_HSE_IO_COMPLETION /*pfn*/,
		void * /*pContext*/,
		LPCSTR /*szStatusCode*/,
		DWORD /*dwBytesToWrite*/,
		DWORD /*dwOffset*/,
		void * /*pvHead*/,
		DWORD /*dwHeadLen*/,
		void * /*pvTail*/,
		DWORD /*dwTailLen*/,
		DWORD /*dwFlags*/)
	{
		ATLASSERT(FALSE);
		return FALSE;
	}
};


// This class represents a collection of validation failures.
// Use this class in combination with CValidateObject to validate
// forms, cookies, or query strings and build up a collection of
// failures. If appropriate, use the information in the collection
// to return detailed responses to the client to help them correct the failures.


class CValidateContext 
{
public:
	enum { ATL_EMPTY_PARAMS_ARE_FAILURES = 0x00000001 };

	CValidateContext(__in DWORD dwFlags=0) throw()
	{
		m_bFailures = false;
		m_dwFlags = dwFlags;
	}

	bool SetResultAt(__in LPCSTR szName, __in DWORD type)
	{
		_ATLTRY
		{
			if (!VALIDATION_SUCCEEDED(type) ||
				(type == VALIDATION_S_EMPTY && (m_dwFlags & ATL_EMPTY_PARAMS_ARE_FAILURES)))
				m_bFailures = true;

			return TRUE == m_results.SetAt(szName,type);

		}
		_ATLCATCHALL()
		{
		}

		return false;
	}

	// Call this function to add a validation result to the collection managed by this object.
	// Each result is identified by a name and the type of result that occurred.
	// The result codes are the VALIDATION_ codes defined at the top of this file.
	// The bOnlyFailure parameter below is used to only allow failure results to
	// be added to the list of failures. The reason you'd want to do this is that
	// success codes should be the common case in validation routines so you can
	// use bOnlyFailures to limit the number of allocations by this class's base
	// map for mapping success results if you don't care about iterating successes.

	bool AddResult(__in LPCSTR szName, __in DWORD type, __in bool bOnlyFailures = true) throw()
	{
		_ATLTRY
		{
			if (!VALIDATION_SUCCEEDED(type) ||
				(type == VALIDATION_S_EMPTY && (m_dwFlags & ATL_EMPTY_PARAMS_ARE_FAILURES)))
				m_bFailures = true;

			if (!bOnlyFailures)
				return TRUE == m_results.Add(szName, type); // add everything

			else if (bOnlyFailures && 
					(!VALIDATION_SUCCEEDED(type) ||
					(type == VALIDATION_S_EMPTY && (m_dwFlags & ATL_EMPTY_PARAMS_ARE_FAILURES))))
				return TRUE == m_results.Add(szName, type); // only add failures
		}
		_ATLCATCHALL()
		{
		}

		return false;
	}

	// Returns true if there are no validation failures in the collection,
	// returns false otherwise.
	__checkReturn bool ParamsOK() throw()
	{
		return !m_bFailures;
	}

	// Returns the number of validation results in the collection.
	__checkReturn int GetResultCount() throw()
	{
		return m_results.GetSize();
	}

	// Call this function to retrieve the name and type of a
	// validation result based on its index in the collection.
	// Returns true on success, false on failure.
	//
	// i        The index of a result managed by this collection.
	//
	// strName  On success, the name of the result with index i.
	//
	// type     On success, the type of the result with index i.
	__checkReturn bool GetResultAt(__in int i, __out CStringA& strName, __out DWORD& type) throw()
	{
		if ( i >= 0 && i < m_results.GetSize())
		{
			_ATLTRY
			{
				strName = m_results.GetKeyAt(i);
				type = m_results.GetValueAt(i);
			}
			_ATLCATCHALL()
			{
				return false;
			}
			return true;
		}
		return false;
	}

	DWORD m_dwFlags;
protected:
	CSimpleMap<CStringA, DWORD> m_results;
	bool m_bFailures;
}; // CValidateContext



class CAtlValidator
{
public:
	template <class T, class TCompType>
	static DWORD Validate(
		__in T value,
		__in TCompType nMinValue,
		__in TCompType nMaxValue) throw()
	{
		DWORD dwRet = VALIDATION_S_OK;
		if (value < static_cast<T>(nMinValue))
			dwRet = VALIDATION_E_LENGTHMIN;
		else if (value > static_cast<T>(nMaxValue))
			dwRet = VALIDATION_E_LENGTHMAX;
		return dwRet;
	}

	static DWORD Validate( __in LPCSTR pszValue, __in int nMinChars, __in int nMaxChars) throw()
	{
		DWORD dwRet = VALIDATION_S_OK;
		if(!pszValue)
		{
			return VALIDATION_E_FAIL;
		}
		int nChars = (int) strlen(pszValue);
		if (nChars < nMinChars)
			dwRet = VALIDATION_E_LENGTHMIN;
		else if (nChars > nMaxChars)
			dwRet = VALIDATION_E_LENGTHMAX;
		return dwRet;
	}
	static DWORD Validate( __in double dblValue, __in double dblMinValue, __in double dblMaxValue) throw()
	{
		DWORD dwRet = VALIDATION_S_OK;
		if ( dblValue < (dblMinValue - ATL_EPSILON) )
			dwRet = VALIDATION_E_LENGTHMIN;
		else if ( dblValue > (dblMaxValue + ATL_EPSILON) )
			dwRet = VALIDATION_E_LENGTHMAX;
		return dwRet;
	}
};

// This class provides functions for retrieving and validating named values.
//
// The named values are expected to be provided in string form by the class used as
// the template parameter. CValidateObject provides the means of
// retrieving these values converted to data types chosen by you. You can validate the values
// by specifying a range for numeric values or by specifying a minimum and maximum length
// for string values.
//
// Call one of the Exchange overloads to retrieve a named value converted to your chosen data type.
// Call one of the Validate overloads to retrieve a named value converted to your chosen data type
// and validated against a minimum and maximum value or length supplied by you.
//
// To add validation functionality to the class TLookupClass, derive that class from CValidateObject<TLookupClass>
// and provide a Lookup function that takes a name as a string and returns the corresponding value
// also as a string:
//      LPCSTR Lookup(LPCSTR szName);
template <class TLookupClass, class TValidator = CAtlValidator>
class CValidateObject
{
public:
	// Exchange Routines

	// Call this function to retrieve a named value converted to your chosen data type.
	// Returns one of the following validation status codes:
	//      VALIDATION_S_OK             The named value was found and could be converted successfully
	//      VALIDATION_S_EMPTY          The name was present, but the value was empty
	//      VALIDATION_E_PARAMNOTFOUND  The named value was not found
	//      VALIDATION_E_INVALIDPARAM   The name was present, but the value could not be converted to the requested data type
	//      VALIDATION_E_FAIL           An unspecified error occurred
	// Pass a pointer to a validation context object if you want to add
	// failures to the collection managed by that object.
	template <class T>
	ATL_NOINLINE __checkReturn DWORD Exchange(
		__in LPCSTR szParam,
		__out T* pValue,
		__inout_opt CValidateContext *pContext = NULL) const throw()
	{
		DWORD dwRet = VALIDATION_E_PARAMNOTFOUND;
		if (pValue)
		{
			_ATLTRY
			{
				const TLookupClass *pT = static_cast<const TLookupClass*>(this);
				LPCSTR szValue = pT->Lookup(szParam);
				if (szValue)
				{
					if (*szValue=='\0')
						dwRet = VALIDATION_S_EMPTY; 
					else
					{
						dwRet = ConvertNumber(szValue, pValue);
					}
				}
			}
			_ATLCATCHALL()
			{
				return VALIDATION_E_FAIL;
			}
		}
		else
			dwRet = VALIDATION_E_FAIL; // invalid input

		if (pContext)
			pContext->AddResult(szParam, dwRet);
		return dwRet;
	}

	template<>
	ATL_NOINLINE __checkReturn DWORD Exchange(
		__in LPCSTR szParam,
		__out_opt CString* pstrValue,
		__in_opt CValidateContext *pContext) const throw()
	{
		_ATLTRY
		{
			LPCSTR pszValue = NULL;
			DWORD dwRet = VALIDATION_E_PARAMNOTFOUND;
			if (pstrValue)
			{
				dwRet = Exchange(szParam, &pszValue, pContext);
				if (VALIDATION_SUCCEEDED(dwRet) && pstrValue != NULL)
					*pstrValue = CA2T(pszValue);
			}
			else
			{
				dwRet = VALIDATION_E_FAIL; // invalid input
				if (pContext)
					pContext->AddResult(szParam, dwRet);
			}

			return dwRet;
		}
		_ATLCATCHALL()
		{
			return VALIDATION_E_FAIL;
		}
	}

	template<>
	ATL_NOINLINE __checkReturn DWORD Exchange(
		__in LPCSTR szParam,
		__deref_out_opt LPCSTR* ppszValue,
		__inout_opt CValidateContext *pContext) const throw()
	{
		DWORD dwRet = VALIDATION_E_PARAMNOTFOUND;
		if (ppszValue)
		{
			_ATLTRY
			{
				*ppszValue = NULL;
				const TLookupClass *pT = static_cast<const TLookupClass*>(this);
				LPCSTR szValue = pT->Lookup(szParam);
				if (szValue)
				{
					if (*szValue=='\0')
						dwRet = VALIDATION_S_EMPTY; 
					else
					{
						*ppszValue = szValue;
						dwRet = VALIDATION_S_OK;
					}
				}
			}
			_ATLCATCHALL()
			{
				return VALIDATION_E_FAIL;
			}
		}
		else
			dwRet = VALIDATION_E_FAIL; // invalid input

		if (pContext)
			pContext->AddResult(szParam, dwRet);
		return dwRet;
	}

	template<>
	ATL_NOINLINE __checkReturn DWORD Exchange(
		__in LPCSTR szParam,
		__out GUID* pValue,
		__inout_opt CValidateContext *pContext) const throw()
	{
		DWORD dwRet = VALIDATION_E_PARAMNOTFOUND;
		if (pValue)
		{
			_ATLTRY
			{
				const TLookupClass *pT = static_cast<const TLookupClass*>(this);
				LPCSTR szValue = pT->Lookup(szParam);
				if (szValue)
				{
					if (*szValue=='\0')
						dwRet = VALIDATION_S_EMPTY; 
					else
					{						
						if (S_OK != CLSIDFromString(CA2W(szValue), pValue))
						{
							dwRet = VALIDATION_E_INVALIDPARAM;
						}
						else
							dwRet = VALIDATION_S_OK;
					}
				}
			}
			_ATLCATCHALL()
			{
				return VALIDATION_E_FAIL;
			}
		}
		else
			dwRet = VALIDATION_E_FAIL; // invalid input

		if (pContext)
			pContext->AddResult(szParam, dwRet);
		return dwRet;
	}

	template<>
	ATL_NOINLINE __checkReturn DWORD Exchange(
		__in LPCSTR szParam,
		__out bool* pbValue,
		__inout_opt CValidateContext *pContext) const throw()
	{
		DWORD dwRet = VALIDATION_S_OK;
		if (pbValue)
		{
			_ATLTRY
			{
				const TLookupClass *pT = static_cast<const TLookupClass*>(this);
				LPCSTR szValue = pT->Lookup(szParam);
				*pbValue = false;
				if (szValue)
				{
					if (*szValue != '\0')
						*pbValue = true;
				}
			}
			_ATLCATCHALL()
			{
				return VALIDATION_E_FAIL;
			}
		}
		else
			dwRet = VALIDATION_E_FAIL; // invalid input

		if (pContext)
			pContext->AddResult(szParam, dwRet);

		return dwRet;
	}

	__checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out ULONGLONG *pnVal) const throw()
	{
		if (!szVal)
			return VALIDATION_E_FAIL;

		ATLASSERT(pnVal);
		if (!pnVal)
			return VALIDATION_E_FAIL;
		char *pEnd = NULL;
		ULONGLONG n = 0;
		errno_t errnoValue = AtlStrToNum(&n, szVal, &pEnd, 10);
		if (pEnd == szVal || errnoValue == ERANGE)
		{
			return VALIDATION_E_INVALIDPARAM;
		}
		*pnVal = n;
		return VALIDATION_S_OK;
	}

	__checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out LONGLONG *pnVal) const throw()
	{
		if (!szVal)
			return VALIDATION_E_FAIL;

		ATLASSERT(pnVal);
		if (!pnVal)
			return VALIDATION_E_FAIL;
		char *pEnd = NULL;
		LONGLONG n = 0;
		errno_t errnoValue = AtlStrToNum(&n, szVal, &pEnd, 10);
		if (pEnd == szVal || errnoValue == ERANGE)
		{
			return VALIDATION_E_INVALIDPARAM;
		}
		*pnVal = n;
		return VALIDATION_S_OK;
	}

	__checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out double *pdblVal) const throw()
	{
		if (!szVal)
			return VALIDATION_E_FAIL;

		ATLASSERT(pdblVal);
		if (!pdblVal)
			return VALIDATION_E_FAIL;
		char *pEnd = NULL;
		double d = 0.0;
		errno_t errnoValue = AtlStrToNum(&d, szVal, &pEnd);
		if (pEnd == szVal || errnoValue == ERANGE)
		{
			return VALIDATION_E_INVALIDPARAM;
		}
		*pdblVal = d;
		return VALIDATION_S_OK;
	}

	__checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out int *pnVal) const throw()
	{
		return ConvertNumber(szVal, (long*)pnVal);
	}

	__checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out unsigned int *pnVal) const throw()
	{
		return ConvertNumber(szVal, (unsigned long*)pnVal);
	}

	__checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out long *pnVal) const throw()
	{
		if (!szVal)
			return VALIDATION_E_FAIL;

		ATLASSERT(pnVal);
		if (!pnVal)
			return VALIDATION_E_FAIL;
		char *pEnd = NULL;
		long n = 0;
		errno_t errnoValue = AtlStrToNum(&n, szVal, &pEnd, 10);
		if (pEnd == szVal || errnoValue == ERANGE)
		{
			return VALIDATION_E_INVALIDPARAM;
		}
		*pnVal = n;
		return VALIDATION_S_OK;
	}

	__checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out unsigned long *pnVal) const throw()
	{
		if (!szVal)
			return VALIDATION_E_FAIL;

		ATLASSERT(pnVal);
		if (!pnVal)
			return VALIDATION_E_FAIL;
		char *pEnd = NULL;
		unsigned long n = 0;
		errno_t errnoValue = AtlStrToNum(&n, szVal, &pEnd, 10);
		if (pEnd == szVal || errnoValue == ERANGE)
		{
			return VALIDATION_E_INVALIDPARAM;
		}
		*pnVal = n;
		return VALIDATION_S_OK;
	}

	__checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out short *pnVal) const throw()
	{
		if (!szVal)
			return VALIDATION_E_FAIL;

		ATLASSERT(pnVal);
		if (!pnVal)
			return VALIDATION_E_FAIL;
		long nVal = 0;
		DWORD dwRet = ConvertNumber(szVal, &nVal);
		if (dwRet == VALIDATION_S_OK)
		{
			// clamp to the size of a short
			if(nVal <= SHRT_MAX &&
				nVal >= SHRT_MIN)
			{
				*pnVal = (short)nVal;
			}
			else
			{
				dwRet = VALIDATION_E_INVALIDPARAM;
			}
		}
		return dwRet;
	};

	__checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out unsigned short *pnVal) const throw()
	{
		if (!szVal)
			return VALIDATION_E_FAIL;

		ATLASSERT(pnVal);
		if (!pnVal)
			return VALIDATION_E_FAIL;
		unsigned long nVal = 0;
		DWORD dwRet = ConvertNumber(szVal, &nVal);
		if (dwRet == VALIDATION_S_OK)
		{
			// clamp to the size of a short
			if(nVal <= USHRT_MAX &&
			   nVal >= 0)
			{
				*pnVal = (unsigned short)nVal;
			}
			else
			{
				dwRet = VALIDATION_E_INVALIDPARAM;
			}
		}
		return dwRet;
	};

	// Call this function to retrieve a named value converted to your chosen data type
	// and validated against a minimum and maximum value or length supplied by you.
	//
	// Returns one of the following validation status codes:
	//      VALIDATION_S_OK             The named value was found and could be converted successfully
	//      VALIDATION_S_EMPTY          The name was present, but the value was empty
	//      VALIDATION_E_PARAMNOTFOUND  The named value was not found
	//      VALIDATION_E_INVALIDPARAM   The name was present, but the value could not be converted to the requested data type
	//      VALIDATION_E_LENGTHMIN      The name was present and could be converted to the requested data type, but the value was too small
	//      VALIDATION_E_LENGTHMAX      The name was present and could be converted to the requested data type, but the value was too large
	//      VALIDATION_E_FAIL           An unspecified error occurred
	//
	// Validate can be used to convert and validate name-value pairs
	// such as those associated with HTTP requests (query string, form fields, or cookie values).  
	// The numeric specializations validate the minimum and maximum value.
	// The string specializations validate the minimum and maximum length.
	//
	// Pass a pointer to a validation context object if you want to add
	// failures to the collection managed by that object.
	//
	// Note that you can validate the value of a parameter without
	// storing its value by passing NULL for the second parameter. However
	// if you pass NULL for the second parameter, make sure you cast the NULL to a 
	// type so that the compiler will call the correct specialization of Validate.
	template <class T, class TCompType>
	ATL_NOINLINE __checkReturn DWORD Validate(
		__in LPCSTR Param,
		__out_opt T *pValue,
		__in TCompType nMinValue,
		__in TCompType nMaxValue,
		__inout_opt CValidateContext *pContext = NULL) const throw()
	{
		T value;
		DWORD dwRet = Exchange(Param, &value, pContext);
		if ( dwRet == VALIDATION_S_OK )
		{
			if (pValue)
				*pValue = value;
			dwRet = TValidator::Validate(value, nMinValue, nMaxValue);
			if (pContext && dwRet != VALIDATION_S_OK)
				pContext->AddResult(Param, dwRet);
		}
		else if (dwRet == VALIDATION_S_EMPTY &&
				 !IsNullByType(nMinValue))
		{
				 dwRet = VALIDATION_E_LENGTHMIN;
				 if (pContext)
				 {
					pContext->SetResultAt(Param, VALIDATION_E_LENGTHMIN);
				 }
		}

		return dwRet;
	}

	// Specialization for strings. Comparison is for number of characters.
	template<>
	ATL_NOINLINE __checkReturn DWORD Validate(
		__in LPCSTR Param,
		__deref_opt_out LPCSTR* ppszValue,
		__in int nMinChars,
		__in int nMaxChars,
		__inout_opt CValidateContext *pContext) const throw()
	{
		LPCSTR pszValue = NULL;
		DWORD dwRet = Exchange(Param, &pszValue, pContext);
		if (dwRet == VALIDATION_S_OK )
		{
			if (ppszValue)
				*ppszValue = pszValue;
			dwRet = TValidator::Validate(pszValue, nMinChars, nMaxChars);
			if (pContext && dwRet != VALIDATION_S_OK)
				pContext->AddResult(Param, dwRet);
		}
		else if (dwRet == VALIDATION_S_EMPTY &&
				 nMinChars > 0)
		{
				 dwRet = VALIDATION_E_LENGTHMIN;
				 if (pContext)
				 {
					pContext->SetResultAt(Param, VALIDATION_E_LENGTHMIN);
				 }
		}


		return dwRet;
	}

	// Specialization for CString so caller doesn't have to cast CString
	template<>
	ATL_NOINLINE __checkReturn DWORD Validate(
		__in LPCSTR Param,
		__out_opt CString* pstrValue,
		__in int nMinChars,
		__in int nMaxChars,
		__inout_opt CValidateContext *pContext) const throw()
	{
		_ATLTRY
		{
			LPCSTR szValue;
			DWORD dwRet = Validate(Param, &szValue, nMinChars, nMaxChars, pContext);
			if (pstrValue && dwRet == VALIDATION_S_OK )
				*pstrValue = szValue;
			return dwRet;
		}
		_ATLCATCHALL()
		{
			return VALIDATION_E_FAIL;
		}
	}

	// Specialization for doubles, uses a different comparison.
	template<>
	ATL_NOINLINE __checkReturn DWORD Validate(
		__in LPCSTR Param,
		__out_opt double* pdblValue,
		__in double dblMinValue,
		__in double dblMaxValue,
		__inout_opt CValidateContext *pContext) const throw()
	{
		double dblValue;
		DWORD dwRet = Exchange(Param, &dblValue, pContext);
		if (dwRet == VALIDATION_S_OK)
		{
			if (pdblValue)
				*pdblValue = dblValue;
			dwRet = TValidator::Validate(dblValue, dblMinValue, dblMaxValue);
			if (pContext && dwRet != VALIDATION_S_OK)
				pContext->AddResult(Param, dwRet);
		}
		else if (dwRet == VALIDATION_S_EMPTY &&
				 (dblMinValue < -ATL_EPSILON ||
				 dblMinValue > ATL_EPSILON))
		{
			dwRet = VALIDATION_E_LENGTHMIN;
			if (pContext)
			{
				pContext->SetResultAt(Param, VALIDATION_E_LENGTHMIN);
			}
		}
		return dwRet;
	}
};

// Cookies provide a way for a server to store a small amount of data on a client
// and have that data returned to it on each request the client makes.
// Use this class to represent a cookie to be sent from the server to a client
// or to represent a cookie that has been returned by a client to the originating server.
//
// At the HTTP level, a cookie is an application-defined name-value pair
// plus some standard attribute-value pairs that describe the way in which the user agent (web browser)
// should interact with the cookie. The HTTP format of a cookie is described in RFC 2109.
//
// The CCookie class provides methods to set and get the application-defined name and value
// as well as methods for the standard attributes. In addition, CCookie provides an abstraction
// on top of the application-defined value that allows it to be treated as a collection of name-value
// pairs if that model makes sense to you. Cookies with a single value are known as single-valued cookies.
// Cookies whose value consists of name-value pairs are known as multi-valued cookies or dictionary cookies.
//
// You can set the name of a cookie by calling SetName or using the appropriate constructor.
// The name of a cookie can be 0 or more characters.
//
// You can set the value of a cookie by calling SetValue or using the appropriate constructor.
// If the cookie has a value set, it is a single-valued cookie and attempts to add a name-value pair will fail.
// You can remove the value of a cookie by calling SetValue(NULL).
//
// You can add a name-value pair to a cookie by calling AddValue.
// If the cookie has any name-value pairs, it is a multi-valued cookie and attempts to set the primary value will fail.
// You can remove all the name-value pairs of a cookie by calling RemoveAllValues.
//
// Class CCookie follows the same rules for creating cookies as ASP does.
class CCookie :
	public CValidateObject<CCookie>
{
	typedef CAtlMap<CStringA, CStringA, CStringElementTraits<CStringA>,
		CStringElementTraits<CStringA> > mapType;

	const static DWORD ATLS_MAX_HTTP_DATE = 64;

public:
	// Constructs a named cookie.
	CCookie(__in LPCSTR szName) throw(...)
	{
		ATLENSURE(SetName(szName));
	}

	// Constructs a single-valued cookie.
	CCookie(__in LPCSTR szName, __in_opt LPCSTR szValue) throw(...)
	{
		ATLENSURE(SetName(szName));
		ATLENSURE(SetValue(szValue));
	}

	CCookie(__in const CCookie& thatCookie) throw(...)
	{
		Copy(thatCookie);
	}

	CCookie& operator=(__in const CCookie& thatCookie) throw(...)
	{
		if(this!=&thatCookie)
		{
			return Copy(thatCookie);
		}
		return *this;
	}

	CCookie() throw()
	{

	}

	__checkReturn BOOL IsEmpty() const throw()
	{
		return m_strName.IsEmpty();
	}

	// Call this function to set the name of this cookie.
	// Returns TRUE on success, FALSE on failure.
	// The name of a cookie cannot contain whitespace, semicolons or commas.
	// The name should not begin with a dollar sign ($) since such names are reserved for future use.
	__checkReturn BOOL SetName(__in LPCSTR szName) throw()
	{
		_ATLTRY
		{
			if (szName && *szName)
			{
				m_strName = szName;
				return TRUE;
			}
		}
		_ATLCATCHALL()
		{
		}
		return FALSE;
	}

	// Call this function to retrieve the name of this cookie.
	// Returns TRUE on success, FALSE on failure.
	__checkReturn BOOL GetName(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) const throw()
	{
		return CopyCString(m_strName, szBuff, pdwSize);
	}

	// Call this function to retrieve the name of this cookie.
	// Returns TRUE on success, FALSE on failure.
	__checkReturn BOOL GetName(__out CStringA &strName) const throw()
	{
		_ATLTRY
		{
			strName = m_strName;
			return TRUE;
		}
		_ATLCATCHALL()
		{
		}
		return FALSE;
	}

	// Call this function to set the value of this cookie.
	// Returns TRUE on success, FALSE on failure.
	// Will fail if the cookie is multi-valued.
	// Pass NULL to remove the cookie's value.
	__checkReturn BOOL SetValue(__in_opt LPCSTR szValue) throw()
	{
		_ATLTRY
		{
			if (m_Values.GetCount())
				return FALSE; //already dictionary values in the cookie

			if (!szValue)
				m_strValue.Empty();
			else 
				m_strValue = szValue;

			return TRUE;
		}
		_ATLCATCHALL()
		{
		}
		return FALSE;
	}

	// Call this function to retrieve the value of this cookie.
	// Returns TRUE on success, FALSE on failure.
	// Returns TRUE if there is no value or the value is of zero length.
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetValue(__out_ecount(*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) const throw()
	{
		return CopyCString(m_strValue, szBuff, pdwSize);
	}

	// Call this function to retrieve the value of this cookie.
	// Returns TRUE on success, FALSE on failure.
	__checkReturn BOOL GetValue(__out CStringA &strValue) const throw()
	{
		_ATLTRY
		{
			strValue = m_strValue;
			return TRUE;
		}
		_ATLCATCHALL()
		{
		}
		return FALSE;
	}

	// Call this function to add a name-value pair to the cookie.
	// Returns TRUE on success, FALSE on failure.
	// Will fail if the cookie is single-valued.
	// If the named value is already present in the cookie, calling this function
	// will modify the current value, otherwise a new name-value pair is added to the cookie.
	// Call RemoveValue or RemoveAllValues to remove the name-value pairs
	// added by this function.
	__checkReturn BOOL AddValue(__in LPCSTR szName, __in_opt LPCSTR szValue) throw()
	{
		if (m_strValue.GetLength())
			return FALSE;
		_ATLTRY
		{
			return m_Values.SetAt(szName, szValue) != NULL;
		}
		_ATLCATCHALL()
		{
		}
		return FALSE;
	}

	// Call this function to modify a name-value pair associated with the cookie.
	// Returns TRUE on success, FALSE on failure.
	// Will fail if the cookie is single-valued.
	// This function just calls AddValue so the name-value pair will be added if not already present.
	// Use this function instead of AddValue to document the intentions of your call. 
	__checkReturn BOOL ModifyValue(__in LPCSTR szName, __in LPCSTR szValue) throw()
	{
		return AddValue(szName, szValue);
	}

	// Call this function to remove a name-value pair from the collection managed by this cookie.
	// Returns TRUE on success, FALSE on failure.
	__checkReturn BOOL RemoveValue(__in LPCSTR szName) throw()
	{
		return m_Values.RemoveKey(szName);
	}

	// Call this function to remove all the name-value pairs from the collection managed by this cookie.
	void RemoveAllValues() throw()
	{
		m_Values.RemoveAll();
	}

	// Call this function to add an attribute-value pair to the collection of attributes for this cookie.
	// Returns TRUE on success, FALSE on failure.
	// This function is equivalent to calling ModifyAttribute.
	// Both functions will add the attribute if not already present or
	// change its value if it has already been applied to the cookie.
	__checkReturn BOOL AddAttribute(__in LPCSTR szName, __in LPCSTR szValue) throw()
	{
		if (!szName || !*szName || !szValue)
			return FALSE;

		_ATLTRY
		{
			return (m_Attributes.SetAt(szName, szValue) != NULL);
		}
		_ATLCATCHALL()
		{
		}
		return FALSE;

	}

	// Call this function to modify an attribute-value pair associated with the cookie.
	// Returns TRUE on success, FALSE on failure.
	// This function is equivalent to calling AddAttribute.
	// Both functions will add the attribute if not already present or
	// change its value if it has already been applied to the cookie.
	__checkReturn BOOL ModifyAttribute(__in LPCSTR szName, __in LPCSTR szValue) throw()
	{
		return AddAttribute(szName, szValue);
	}

	// Call this function to remove an attribute-value pair from the collection of attributes managed by this cookie.
	// Returns TRUE on success, FALSE on failure.
	__checkReturn BOOL RemoveAttribute(__in LPCSTR szName) throw()
	{
		return m_Attributes.RemoveKey(szName);
	}

	// Call this function to remove all the attribute-value pairs from the collection of attributes managed by this cookie.
	void RemoveAllAttributes() throw()
	{
		m_Attributes.RemoveAll();
	}


	// Call this function to set the Comment attribute of the cookie.
	// Returns TRUE on success, FALSE on failure.
	// The Comment attribute allows a web server to document its
	// intended use of a cookie. This information may be displayed
	// by supporting browsers so that the user of the web site can
	// decide whether to initiate or continue a session with this cookie.
	// This attribute is optional.
	// Version 1 attribute.
	__checkReturn BOOL SetComment(__in LPCSTR szComment) throw()
	{
		BOOL bRet = SetVersion(1);
		if (bRet)
			bRet = AddAttribute("comment", szComment);
		return bRet;
	}

	// Call this function to set the CommentUrl attribute of the cookie.
	// Returns TRUE on success, FALSE on failure.
	// The CommentUrl attribute allows a web server to document its intended 
	// use of a cookie via a URL that the user of the web site can navigate to.
	// The URL specified here should not send further cookies to the client to
	// avoid frustrating the user.
	// This attribute is optional.
	// Version 1 attribute.
	__checkReturn BOOL SetCommentUrl(__in LPCSTR szUrl) throw()
	{
		BOOL bRet = SetVersion(1);
		if (bRet)
			bRet = AddAttribute("commenturl", szUrl);
		return bRet;
	}

	// Call this function to add or remove the Discard attribute of the cookie.
	// Returns TRUE on success, FALSE on failure.
	// The Discard attribute does not have a value.
	// Call SetDiscard(TRUE) to add the Discard attribute
	// or SetDiscard(FALSE) to remove the Discard attribute.
	// Setting the Discard attribute tells a web browser that it should
	// discard this cookie when the browser exits regardless of the 
	// value of the Max-Age attribute.
	// This attribute is optional.
	// When omitted, the default behavior is that the Max-Age attribute
	// controls the lifetime of the cookie.
	// Version 1 attribute.
	__checkReturn BOOL SetDiscard(__in BOOL bDiscard) throw()
	{
		BOOL bRet = FALSE;
		LPCSTR szKey = "Discard";
		bRet = SetVersion(1);
		if (bRet)
		{
			if (bDiscard == 0)
			{
				bRet = m_Attributes.RemoveKey(szKey);
			}
			else
			{
				_ATLTRY
				{
					bRet = m_Attributes.SetAt(szKey, " ") != 0;
				}
				_ATLCATCHALL()
				{
					bRet = FALSE;
				}
			}
		}
		return bRet;
	}

	// Call this function to set the Domain attribute of the cookie.
	// Returns TRUE on success, FALSE on failure.
	// The Domain attribute is used to indicate the domain to which the current 
	// cookie applies. Browsers should only send cookies back to the relevant domains.
	// This attribute is optional.
	// When omitted, the default behavior is for
	// browsers to use the full domain of the server originating the cookie. You can
	// set this attribute value explicitly if you want to share cookies among several servers.
	// Version 0 & Version 1 attribute.
	__checkReturn BOOL SetDomain(__in LPCSTR szDomain) throw()
	{
		BOOL bRet = SetVersion(1);
		if (bRet)
			bRet = AddAttribute("domain", szDomain);
		return bRet;
	}

	// Call this function to set the Max-Age attribute of the cookie.
	// Returns TRUE on success, FALSE on failure.
	// The value of the Max-Age attribute is a lifetime in seconds for the cookie.
	// When the time has expired, compliant browsers will discard this cookie
	// (if they haven't already done so as a result of the Discard attribute).
	// If Max-Age is set to zero, the browser discards the cookie immediately.
	// This attribute is the Version 1 replacement for the Expires attribute.
	// This attribute is optional.
	// When omitted, the default behavior is for browsers to discard cookies
	// when the user closes the browser.
	// Version 1 attribute.
	__checkReturn BOOL SetMaxAge(__in UINT nMaxAge) throw()
	{
		BOOL bRet = FALSE;
		bRet = SetVersion(1);
		if (bRet)
		{
			CHAR buff[20];
			if (0 == _itoa_s(nMaxAge, buff, _countof(buff), 10))
			{
				bRet = AddAttribute("max-age", buff);
			}
		}
		return bRet;
	}

	// Call this function to set the Path attribute of the cookie.
	// Returns TRUE on success, FALSE on failure.
	// The Path attribute specifies the subset of URLs to which this cookie applies.
	// Only URLs that contain that path are allowed to read or modify the cookie. 
	// This attribute is optional.
	// When omitted the default behavior is for browsers to treat the path of a cookie
	// as the path of the request URL that generated the Set-Cookie response, up to,
	// but not including, the right-most /.
	// Version 0 & Version 1 attribute.
	__checkReturn BOOL SetPath(__in LPCSTR szPath) throw()
	{   
		BOOL bRet = SetVersion(1);
		if (bRet)
			bRet = AddAttribute("path", szPath);
		return bRet;
	}

	// Call this function to set the Port attribute of the cookie.
	// Returns TRUE on success, FALSE on failure.
	// The Port attribute specifies the port to which this cookie applies.
	// Only URLs accessed via that port are allowed to read or modify the cookie. 
	// This attribute is optional.
	// When omitted the default behavior is for browsers to return the cookie via any port.
	// Version 1 attribute.
	__checkReturn BOOL SetPort(__in LPCSTR szPort) throw()
	{
		BOOL bRet = SetVersion(1);
		if (bRet)
			bRet = AddAttribute("port", szPort);
		return bRet;
	}

	// Call this function to add or remove the Secure attribute of the cookie.
	// Returns TRUE on success, FALSE on failure.
	// The Secure attribute does not have a value.
	// Call SetSecure(TRUE) to add the Secure attribute
	// or SetSecure(FALSE) to remove the Secure attribute.
	// Setting the Secure attribute tells a browser that it should
	// transmit the cookie to the web server only via secure means such as HTTPS.
	// This attribute is optional.
	// When omitted, the default behavior is that the cookie
	// will be sent via unsecured protocols.
	// Version 0 & Version 1 attribute.
	__checkReturn BOOL SetSecure(__in BOOL bSecure) throw()
	{
		BOOL bRet = FALSE;
		LPCSTR szKey = "secure";
		bRet = SetVersion(1);
		if (bRet)
		{
			if (bSecure == 0)
			{
				bRet = m_Attributes.RemoveKey(szKey);
			}
			else
			{
				_ATLTRY
				{
					bRet = m_Attributes.SetAt(szKey, " ") != 0;
				}
				_ATLCATCHALL()
				{
					bRet = FALSE;
				}
			}
		}
		return bRet;
	}

	// Call this function to set the Version attribute of the cookie.
	// Returns TRUE on success, FALSE on failure.
	// This attribute is required for Version 1 cookies by RFC 2109 and must have a value of 1.
	// However, you do not need to call SetVersion explicitly from your own code unless you need to
	// force RFC 2109 compliance. CCookie will automatically set this attribute whenever
	// you use a Version 1 attribute in your cookie.
	// Version 1 attribute.
	__checkReturn BOOL SetVersion(__in UINT nVersion) throw()
	{
		BOOL bRet = FALSE;
		CHAR buff[20];
		if (0 == _itoa_s(nVersion, buff, _countof(buff), 10))
		{
			bRet = AddAttribute("version", buff);
		}
		return bRet;
	}

	// Call this function to set the Expires attribute of the cookie.
	// Returns TRUE on success, FALSE on failure.
	// The Expires attribute specifies an absolute date and time at which this cookie
	// should be discarded by web browsers. Pass a SYSTEMTIME holding a Greenwich Mean Time (GMT)
	// value or a string in the following format:
	//      Wdy, DD-Mon-YY HH:MM:SS GMT
	// This attribute is optional.
	// When omitted, the default behavior is for browsers to discard cookies
	// when the user closes the browser.
	// This attribute has been superceded in Version 1 by the Max-Age attribute,
	// but you should continue to use this attribute for Version 0 clients.
	// Version 0 attribute.
	__checkReturn BOOL SetExpires(__in LPCSTR szExpires) throw()
	{
		return AddAttribute("expires", szExpires);
	}

	__checkReturn BOOL SetExpires(__in const SYSTEMTIME &st) throw()
	{
		_ATLTRY
		{
			CFixedStringT<CStringA, ATLS_MAX_HTTP_DATE> strTime;
			SystemTimeToHttpDate(st, strTime);
			return SetExpires(strTime);
		}
		_ATLCATCHALL()
		{
		}
		return FALSE;
	}

	// Call this function to look up the value of a name-value pair applied to this cookie.
	// Returns the requested value if present or NULL if the name was not found.
	__checkReturn LPCSTR Lookup(__in_opt LPCSTR szName=NULL) const throw()
	{
		if (IsEmpty())
			return NULL;

		if (m_strValue.GetLength())
		{
			ATLASSERT(szName == NULL);
			return m_strValue;
		}

		if (m_Values.GetCount())
		{
			ATLENSURE_RETURN_VAL(szName, NULL);
			const mapType::CPair *pPair = NULL;
			ATLTRY(pPair = m_Values.Lookup(szName));
			if (pPair)
				return (LPCSTR)pPair->m_value;
		}

		return NULL;
	}

	// Call this function to clear the cookie of all content
	// including name, value, name-value pairs, and attributes.
	void Empty() throw()
	{
		m_strName.Empty();
		m_strValue.Empty();
		m_Attributes.RemoveAll();
		m_Values.RemoveAll();
	}

	// Call this function to create a CCookie from a buffer.
	// The passed in buffer contains a cookie header retrieved
	// from the HTTP_COOKIE request variable
	bool ParseValue(__in const char *pstart)
	{
		ATLASSERT(pstart);
		if (!pstart || *pstart == '\0')
			return false;

		// could be just a value or could be an array of name=value pairs
		LPCSTR pEnd = pstart;
		LPCSTR pStart = pstart;
		CStringA name, value;

		while (*pEnd != '\0')
		{
			while (*pEnd && *pEnd != '=' && *pEnd != '&')
				pEnd++;

			if (*pEnd == '\0' || *pEnd == '&')
			{
				if (pEnd > pStart)
					CopyToCString(value, pStart, pEnd);
				SetValue(value);
				if (*pEnd == '&')
				{
					pEnd++;
					pStart = pEnd;
					continue;
				}
				return true; // we're done;
			}
			else if (*pEnd == '=' )
			{
				// we have name=value
				if (pEnd > pStart)
				{
					CopyToCString(name, pStart, pEnd);
				}
				else
				{
					pEnd++;
					pStart = pEnd;
					break;
				}

				// skip '=' and go for value
				pEnd++;
				pStart = pEnd;
				while (*pEnd && *pEnd != '&' && *pEnd != '=')
					pEnd++;
				if (pEnd > pStart)
					CopyToCString(value, pStart, pEnd);

				ATLENSURE(AddValue(name, value));

				if (*pEnd != '\0')
					pEnd++;
					pStart = pEnd;

			}
		}
		
		return true;
	}

	// Call this function to render this cookie
	// into a buffer. Returns TRUE on success, FALSE on failure.
	// On entry, pdwLen should point to a DWORD that indicates 
	// the size of the buffer in bytes. On exit, the DWORD contains
	// the number of bytes transferred or available to be transferred
	// into the buffer (including the nul-terminating byte). On
	// success, the buffer will contain the correct HTTP 
	// representation of the current cookie suitable for sending to 
	// a client as the body of a Set-Cookie header.
	__success(return==true) ATL_NOINLINE __checkReturn BOOL Render(__out_ecount_part_opt(*pdwLen,*pdwLen) LPSTR szCookieBuffer, __inout DWORD *pdwLen) const throw()
	{
		if (!pdwLen)
			return FALSE;
		CStringA strCookie;
		CStringA name, value;
		DWORD dwLenBuff = *pdwLen;
		*pdwLen = 0;

		// A cookie must have a name!
		if (!m_strName.GetLength())
		{
			*pdwLen = 0;
			return FALSE;
		}
		_ATLTRY
		{
			strCookie = m_strName;
			int nValSize = (int) m_Values.GetCount();
			if (nValSize)
			{
				strCookie += '=';
				POSITION pos = m_Values.GetStartPosition();
				for (int i=0; pos; i++)
				{
					m_Values.GetNextAssoc(pos, name, value);
					strCookie += name;
					if (value.GetLength())
					{
						strCookie += '=';
						strCookie += value;
					}
					if (i <= nValSize-2)
						strCookie += '&';
				}
			}
			else
			{
				strCookie += '=';
				if (m_strValue.GetLength())
					strCookie += m_strValue;
			}

			CStringA strAttributes;
			if (!RenderAttributes(strAttributes))
				return FALSE;
			if (strAttributes.GetLength() > 0)
			{
				strCookie += "; ";
				strCookie += strAttributes;
			}

			DWORD dwLenCookie = strCookie.GetLength() + 1;
			*pdwLen = dwLenCookie;
			if (!szCookieBuffer)
				return TRUE; // caller just wanted the length

			// see if buffer is big enough
			if (dwLenCookie > dwLenBuff)
				return FALSE; //buffer wasn't big enough

			// copy the buffer
			Checked::strcpy_s(szCookieBuffer, *pdwLen, strCookie);
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
		return TRUE;
	}

	POSITION GetFirstAttributePos() const throw()
	{
		return m_Attributes.GetStartPosition();
	}

	const CStringA& GetNextAttributeName(__inout POSITION& pos) const throw()
	{
		return m_Attributes.GetNextKey(pos);
	}

	const CStringA& GetAttributeValueAt(__in POSITION pos) const throw()
	{
		return m_Attributes.GetValueAt(pos);
	}

	BOOL GetNextAttrAssoc(__inout POSITION& pos, __out CStringA& key,
		__out CStringA& val) const throw()
	{
		_ATLTRY
		{
			m_Attributes.GetNextAssoc(pos, key, val);
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
		return TRUE;
	}

	POSITION GetFirstValuePos() const throw()
	{
		return m_Values.GetStartPosition();
	}

	const CStringA& GetNextValueName(__inout POSITION& pos) const throw()
	{
		return m_Values.GetNextKey(pos);
	}

	const CStringA& GetValueAt(__in POSITION pos) const throw()
	{
		return m_Values.GetValueAt(pos);
	}

	BOOL GetNextValueAssoc(__inout POSITION& pos, __out CStringA& key,
		__out CStringA& val) const throw()
	{
		_ATLTRY
		{
			m_Values.GetNextAssoc(pos, key, val);
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
		return TRUE;
	}

protected:
// Implementation
	BOOL RenderAttributes(__out CStringA& strAttributes) const throw()
	{
		_ATLTRY
		{
			strAttributes.Empty();

			POSITION pos = m_Attributes.GetStartPosition();
			CStringA key, val;
			for (int i=0; pos; i++)
			{
				if (i)
					strAttributes += ";";
				m_Attributes.GetNextAssoc(pos, key, val);
				strAttributes += key;
				strAttributes += '=';
				strAttributes += val;
			}
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
		return TRUE;
	}
private:
	CCookie& Copy(__in const CCookie& thatCookie) throw(...)
	{
		m_strName = thatCookie.m_strName;
		m_strValue = thatCookie.m_strValue;
		POSITION pos = NULL;
		CStringA strName, strValue;
		if (!thatCookie.m_Attributes.IsEmpty())
		{
			pos = thatCookie.m_Attributes.GetStartPosition();
			while (pos)
			{
				thatCookie.m_Attributes.GetNextAssoc(pos, strName, strValue);
				m_Attributes.SetAt(strName, strValue);
			}
		}
		if (!thatCookie.m_Values.IsEmpty())
		{
			strName.Empty();
			strValue.Empty();
			pos = thatCookie.m_Values.GetStartPosition();
			while (pos)
			{
				thatCookie.m_Values.GetNextAssoc(pos, strName, strValue);
				m_Values.SetAt(strName, strValue);
			}
		}
		return *this;
	}

	// Call this function to copy a substring to a CString reference and ensure nul-termination.
	void CopyToCString(__out CStringA& string, __in_ecount(pEnd-pStart) LPCSTR pStart, __in LPCSTR pEnd) throw( ... )
	{
		ATLENSURE( pStart != NULL );
		ATLENSURE( pEnd != NULL );

		string.SetString(pStart, (int)(pEnd-pStart));
		string.Trim();
	}


public:
	// These are implementation only, use at your own risk!
	// Map of attribute-value pairs applied to this cookie.
	mapType m_Attributes;

	// Map of name-value pairs applied to this cookie.
	mapType m_Values;

	// The name of this cookie.
	CStringA m_strName;

	// The value of this cookie.
	CStringA m_strValue;

};  // class CCookie

class CSessionCookie : public CCookie
{
public:
	CSessionCookie() throw(...)
	{
		if (!SetName(SESSION_COOKIE_NAME) ||
			!SetPath("/"))
			AtlThrow(E_OUTOFMEMORY);
	}

	CSessionCookie(LPCSTR szSessionID) throw(...)
	{
		if (!SetName(SESSION_COOKIE_NAME) ||
			!SetPath("/") ||
			!SetSessionID(szSessionID) )
			AtlThrow(E_OUTOFMEMORY);
	}

	BOOL SetSessionID(LPCSTR szSessionID) throw()
	{
		ATLASSERT(szSessionID && szSessionID[0]);
		return SetValue(szSessionID);
	}
}; // class CSessionCookie

template<>
class CElementTraits< CCookie > :
	public CElementTraitsBase< CCookie >
{
public:
	typedef const CCookie& INARGTYPE;
	typedef CCookie& OUTARGTYPE;

	static ULONG Hash( __in INARGTYPE cookie )
	{
		return CStringElementTraits<CStringA>::Hash( cookie.m_strName );
	}

	static bool CompareElements( __in INARGTYPE cookie1, __in INARGTYPE cookie2 )
	{
		return( cookie1.m_strName == cookie2.m_strName );
	}

	static int CompareElementsOrdered( __in INARGTYPE cookie1, __in INARGTYPE cookie2 )
	{
		return( cookie1.m_strName.Compare( cookie2.m_strName ) );
	}
};


///////////////////////////////////////////////////////////////////////////////
// Request and response classes and support functions


// This class is a wrapper for CAtlMap that allows maps to be chained.
// It simply adds a bool that tells whether or not a map shares values
template <typename K, typename V, typename KTraits=CElementTraits<K>, typename VTraits=CElementTraits<V> >
class CHttpMap
{
private:

#ifdef ATL_HTTP_PARAM_MULTIMAP
	typedef CRBMultiMap<K, V, KTraits, VTraits> MAPTYPE;
#else
	typedef CAtlMap<K, V, KTraits, VTraits> MAPTYPE;
#endif // ATL_HTTP_PARAM_MULTIMAP

public:

	typedef typename KTraits::INARGTYPE KINARGTYPE;
	typedef typename KTraits::OUTARGTYPE KOUTARGTYPE;
	typedef typename VTraits::INARGTYPE VINARGTYPE;
	typedef typename VTraits::OUTARGTYPE VOUTARGTYPE;

	typedef typename MAPTYPE::CPair CPair;

private:

	bool m_bShared;

	MAPTYPE m_map;

public:

	CHttpMap() throw()
		: m_bShared(false)
	{
	}

	virtual ~CHttpMap()
	{
	}

	inline bool IsShared() const throw()
	{
		return m_bShared;
	}

	inline void SetShared(__in bool bShared) throw()
	{
		m_bShared = bShared;
	}

	//
	// exposed lookup and iteration functionality
	//

	inline size_t GetCount() const throw()
	{
		return m_map.GetCount();
	}

	inline bool IsEmpty() const throw()
	{
		return m_map.IsEmpty();
	}

	inline POSITION GetStartPosition() const throw()
	{
#ifdef ATL_HTTP_PARAM_MULTIMAP
		return m_map.GetHeadPosition();
#else
		return m_map.GetStartPosition();
#endif // ATL_HTTP_PARAM_MULTIMAP
	}

	// Lookup wrappers
	bool Lookup( __in KINARGTYPE key, __out VOUTARGTYPE value ) const throw()
	{
		_ATLTRY
		{
#ifdef ATL_HTTP_PARAM_MULTIMAP
			CPair *p = Lookup(key);
			if (p != NULL)
			{
				value = p->m_value;
				return true;
			}
			return false;
#else
			return m_map.Lookup(key, value);
#endif // ATL_HTTP_PARAM_MULTIMAP
		}
		_ATLCATCHALL()
		{
			return false;
		}
	}

	const CPair* Lookup( __in KINARGTYPE key ) const throw()
	{
#ifdef ATL_HTTP_PARAM_MULTIMAP
		POSITION pos = m_map.FindFirstWithKey(key);
		if (pos != NULL)
		{
			return m_map.GetAt(pos);
		}
		return NULL;
#else
		return m_map.Lookup(key);
#endif // ATL_HTTP_PARAM_MULTIMAP
	}

	CPair* Lookup( __in KINARGTYPE key ) throw()
	{
#ifdef ATL_HTTP_PARAM_MULTIMAP
		POSITION pos = m_map.FindFirstWithKey(key);
		if (pos != NULL)
		{
			return m_map.GetAt(pos);
		}
		return NULL;
#else
		return m_map.Lookup(key);
#endif // ATL_HTTP_PARAM_MULTIMAP
	}

	// iteration wrappers
	void GetNextAssoc( __inout POSITION& pos, __out KOUTARGTYPE key, __out VOUTARGTYPE value ) const throw(...)
	{
		m_map.GetNextAssoc(pos, key, value);
	}

	const CPair* GetNext( __inout POSITION& pos ) const throw()
	{
		return m_map.GetNext(pos);
	}

	CPair* GetNext( __inout POSITION& pos ) throw()
	{
		return m_map.GetNext(pos);
	}

	const K& GetNextKey( __inout POSITION& pos ) const throw()
	{
		return m_map.GetNextKey(pos);
	}

	const V& GetNextValue( __inout POSITION& pos ) const throw()
	{
		return m_map.GetNextValue(pos);
	}

	V& GetNextValue( __inout POSITION& pos ) throw()
	{
		return m_map.GetNextValue(pos);
	}

	void GetAt( __in POSITION pos, __out KOUTARGTYPE key, __out VOUTARGTYPE value ) const throw(...)
	{
		return m_map.GetAt(pos, key, value);
	}

	CPair* GetAt( __in POSITION pos ) throw()
	{
		return m_map.GetAt(pos);
	}

	const CPair* GetAt( __in POSITION pos ) const throw()
	{
		return m_map.GetAt(pos);
	}

	const K& GetKeyAt( __in POSITION pos ) const throw()
	{
		return m_map.GetKeyAt(pos);
	}

	const V& GetValueAt( __in POSITION pos ) const throw()
	{
		return m_map.GetValueAt(pos);
	}

	V& GetValueAt( __in POSITION pos ) throw()
	{
		return m_map.GetValueAt(pos);
	}

	// modification wrappers
	POSITION SetAt( __in KINARGTYPE key, __in_opt VINARGTYPE value ) throw(...)
	{
#ifdef ATL_HTTP_PARAM_MULTIMAP
		return m_map.Insert(key, value);
#else
		return m_map.SetAt(key, value);
#endif // ATL_HTTP_PARAM_MULTIMAP
	}

	bool RemoveKey( __in KINARGTYPE key ) throw()
	{
#ifdef ATL_HTTP_PARAM_MULTIMAP
		return (m_map.RemoveKey(key) != 0);
#else
		return m_map.RemoveKey(key);
#endif // ATL_HTTP_PARAM_MULTIMAP
	}

	virtual void RemoveAll()
	{
		m_map.RemoveAll();
	}
};

// This class is a wrapper for CHttpMap that assumes it's values are pointers that
// should be deleted on RemoveAll
template <typename K, typename V, typename KTraits=CElementTraits<K>, typename VTraits=CElementTraits<V> >
class CHttpPtrMap : public CHttpMap<K, V, KTraits, VTraits>
{
public:
	typedef CHttpMap<K, V, KTraits, VTraits> Base;

	void RemoveAll()
	{
		if (!IsShared())
		{
			POSITION pos = GetStartPosition();
			while (pos)
			{
				GetNextValue(pos)->Free();
			}
		}
		Base::RemoveAll();
	}

	~CHttpPtrMap()
	{
		RemoveAll();
	}
};

// This class represents a collection of request parameters - the name-value pairs
// found, for example, in a query string or in the data provided when a form is submitted to the server.
// Call Parse to build the collection from a string of URL-encoded data.
// Use the standard collection methods of the CHttpMap base class to retrieve the
// decoded names and values.
// Use the methods of the CValidateObject base class to validate the parameters.
class CHttpRequestParams : 
#if (defined(ATL_HTTP_PARAM_MAP_CASEINSENSITIVE))
	public CHttpMap<CStringA, CStringA, CStringElementTraitsI<CStringA>, CStringElementTraitsI<CStringA> >, 
#else
	public CHttpMap<CStringA, CStringA, CStringElementTraits<CStringA>, CStringElementTraits<CStringA> >, 
#endif
	public CValidateObject<CHttpRequestParams>
{
public:
#if (defined(ATL_HTTP_PARAM_MAP_CASEINSENSITIVE))
	typedef CHttpMap<CStringA, CStringA, CStringElementTraitsI<CStringA>, CStringElementTraitsI<CStringA> > BaseMap;
#else
	typedef CHttpMap<CStringA, CStringA, CStringElementTraits<CStringA>, CStringElementTraits<CStringA> > BaseMap;
#endif

	LPCSTR Lookup(__in LPCSTR szName) const throw()
	{
		_ATLTRY
		{
			if (!szName)
				return NULL;

			const CPair *p = BaseMap::Lookup(szName);
			if (p)
			{
				return p->m_value;
			}
		}
		_ATLCATCHALL()
		{
		}
		return NULL;
	}

	// Call this function to build a collection of name-value pairs from a string of URL-encoded data.
	// Returns TRUE on success, FALSE on failure.
	// URL-encoded data:
	//      Each name-value pair is separated from the next by an ampersand (&)
	//      Each name is separated from its corresponding value by an equals signs (=)
	//      The end of the data to be parsed is indicated by a nul character (\0) or a pound symbol (#)
	//      A plus sign (+) in the input will be decoded as a space
	//      A percent sign (%) in the input signifies the start of an escaped octet.
	//          The next two digits represent the hexadecimal code of the character.
	//          For example, %21 is the escaped encoding for the US-ASCII exclamation mark and will be decoded as !.
	// Common sources of URL-encoded data are query strings and the bodies of POST requests with content type of application/x-www-form-urlencoded.
	//
	// Parse and Render are complementary operations.
	// Parse creates a collection from a string.
	// Render creates a string from a collection.
	ATL_NOINLINE __checkReturn BOOL Parse(__inout LPSTR szQueryString) throw()
	{
		while (szQueryString && *szQueryString)
		{
			LPSTR szUrlCurrent = szQueryString;
			LPSTR szName = szUrlCurrent;
			LPSTR szPropValue;

			while (*szQueryString)
			{
				if (*szQueryString == '=')
				{
					szQueryString++;
					break;
				}
				if (*szQueryString == '&')
				{
					break;
				}
				if (*szQueryString == '+')
					*szUrlCurrent = ' ';
				else if (*szQueryString == '%')
				{
					// if there is a % without two characters
					// at the end of the url we skip it
					if (*(szQueryString+1) && *(szQueryString+2))
					{
						short nFirstDigit = AtlHexValue(szQueryString[1]);
						short nSecondDigit = AtlHexValue(szQueryString[2]);

						if( nFirstDigit < 0 || nSecondDigit < 0 )
						{
							break;
						}
						*szUrlCurrent = static_cast<CHAR>(16*nFirstDigit+nSecondDigit);
						szQueryString += 2;
					}
					else
						*szUrlCurrent = '\0';
				}
				else
					*szUrlCurrent = *szQueryString;

				szQueryString++;
				szUrlCurrent++;
			}

			if (*szUrlCurrent == '&')
			{
				*szUrlCurrent++ = '\0';
				szQueryString++;
				szPropValue = "";
			}
			else
			{
				if (*szUrlCurrent)
					*szUrlCurrent++ = '\0';

				// we have the property name
				szPropValue = szUrlCurrent;
				while (*szQueryString && *szQueryString != '#')
				{
					if (*szQueryString == '&')
					{
						szQueryString++;
						break;
					}
					if (*szQueryString == '+')
						*szUrlCurrent = ' ';
					else if (*szQueryString == '%')
					{
						// if there is a % without two characters
						// at the end of the url we skip it
						if (*(szQueryString+1) && *(szQueryString+2))
						{
							short nFirstDigit = AtlHexValue(szQueryString[1]);
							short nSecondDigit = AtlHexValue(szQueryString[2]);

							if( nFirstDigit < 0 || nSecondDigit < 0 )
							{
								break;
							}
							*szUrlCurrent = static_cast<CHAR>(16*nFirstDigit+nSecondDigit);
							szQueryString += 2;
						}
						else
							*szUrlCurrent = '\0';
					}
					else
						*szUrlCurrent = *szQueryString;
					szQueryString++;
					szUrlCurrent++;
				}
				// we have the value
				*szUrlCurrent = '\0';
				szUrlCurrent++;
			}

			_ATLTRY
			{
				SetAt(szName, szPropValue);
			}
			_ATLCATCHALL()
			{
				return FALSE;
			}
		}
		return TRUE;
	}

	// Call this function to render the map of names and values into a buffer as a URL-encoded string.
	// Returns TRUE on success, FALSE on failure.
	// On entry, pdwLen should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	// On success, the buffer will contain the correct URL-encoded representation of the current object
	// suitable for sending to a server as a query string or in the body of a form.
	// URL-encoding:
	//      Each name-value pair is separated from the next by an ampersand (&)
	//      Each name is separated from its corresponding value by an equals signs (=)
	//      A space is encoded as a plus sign (+).
	//      Other unsafe characters (as determined by AtlIsUnsafeUrlChar) are encoded as escaped octets.
	//      An escaped octet is a percent sign (%) followed by two digits representing the hexadecimal code of the character.
	//
	// Parse and Render are complementary operations.
	// Parse creates a collection from a string.
	// Render creates a string from a collection.
	ATL_NOINLINE __checkReturn BOOL Render(__out_ecount(pdwLen) LPSTR szParameters, __inout LPDWORD pdwLen)
	{
		ATLENSURE(szParameters);
		ATLENSURE(pdwLen);
		_ATLTRY
		{
			if (GetCount() == 0)
			{
				*szParameters = '\0';
				*pdwLen = 0;
				return TRUE;
			}

			CStringA strParams;
			POSITION pos = GetStartPosition();
			while (pos != NULL)
			{
				LPCSTR szBuf = GetKeyAt(pos);
				EscapeToCString(strParams, szBuf);
				szBuf = GetValueAt(pos);
				if (*szBuf)
				{
					strParams+= '=';
					EscapeToCString(strParams, szBuf);
				}
				strParams+= '&';
				GetNext(pos);
			}

			DWORD dwLen = strParams.GetLength();
			strParams.Delete(dwLen-1);
			BOOL bRet = TRUE;
			if (dwLen > *pdwLen)
			{
				bRet = FALSE;
			}
			else
			{
				dwLen--;
				Checked::memcpy_s(szParameters, *pdwLen, static_cast<LPCSTR>(strParams), dwLen);
				szParameters[dwLen] = '\0';
			}

			*pdwLen = dwLen;
			return bRet;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}	
	}

}; // class CHttpRequestParams

#ifndef MAX_TOKEN_LENGTH
#define MAX_TOKEN_LENGTH (MAX_PATH)
#endif

// This class represents the information about a file that has been uploaded to the web server.
class CHttpRequestFile : public IHttpFile
{
private:

	// The name of the form field used to upload the file.
	CHAR m_szParamName[MAX_TOKEN_LENGTH];

	// The original file name of the uploaded file as set by the client.
	CHAR m_szFileName[MAX_PATH];

	// The original path and file name of the uploaded file as set by the client.
	CHAR m_szFullFileName[MAX_PATH];

	// The MIME type of the uploaded file.
	CHAR m_szContentType[MAX_TOKEN_LENGTH];

	// The name of the uploaded file on the server.
	CHAR m_szTempFileName[MAX_PATH];

	// The size of the file in bytes.
	ULONGLONG m_nFileSize;

public:

	CHttpRequestFile() throw()
	{
		*m_szParamName = '\0';
		*m_szFileName = '\0';
		*m_szFullFileName = '\0';
		*m_szContentType = '\0';
		m_nFileSize = 0;
	}

	__checkReturn BOOL Initialize(
		__in_opt LPCSTR pParamName,
		__in LPCSTR pFileName,
		__in_opt LPCSTR pTempFileName,
		__in_opt LPCSTR pContentType, 
		__in const ULONGLONG &nFileSize)
	{
		ATLENSURE( pFileName != NULL );

		if (!SafeStringCopy(m_szFullFileName, pFileName))
		{
			// path too long
			return FALSE;
		}

		if (pParamName && *pParamName)
		{
			if (!SafeStringCopy(m_szParamName, pParamName))
			{
				// string too long
				return FALSE;
			}
		}

		if (pTempFileName && *pTempFileName)
		{
			if (!SafeStringCopy(m_szTempFileName, pTempFileName))
			{
				// path too long
				return FALSE;
			}
		}

		if (pContentType && *pContentType)
		{
			if (!SafeStringCopy(m_szContentType, pContentType))
			{
				// string too long
				return FALSE;
			}
		}

		// Set m_szFileName to be the file name without the path.
		// This is likely to be the most meaningful part of the 
		// original file name once the file reaches the server.

		LPSTR szTmp = m_szFullFileName;
		LPSTR szFile = m_szFileName;
		LPSTR pszLastCharFileBuf=m_szFileName + _countof(m_szFileName) - 1;		
		while (*szTmp)
		{
			if (*szTmp == '\\')
			{
				szFile = m_szFileName;
			}
			else
			{
				if (szFile > pszLastCharFileBuf)
				{
					return FALSE;
				}
				*szFile++ = *szTmp;
			}
			szTmp++;
		}
		if (szFile > pszLastCharFileBuf)
		{
			return FALSE;
		}
		*szFile = 0;

		m_nFileSize = nFileSize;
		return TRUE;
	}


	//=======================================
	// IHttpFile interface
	//=======================================
	__checkReturn LPCSTR GetParamName()
	{
		return m_szParamName;
	}

	__checkReturn LPCSTR GetFileName()
	{
		return m_szFileName;
	}

	__checkReturn LPCSTR GetFullFileName()
	{
		return m_szFullFileName;
	}

	__checkReturn LPCSTR GetContentType()
	{
		return m_szContentType;
	}

	__checkReturn LPCSTR GetTempFileName()
	{
		return m_szTempFileName;
	}

	__checkReturn ULONGLONG GetFileSize()
	{
		return m_nFileSize;
	}

	void Free()
	{
		delete this;
	}

}; // class CHttpRequestFile


// utility function to ReadData from a ServerContext
ATL_NOINLINE inline 
__checkReturn BOOL ReadClientData(__inout IHttpServerContext *pServerContext, __out_ecount_part(*pdwLen,*pdwLen) LPSTR pbDest, __inout LPDWORD pdwLen, __in DWORD dwBytesRead)
{
	ATLENSURE(pServerContext != NULL);	
	ATLENSURE(pdwLen != NULL);
	ATLENSURE(pbDest != NULL);

	_ATLTRY
	{
		DWORD dwToRead = *pdwLen;
		DWORD dwAvailableBytes = pServerContext->GetAvailableBytes();
		DWORD dwRead(0);

		// Read from available data first
		if (dwBytesRead < dwAvailableBytes)
		{			
			LPBYTE pbAvailableData = pServerContext->GetAvailableData();
			pbAvailableData+= dwBytesRead;
			DWORD dwAvailableToRead = __min(dwToRead, dwAvailableBytes-dwBytesRead);			
			Checked::memcpy_s(pbDest, *pdwLen, pbAvailableData, dwAvailableToRead);
			dwBytesRead+= dwAvailableToRead;
			dwToRead-= dwAvailableToRead;
			pbDest+= dwAvailableToRead;
			dwRead+= dwAvailableToRead;
		}

		DWORD dwTotalBytes = pServerContext->GetTotalBytes();

		// If there is still more to read after the available data is exhausted
		if (dwToRead && dwBytesRead < dwTotalBytes)
		{
			DWORD dwClientBytesToRead = __min(pServerContext->GetTotalBytes()-dwBytesRead, dwToRead);
			DWORD dwClientBytesRead = 0;

			// keep on reading until we've read the amount requested
			do
			{
				dwClientBytesRead = dwClientBytesToRead;
				if (!pServerContext->ReadClient(pbDest, &dwClientBytesRead))
				{
					return FALSE;
				}
				dwClientBytesToRead-= dwClientBytesRead;
    			dwRead+= dwClientBytesRead;
				pbDest+= dwClientBytesRead;

			} while (dwClientBytesToRead != 0 && dwClientBytesRead != 0);

			 
		}

		*pdwLen = dwRead;
	}
	_ATLCATCHALL()
	{
		return FALSE;
	}

	return TRUE;
}

#ifndef MAX_MIME_BOUNDARY_LEN
	#define MAX_MIME_BOUNDARY_LEN 128
#endif

enum ATL_FORM_FLAGS
{
	ATL_FORM_FLAG_NONE = 0,
	ATL_FORM_FLAG_IGNORE_FILES = 1,
	ATL_FORM_FLAG_REFUSE_FILES = 2,
	ATL_FORM_FLAG_IGNORE_EMPTY_FILES = 4,
	ATL_FORM_FLAG_IGNORE_EMPTY_FIELDS = 8,
};

// Use this class to read multipart/form-data from the associated server context
// and generate files as necessary from the data in the body of the request.
class CMultiPartFormParser
{
protected:

	LPSTR                       m_pCurrent;
	LPSTR                       m_pEnd;
	LPSTR                       m_pStart;
	CHAR                        m_szBoundary[MAX_MIME_BOUNDARY_LEN+2];
	DWORD                       m_dwBoundaryLen;
	BOOL                        m_bFinished;
	CComPtr<IHttpServerContext> m_spServerContext;

public:

	typedef CHttpMap<CStringA, IHttpFile*, CStringElementTraits<CStringA> > FILEMAPTYPE;

	CMultiPartFormParser(__in IHttpServerContext* pServerContext) throw() :
		m_pCurrent(NULL),
		m_pEnd(NULL),
		m_pStart(NULL),
		m_dwBoundaryLen(0),
		m_bFinished(FALSE),
		m_spServerContext(pServerContext)
	{
		*m_szBoundary = '\0';
	}

	~CMultiPartFormParser() throw()
	{
		_ATLTRY
		{
			// free memory if necessary
			if (m_spServerContext->GetTotalBytes() > m_spServerContext->GetAvailableBytes())
			{
				free(m_pStart);
			}
		}
		_ATLCATCHALL()
		{
		}
	}

	// Call this function to read multipart/form-data from the current HTTP request,
	// allowing files to be uploaded to the web server.
	//
	// Returns TRUE on success, FALSE on failure.
	//
	// Forms can be sent to a web server using one of two encodings: application/x-www-form-urlencoded or multipart/form-data.
	// In addition to the simple name-value pairs typically associated with
	// application/x-www-form-urlencoded form data, multipart/form-data (as 
	// described in RFC 2388) can also contain files to be uploaded
	// to the web server.
	//
	// This function will generate a physical file for each file contained in the multipart/form-data request body.
	// The generated files are stored in the server's temporary directory as returned by the 
	// GetTempPath API and are named using the GetTempFileName API.
	// The information about each file can be obtained from the elements of the Files array.
	// You can retrieve the original name of the file on the client, the name of the generated file on the server,
	// the MIME content type of the uploaded file, the name of the form field associated with that file, and the size in
	// bytes of the file. All this information is exposed by the CHttpRequestFile objects in the array.
	//
	// In addition to generating files and populating the Files array with information about them,
	// this function also populates the pQueryParams array with the names and values of the other form fields
	// contained in the current request. The file fields are also added to this array. The value of these fields
	// is the full name of the generated file on the server.
	//
	//      Note that files can be generated even if this function returns FALSE unless you specify either the
	//      ATL_FORM_FLAG_IGNORE_FILES or the ATL_FORM_FLAG_REFUSE_FILES flag. If you don't specify one of these
	//      flags, you should always check the Files array for generated files and delete any that are no longer
	//      needed to prevent your web server from running out of disk space.
	//
	// dwFlags can be a combination of one or more of the following values:
	//      ATL_FORM_FLAG_NONE                  Default behavior.
	//      ATL_FORM_FLAG_IGNORE_FILES          Any attempt to upload files is ignored.
	//      ATL_FORM_FLAG_REFUSE_FILES          Any attempt to upload files is treated as a failure. The function will return FALSE.
	//      ATL_FORM_FLAG_IGNORE_EMPTY_FILES    Files with a size of zero bytes are ignored.
	//      ATL_FORM_FLAG_IGNORE_EMPTY_FIELDS   Fields with no content are ignored.
	ATL_NOINLINE BOOL GetMultiPartData(
		__inout FILEMAPTYPE& Files, 
		__inout CHttpRequestParams* pQueryParams, 
		__in DWORD dwFlags=ATL_FORM_FLAG_NONE) throw()
	{
		_ATLTRY
		{
			if (!InitializeParser())
			{
				return FALSE;
			}

			//Get to the first boundary
			if (!ReadUntilBoundary())
			{
				return FALSE;
			}

			CStringA strParamName;
			CStringA strFileName;
			CStringA strContentType;
			CStringA strData;
			BOOL bFound;

			while (!m_bFinished)
			{
				// look for "name" field
				if (!GetMimeData(strParamName, "name=", sizeof("name=")-1, &bFound, TRUE) || !bFound)
				{
					ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
					return FALSE;
				}

				// see if it's a file
				if (!GetMimeData(strFileName, "filename=", sizeof("filename=")-1, &bFound, TRUE))
				{
					ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
					return FALSE;
				}

				if (bFound)
				{
					if (dwFlags & ATL_FORM_FLAG_REFUSE_FILES)
					{
						return FALSE;
					}

					if (!strFileName.GetLength())
					{
						if(!ReadUntilBoundary())
						{
							return FALSE;
						}
						continue;
					}

					if (!GetMimeData(strContentType, "Content-Type:", sizeof("Content-Type:")-1, &bFound, TRUE))
					{
						ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
						return FALSE;
					}

					// move to the actual uploaded data
					if (!MoveToData())
					{
						ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
						return FALSE;
					}

					// if the user doesn't want files, don't save the file
					if (dwFlags & ATL_FORM_FLAG_IGNORE_FILES)
					{
						if (!ReadUntilBoundary(NULL, NULL))
						{
							return FALSE;
						}
						continue;
					}

					CAtlTemporaryFile ctf;
					HRESULT hr = ctf.Create();
					if (hr != S_OK)
						return FALSE;

					if (!ReadUntilBoundary(NULL, &ctf))
					{
						ctf.Close();
						return FALSE;
					}
					ULONGLONG nFileSize = 0;
					if (ctf.GetSize(nFileSize) != S_OK)
						return FALSE;

					if ((dwFlags & ATL_FORM_FLAG_IGNORE_EMPTY_FILES) && nFileSize == 0)
					{
						ctf.Close();
						continue;
					}

					CAutoPtr<CHttpRequestFile> spFile;

					CT2AEX<MAX_PATH+1> szTempFileNameA(ctf.TempFileName());

					ATLTRY(spFile.Attach(new CHttpRequestFile()));
					if (!spFile)
					{
						return FALSE;
					}

					if (!spFile->Initialize(strParamName, strFileName, szTempFileNameA, strContentType, nFileSize))
					{
						// one of the strings was too long
						return FALSE;
					}

					if (!Files.SetAt(szTempFileNameA, spFile))
					{
						return FALSE;
					}

					spFile.Detach();
					
					if (!pQueryParams || !pQueryParams->SetAt(strParamName, szTempFileNameA))
					{
						return FALSE;
					}

					ctf.HandsOff();

					continue;
				}

				// move to the actual uploaded data
				if (!MoveToData())
				{
					ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
					return FALSE;
				}

				if (!ReadUntilBoundary(&strData))
				{
					return FALSE;
				}

				if ((dwFlags & ATL_FORM_FLAG_IGNORE_EMPTY_FIELDS) && strData.GetLength() == 0)
					continue;

				if (!pQueryParams || !pQueryParams->SetAt(strParamName, strData))
				{
					return FALSE;
				}

			}

			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

private:

	// implementation

	// case insensitive substring search -- does not handle multibyte characters (unlike tolower)
	// allows searching up to a maximum point in a string
	inline char AtlCharLower(__in char ch) throw()
	{
		if (ch > 64 && ch < 91)
		{
			return ch+32;
		}

		return ch;
	}

	inline __checkReturn char * _stristr (__in const char * str1, __in const char * str2)
	{
		ATLENSURE(str1!=NULL);
		ATLENSURE(str2!=NULL);
		char *cp = (char *) str1;
		char *s1, *s2;

		if ( !*str2 )
			return((char *)str1);

		while (*cp)
		{
			s1 = cp;
			s2 = (char *) str2;

			while ( *s1 && *s2 && !(AtlCharLower(*s1)-AtlCharLower(*s2)) )
			{
				s1++, s2++;
			}

			if (!*s2)
			{
				return(cp);
			}

			cp++;
		}

		return(NULL);
	}


	inline __checkReturn char * _stristrex (__in const char * str1, __in const char * str2, __in const char * str1End)
	{
		ATLENSURE(str1!=NULL);
		ATLENSURE(str2!=NULL);
		char *cp = (char *) str1;
		char *s1, *s2;

		if ( !*str2 )
			return((char *)str1);

		while (cp != str1End)
		{
			s1 = cp;
			s2 = (char *) str2;

			while ( s1 != str1End && *s2 && !(AtlCharLower(*s1)-AtlCharLower(*s2)) )
			{
				s1++, s2++;
			}

			if (!*s2)
			{
				return (cp);
			}

			if (s1 == str1End)
			{
				return (NULL);
			}

			cp++;
		}

		return(NULL);
	}

	inline __checkReturn char * _strstrex (__in const char * str1, __in const char * str2, __in const char * str1End)
	{
		ATLENSURE(str1!=NULL);
		ATLENSURE(str2!=NULL);
		char *cp = (char *) str1;
		char *s1, *s2;

		if ( !*str2 )
			return((char *)str1);

		while (cp != str1End)
		{
			s1 = cp;
			s2 = (char *) str2;

			while ( s1 != str1End && *s2 && !((*s1)-(*s2)) )
			{
				s1++, s2++;
			}

			if (!*s2)
			{
				return (cp);
			}

			if (s1 == str1End)
			{
				return (NULL);
			}

			cp++;
		}

		return(NULL);
	}

	ATL_NOINLINE __checkReturn BOOL InitializeParser() throw()
	{
		ATLASSUME( m_spServerContext != NULL );

		_ATLTRY
		{
			DWORD dwBytesTotal = m_spServerContext->GetTotalBytes();

			// if greater than bytes available, allocate necessary space
			if (dwBytesTotal > m_spServerContext->GetAvailableBytes())
			{
				m_pStart = (LPSTR) malloc(dwBytesTotal);
				if (!m_pStart)
				{
					return FALSE;
				}
				m_pCurrent = m_pStart;
				DWORD dwLen = dwBytesTotal;
				if (!ReadClientData(m_spServerContext, m_pStart, &dwLen, 0) || dwLen != dwBytesTotal)
				{
					return FALSE;
				}
			}
			else
			{
				m_pStart = (LPSTR) m_spServerContext->GetAvailableData();
			}

			m_pCurrent = m_pStart;
			m_pEnd = m_pCurrent + dwBytesTotal;

			//get the boundary
			LPCSTR pszContentType = m_spServerContext->GetContentType();
			ATLASSERT(pszContentType != NULL);

			LPCSTR pszTmp = _stristr(pszContentType, "boundary=");
			if (!pszTmp)
			{
				ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
				return FALSE;
			}

			pszTmp += sizeof("boundary=")-1;
			BOOL bInQuote = FALSE;
			if (*pszTmp == '\"')
			{
				bInQuote = TRUE;
				pszTmp++;
			}

			LPSTR pszMimeBoundary = m_szBoundary;
			*pszMimeBoundary++ = '-';
			*pszMimeBoundary++ = '-';
			m_dwBoundaryLen = 2;
			while (*pszTmp && (bInQuote || IsStandardBoundaryChar(*pszTmp)))
			{
				if (m_dwBoundaryLen >= MAX_MIME_BOUNDARY_LEN)
				{
					ATLTRACE(atlTraceISAPI, 0, _T("Malformed MIME boundary"));
					return FALSE;
				}

				if (*pszTmp == '\r' || *pszTmp == '\n')
				{
					if (bInQuote)
					{
						pszTmp++;
						continue;
					}
					break;
				}
				if (bInQuote && *pszTmp == '"')
				{
					break;
				}

				*pszMimeBoundary++ = *pszTmp++;
				m_dwBoundaryLen++;
			}

			*pszMimeBoundary = '\0';
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}

		return TRUE;
	}

	inline __checkReturn BOOL MoveToData() throw()
	{
		LPSTR szEnd = _strstrex(m_pCurrent, "\r\n\r\n", m_pEnd);
		if (!szEnd)
		{
			return FALSE;
		}

		m_pCurrent = szEnd+4;
		if (m_pCurrent >= m_pEnd)
		{
			return FALSE;
		}

		return TRUE;
	}

	inline __checkReturn BOOL GetMimeData(__inout CStringA &str, __in_ecount(dwFieldLen) LPCSTR szField, __in DWORD dwFieldLen, __out LPBOOL pbFound, __in BOOL bIgnoreCase = FALSE) throw()
	{
		_ATLTRY
		{
			ATLASSERT( szField != NULL );
			ATLENSURE( pbFound != NULL );

			*pbFound = FALSE;

			LPSTR szEnd = _strstrex(m_pCurrent, "\r\n\r\n", m_pEnd);
			if (!szEnd)
			{
				return FALSE;
			}

			LPSTR szDataStart = NULL;

			if (!bIgnoreCase)
			{
				szDataStart = _strstrex(m_pCurrent, szField, szEnd);
			}
			else
			{
				szDataStart = _stristrex(m_pCurrent, szField, szEnd);
			}

			if (szDataStart)
			{
				szDataStart+= dwFieldLen;
				if (szDataStart >= m_pEnd)
				{
					return FALSE;
				}

				BOOL bInQuote = FALSE;
				if (*szDataStart == '\"')
				{
					bInQuote = TRUE;
					szDataStart++;
				}

				LPSTR szDataEnd = szDataStart;
				while (!bInQuote && (szDataEnd < m_pEnd) && (*szDataEnd == ' ' || *szDataEnd == '\t'))
				{
					szDataEnd++;
				}

				if (szDataEnd >= m_pEnd)
				{
					return FALSE;
				}

				while (szDataEnd < m_pEnd)
				{
					if (!IsValidTokenChar(*szDataEnd))
					{
						if (*szDataEnd == '\"' || !bInQuote)
						{
							break;
						}
					}
					szDataEnd++;
				}

				if (szDataEnd >= m_pEnd)
				{
					return FALSE;
				}

				str.SetString(szDataStart, (int)(szDataEnd-szDataStart));
				*pbFound = TRUE;
			}

			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

	ATL_NOINLINE __checkReturn BOOL ReadUntilBoundary(__inout_opt CStringA* pStrData=NULL, __inout_opt CAtlTemporaryFile* pCtf=NULL) throw()
	{
		_ATLTRY
		{
			LPSTR szBoundaryStart = m_pCurrent;
			LPSTR szBoundaryEnd = NULL;

			do 
			{
				szBoundaryStart = _strstrex(szBoundaryStart, m_szBoundary, m_pEnd);
				if (szBoundaryStart)
				{
					if ((szBoundaryStart-m_pStart) >= 2)
					{
						if (*(szBoundaryStart-1) != 0x0a || *(szBoundaryStart-2) != 0x0d)
						{
							szBoundaryStart++;
							continue;
						}
					}
					szBoundaryEnd = szBoundaryStart+m_dwBoundaryLen;
					if (szBoundaryEnd+2 >= m_pEnd)
					{
						return FALSE;
					}
					if (szBoundaryEnd[0] == '\r' && szBoundaryEnd[1] == '\n')
					{
						break;
					}
					if (szBoundaryEnd[0] == '-' && szBoundaryEnd[1] == '-')
					{
						m_bFinished = TRUE;
						break;
					}
					szBoundaryStart++;
				}
			} while (szBoundaryStart && szBoundaryStart < m_pEnd);

			if (!szBoundaryStart || szBoundaryStart >= m_pEnd)
			{
				return FALSE;
			}

			if ((szBoundaryStart-m_pStart) >= 2)
			{
				szBoundaryStart-= 2;
			}
			if (pStrData)
			{
				pStrData->SetString(m_pCurrent, (int)(szBoundaryStart-m_pCurrent)); 
			}
			if (pCtf)
			{
				if (FAILED(pCtf->Write(m_pCurrent, (DWORD)(szBoundaryStart-m_pCurrent))))
				{
					return FALSE;
				}
			}

			if (!m_bFinished)
			{
				m_pCurrent = szBoundaryEnd+2;
				if (m_pCurrent >= m_pEnd)
				{
					return FALSE;
				}
			}

			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

	static inline __checkReturn BOOL IsStandardBoundaryChar(__in CHAR ch) throw()
	{
		if ( (ch >= 'A' && ch <= 'Z') ||
			 (ch >= 'a' && ch <= 'z') ||
			 (ch >= '0' && ch <= '9') ||
			 (ch == '\'') ||
			 (ch == '+')  ||
			 (ch == '_')  ||
			 (ch == '-')  ||
			 (ch == '=')  ||
			 (ch == '?') )
		{
			return TRUE;
		}

		return FALSE;
	}

	inline __checkReturn BOOL IsValidTokenChar(__in CHAR ch) throw()
	{
		return ( (ch != 0) && (ch != 0xd) && (ch != 0xa) && (ch != ' ') && (ch != '\"') );
	}

private:
	// Prevents copying.
	CMultiPartFormParser(__in const CMultiPartFormParser& /*that*/) throw()
	{
		ATLASSERT(FALSE);
	}

	const CMultiPartFormParser& operator=(__in const CMultiPartFormParser& /*that*/) throw()
	{
		ATLASSERT(FALSE);
		return (*this);
	}
}; // class CMultiPartFormParser


// 48K max form size
#ifndef DEFAULT_MAX_FORM_SIZE
#define DEFAULT_MAX_FORM_SIZE 49152
#endif

// This class provides access to the information contained in an HTTP request submitted to a web server.
//
// CHttpRequest provides access to the query string parameters, form fields, cookies, and files
// that make up an HTTP request, as well as many other important properties of the request.
class CHttpRequest : public IHttpRequestLookup
{
protected:
	// Implementation: Array used to map an HTTP request method (for example, "GET" or "POST")
	// from a string to a numeric constant from the HTTP_METHOD enum (HTTP_METHOD_GET or HTTP_METHOD_HEAD).
	static const char* const m_szMethodStrings[];

	// Implementation: The server context.
	CComPtr<IHttpServerContext> m_spServerContext;

	// Implementation: The number of bytes read from the body of the request.
	DWORD m_dwBytesRead;

	// Implementation: TRUE if the request method was POST and the encoding was
	// multipart/form-data, FALSE otherwise.
	BOOL m_bMultiPart;

	CCookie m_EmptyCookie;

	// Implementation: Constructor function used to reinitialize all data members.
	void Construct() throw()
	{
		m_spServerContext.Release();
		m_bMultiPart = FALSE;
		m_dwBytesRead = 0;
		if (m_pFormVars != &m_QueryParams)
			delete m_pFormVars;

		m_pFormVars = NULL;
		m_pFormVars = &m_QueryParams;
		m_QueryParams.RemoveAll();
		m_QueryParams.SetShared(false);

		ClearFilesAndCookies();
	}

	void ClearFilesAndCookies() throw()
	{
		m_Files.RemoveAll();
		m_Files.SetShared(false);
		m_requestCookies.RemoveAll();
		m_requestCookies.SetShared(false);
	}

public:

	// Implementation: The collection of query parameters (name-value pairs) obtained from the query string.
	CHttpRequestParams m_QueryParams;

	// Implementation: The collection of form fields (name-value pairs).
	// The elements of this collection are obtained from the query string for a GET request,
	// or from the body of the request for a POST request.
	CHttpRequestParams *m_pFormVars;

	// The array of CHttpRequestFiles obtained from the current request.
	// See CHttpRequest::Initialize and CMultiPartFormParser::GetMultiPartData for more information.
	typedef CHttpPtrMap<CStringA, IHttpFile*, CStringElementTraits<CStringA> > FileMap;
	FileMap m_Files;

	// Implementation: The array of cookies obtained from the current request.
	typedef CHttpMap<CStringA, CCookie, CStringElementTraits<CStringA> > CookieMap;
	CookieMap m_requestCookies;

	// Numeric constants for the HTTP request methods (such as GET and POST).
	enum HTTP_METHOD 
	{
		HTTP_METHOD_UNKNOWN=-1,
		HTTP_METHOD_GET,
		HTTP_METHOD_POST,
		HTTP_METHOD_HEAD,
		HTTP_METHOD_DELETE,
		HTTP_METHOD_LINK,
		HTTP_METHOD_UNLINK,
		HTTP_METHOD_DEBUG  // Debugging support for VS7
	};

	// The collection of query parameters (name-value pairs) obtained from the query string.
	// A read-only property.
	__declspec(property(get=GetQueryParams)) const CHttpRequestParams& QueryParams;

	// Returns a reference to the collection of query parameters(name-value pairs) 
	// obtained from the query string.
	const CHttpRequestParams& GetQueryParams() const throw()
	{
		return m_QueryParams;
	}

	// The collection of form fields (name-value pairs).
	// The elements of this collection are obtained from the query string for a GET request,
	// or from the body of the request for a POST request.
	// A read-only property.
	__declspec(property(get=GetFormVars)) const CHttpRequestParams& FormVars;

	// Returns a reference to the collection of form fields (name-value pairs)
	// obtained from the query string for a GET request,
	// or from the body of the request for a POST request.
	const CHttpRequestParams& GetFormVars() const throw()
	{
		return *m_pFormVars;
	}

	CHttpRequest() throw()
	{
		m_bMultiPart = FALSE;
		m_dwBytesRead = 0;
		m_pFormVars = &m_QueryParams;
	}

	virtual ~CHttpRequest() throw()
	{
		DeleteFiles();

		if (m_pFormVars != &m_QueryParams)
		{
			delete m_pFormVars;
			m_pFormVars = NULL;
		}
	}

	// Constructs and initializes the object.
	CHttpRequest(
		__in IHttpServerContext *pServerContext,
		__in DWORD dwMaxFormSize=DEFAULT_MAX_FORM_SIZE,
		__in DWORD dwFlags=ATL_FORM_FLAG_NONE) throw(...)
		:m_pFormVars(NULL)
	{
		Construct();
		if (!Initialize(pServerContext, dwMaxFormSize, dwFlags))
			AtlThrow(E_FAIL);
	}

	CHttpRequest(__in IHttpRequestLookup *pRequestLookup) throw(...)
		:m_pFormVars(NULL)
	{
		if (!Initialize(pRequestLookup)) // Calls Construct for you
			AtlThrow(E_FAIL);
	}

	//=========================================================================================
	// BEGIN IHttpRequestLoookup interface
	//=========================================================================================
	__success(return != NULL) __checkReturn POSITION GetFirstQueryParam(__deref_out LPCSTR *ppszName, __deref_out LPCSTR *ppszValue)
	{		
		POSITION pos = m_QueryParams.GetStartPosition();
		if (pos != NULL)
		{
			ATLENSURE(ppszName != NULL);
			ATLENSURE(ppszValue != NULL);
			*ppszName = m_QueryParams.GetKeyAt(pos);
			*ppszValue = m_QueryParams.GetValueAt(pos);
		}

		return pos;
	}

	__success(return != NULL) __checkReturn POSITION GetNextQueryParam(__in POSITION pos, __deref_out LPCSTR *ppszName, __deref_out LPCSTR *ppszValue)
	{
		ATLENSURE(pos != NULL);
		

		POSITION posNext(pos);
		m_QueryParams.GetNext(posNext);
		if (posNext != NULL)
		{
			ATLENSURE(ppszName != NULL);
			ATLENSURE(ppszValue != NULL);
			*ppszName = m_QueryParams.GetKeyAt(posNext);
			*ppszValue = m_QueryParams.GetValueAt(posNext);
		}

		return posNext;
	}

	__success(return != NULL) __checkReturn POSITION GetFirstFormVar(__deref_out LPCSTR *ppszName, __deref_out LPCSTR *ppszValue)
	{
		// if no form vars and just pointing to the query params,
		// then return NULL
		if (m_pFormVars == &m_QueryParams)
			return NULL;

		POSITION pos = m_pFormVars->GetStartPosition();
		if (pos != NULL)
		{
			ATLENSURE(ppszName != NULL);
			ATLENSURE(ppszValue != NULL);
			*ppszName = m_pFormVars->GetKeyAt(pos);
			*ppszValue = m_pFormVars->GetValueAt(pos);
		}

		return pos;
	}

	__success(return != NULL) __checkReturn POSITION GetNextFormVar(__in POSITION pos, __deref_out LPCSTR *ppszName, __deref_out LPCSTR *ppszValue)
	{
		ATLASSERT(pos != NULL);
		ATLASSERT(ppszName != NULL);
		ATLASSERT(ppszValue != NULL);

		POSITION posNext(pos);
		m_pFormVars->GetNext(posNext);
		if (posNext != NULL)
		{
			*ppszName = m_pFormVars->GetKeyAt(posNext);
			*ppszValue = m_pFormVars->GetValueAt(posNext);
		}

		return posNext;
	}

	POSITION GetFirstFile(__in LPCSTR *ppszName, __deref_out IHttpFile **ppFile)
	{
		ATLASSERT(ppszName != NULL);
		ATLASSERT(ppFile != NULL);

		POSITION pos = m_Files.GetStartPosition();
		if (pos != NULL)
		{
			*ppszName = m_Files.GetKeyAt(pos);
			*ppFile = m_Files.GetValueAt(pos);
		}

		return pos;
	}

	__success(return != NULL) __checkReturn POSITION GetNextFile(__in POSITION pos, __deref_out LPCSTR *ppszName, __deref_out IHttpFile **ppFile)
	{
		ATLASSERT(pos != NULL);
		ATLASSERT(ppszName != NULL);
		ATLASSERT(ppFile != NULL);

		m_Files.GetNext(pos);
		if (pos != NULL)
		{
			*ppszName = m_Files.GetKeyAt(pos);
			*ppFile = m_Files.GetValueAt(pos);
		}

		return pos;
	}

	// Returns a pointer to the IHttpServerContext interface for the current request.
	HRESULT GetServerContext(__deref_out_opt IHttpServerContext ** ppOut)
	{
		return m_spServerContext.CopyTo(ppOut);
	}
	//=========================================================================================
	// END IHttpRequestLookup interface
	//=========================================================================================

	__success(return != NULL) __checkReturn POSITION GetFirstCookie(__deref_out LPCSTR *ppszName, __deref_out const CCookie **ppCookie) throw()
	{
		ATLASSERT(ppszName != NULL);
		ATLASSERT(ppCookie != NULL);
		POSITION pos = NULL;
		if (GetRequestCookies())
		{
			pos = m_requestCookies.GetStartPosition();
			if (pos != NULL)
			{
				*ppszName = m_requestCookies.GetKeyAt(pos);
				*ppCookie = &(m_requestCookies.GetValueAt(pos));
			}
		}
		return pos;
	}

	__success(return != NULL) __checkReturn POSITION GetNextCookie(__in POSITION pos, __deref_out LPCSTR *ppszName, __deref_out const CCookie **ppCookie) throw()
	{
		ATLASSERT(pos != NULL);
		ATLASSERT(ppszName != NULL);
		ATLASSERT(ppCookie != NULL);

		POSITION posNext(pos);
		m_requestCookies.GetNext(posNext);
		if (posNext != NULL)
		{
			*ppszName = m_requestCookies.GetKeyAt(posNext);
			*ppCookie = &(m_requestCookies.GetValueAt(posNext));
		}
		return posNext;
	}

	void SetServerContext(__in IHttpServerContext *pServerContext) throw()
	{
		m_spServerContext = pServerContext;
	}

	BOOL Initialize(__in IHttpRequestLookup *pRequestLookup) throw()
	{
		_ATLTRY
		{	
			ATLASSERT(pRequestLookup != NULL);
			// if there's no pRequestLookup, just return
			if (!pRequestLookup)
				return TRUE;

			Construct();
			HRESULT hr = pRequestLookup->GetServerContext(&m_spServerContext);
			if (FAILED(hr))
				return FALSE;

			LPCSTR szName(NULL);
			LPCSTR szValue(NULL);

			// Initialize query params from the IHttpRequestLookup*
			POSITION pos(pRequestLookup->GetFirstQueryParam(&szName, &szValue));
			while (pos != NULL)
			{
				m_QueryParams.SetAt(szName, szValue);
				pos = pRequestLookup->GetNextQueryParam(pos, &szName, &szValue);
			}
			m_QueryParams.SetShared(true);

			// Initialize the form vars from the IHttpRequestLookup*
			pos = pRequestLookup->GetFirstFormVar(&szName, &szValue);
			if (pos)
			{
				m_pFormVars = NULL;
				ATLTRY(m_pFormVars = new CHttpRequestParams);
				if (!m_pFormVars)
					return FALSE;

				while (pos != NULL)
				{
					m_pFormVars->SetAt(szName, szValue);
					pos = pRequestLookup->GetNextFormVar(pos, &szName, &szValue);
				}
				m_pFormVars->SetShared(true);
			}
			else
			{
				m_pFormVars = &m_QueryParams;
			}

			// Initialize the files from the IHttpRequestLookup*
			IHttpFile *pFile(NULL);
			pos = pRequestLookup->GetFirstFile(&szName, &pFile);
			while (pos != NULL)
			{
				m_Files.SetAt(szName, pFile);
				pos = pRequestLookup->GetNextFile(pos, &szName, &pFile);
			}
			m_Files.SetShared(true);

			// Initialzie the cookies form the IHttpRequestLookup*
			BOOL bRet = FALSE;
			CStringA strCookies;
			bRet = GetCookies(strCookies);
			if (bRet)
			{
				bRet = Parse(strCookies);
			}
			m_requestCookies.SetShared(false);
			return bRet;
		} // _ATLTRY
		_ATLCATCHALL()
		{
		}
		return FALSE;
	}

	// Call this function to initialize the object with information about the current request.
	//
	// Returns TRUE on success, FALSE on failure.
	//
	// Call Initialize directly or via the appropriate constructor before using the methods and
	// properties of the request object.
	//
	// Initialize does the following:
	//
	//      Parses and decodes the query string into a collection of name-value pairs.
	//      This collection is accessible via the GetQueryParams method or the QueryParams property.
	//
	//      Sets m_bMultiPart to TRUE if the request is a POST request with multipart/form-data encoding.
	//
	//      Parses the body of a POST request if the size of the request data is less than or equal to dwMaxFormSize.
	//      The body of the request will consist of simple form fields and may also contain files if the request is encoded as multipart/form-data.
	//      In that case, the dwFlags parameter is passed to CMultiPartFormParser::GetMultiPartData to control the creation of the files.
	//      The collection of form fields is accessible via the GetFormVars method or the FormVars property.
	//      The collection of files is accessible via the m_Files member.
	//
	// Note that Initialize does not parse the cookies associated with a request.
	// Cookies are not processed until an attempt is made to access a cookie in the collection.
	BOOL Initialize(
		__in IHttpServerContext *pServerContext,
		__in DWORD dwMaxFormSize=DEFAULT_MAX_FORM_SIZE,
		__in DWORD dwFlags=ATL_FORM_FLAG_NONE) throw()
	{
		_ATLTRY
		{
			ATLASSERT(pServerContext != NULL);
			if (!pServerContext)
			{
				return FALSE;
			}

			m_spServerContext = pServerContext;

			HTTP_METHOD httpMethod = GetMethod();
			LPCSTR pszQueryString = GetQueryString();
			if (pszQueryString && *pszQueryString)
			{
				// Parse the query string.
				CHAR szQueryString[ATL_URL_MAX_URL_LENGTH];
				if (!SafeStringCopy(szQueryString, pszQueryString))
				{
					return FALSE;
				}

				if (!m_QueryParams.Parse(szQueryString))
				{
					return FALSE;
				}
			}

			if (m_QueryParams.IsShared())
			{
				return TRUE;
			}

			// If this is a GET request, the collection of form fields
			// is the same as the collection of query parameters.
			if (httpMethod == HTTP_METHOD_GET)
			{
				m_pFormVars = &m_QueryParams;
				return TRUE;
			}
			else if (httpMethod == HTTP_METHOD_POST)
			{
				LPCSTR szContentType = GetContentType();
				if (!szContentType)
					return FALSE;

				// Don't parse the form data if the size is bigger than the maximum specified.
				if (m_spServerContext->GetTotalBytes() > dwMaxFormSize)
				{
					if (strncmp(szContentType, "multipart/form-data", 19) == 0)
						m_bMultiPart = TRUE;

					m_dwBytesRead = 0;

					return TRUE;
				}

				// If POSTed data is urlencoded, call InitFromPost.
				if (strncmp(szContentType, "application/x-www-form-urlencoded", 33) == 0 && !m_pFormVars->IsShared())
					return InitFromPost();

				// If POSTed data is encoded as multipart/form-data, use CMultiPartFormParser.
				if (strncmp(szContentType, "multipart/form-data", 19) == 0 && !m_pFormVars->IsShared())
				{
					if (m_pFormVars != &m_QueryParams)
						delete m_pFormVars;
					m_pFormVars = NULL;

					CMultiPartFormParser FormParser(m_spServerContext);
					ATLTRY(m_pFormVars = new CHttpRequestParams);
					if (!m_pFormVars)
						return FALSE;

					BOOL bRet = FormParser.GetMultiPartData(m_Files, m_pFormVars, dwFlags);
					return bRet;
				}

				// else initialize m_dwBytesRead for ReadData
				m_dwBytesRead = 0;
			}

			return TRUE;
		}
		_ATLCATCHALL()
		{
		}
		return FALSE;

	}

	// Implementation: Call this function to initialize the collection of form fields
	// from the body of an application/x-www-form-urlencoded POST request.
	ATL_NOINLINE BOOL InitFromPost() throw()
	{
		_ATLTRY
		{
			ATLASSUME(m_spServerContext != NULL);

			// create our m_pFormVars
			if (m_pFormVars == NULL || m_pFormVars == &m_QueryParams)
			{
				ATLTRY(m_pFormVars = new CHttpRequestParams);
				if (m_pFormVars == NULL)
				{
					return FALSE;
				}
			}   

			// read the form data into a buffer
			DWORD dwBytesTotal = m_spServerContext->GetTotalBytes();
			CAutoVectorPtr<CHAR> szBuff;
			if ((dwBytesTotal+1 < dwBytesTotal) || (dwBytesTotal < 0))
			{
				return FALSE;
			}
			if (!szBuff.Allocate(dwBytesTotal+1))
			{
				return FALSE;
			}
			// first copy the available
			BOOL bRet = ReadClientData(m_spServerContext, szBuff, &dwBytesTotal, 0);
			if (bRet)
			{
				szBuff[dwBytesTotal] = '\0';
				bRet = m_pFormVars->Parse(szBuff);
			}

			return bRet;
		}
		_ATLCATCHALL()
		{
		}
		return FALSE;
	}

	// Call this function to remove the files listed in m_Files from the web server's hard disk.
	// Returns the number of files deleted.
	int DeleteFiles() throw()
	{
		int nDeleted = 0;
		POSITION pos = m_Files.GetStartPosition();
		while (pos != NULL)
		{
			LPCSTR szTempFile = m_Files.GetKeyAt(pos);
			if (szTempFile && DeleteFileA(szTempFile))
			{
				nDeleted++;
			}
			m_Files.GetNext(pos);
		}

		return nDeleted;
	}

	// Read a specified amount of data into pbDest and return the bytes read in pdwLen.
	// Returns TRUE on success, FALSE on failure.
	BOOL ReadData(__out_ecount_part(*pdwLen,*pdwLen) LPSTR pDest, __inout LPDWORD pdwLen)
	{
		ATLENSURE(pDest);
		ATLENSURE(pdwLen);

		BOOL bRet = ReadClientData(m_spServerContext, pDest, pdwLen, m_dwBytesRead);
		if (bRet)
			m_dwBytesRead+= *pdwLen;
		return bRet;
	}

	// Returns the number of bytes available in the request buffer accessible via GetAvailableData.
	// If GetAvailableBytes returns the same value as GetTotalBytes, the request buffer contains the whole request.
	// Otherwise, the remaining data should be read from the client using ReadData.
	// Equivalent to EXTENSION_CONTROL_BLOCK::cbAvailable.
	__checkReturn DWORD GetAvailableBytes() throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetAvailableBytes();
	}

	// Returns the total number of bytes to be received from the client.
	// If this value is 0xffffffff, then there are four gigabytes or more of available data.
	// In this case, ReadData should be called until no more data is returned.
	// Equivalent to the CONTENT_LENGTH server variable or EXTENSION_CONTROL_BLOCK::cbTotalBytes. 
	__checkReturn DWORD GetTotalBytes() throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetTotalBytes();
	}

	// Returns a pointer to the request buffer containing the data sent by the client.
	// The size of the buffer can be determined by calling GetAvailableBytes.
	// Equivalent to EXTENSION_CONTROL_BLOCK::lpbData
	__checkReturn LPBYTE GetAvailableData() throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetAvailableData();
	}


	// Returns a nul-terminated string that contains the query information.
	// This is the part of the URL that appears after the question mark (?). 
	// Equivalent to the QUERY_STRING server variable or EXTENSION_CONTROL_BLOCK::lpszQueryString.
	__checkReturn LPCSTR GetQueryString() throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetQueryString();
	}

	// Returns a nul-terminated string that contains the HTTP method of the current request.
	// Examples of common HTTP methods include "GET" and "POST".
	// Equivalent to the REQUEST_METHOD server variable or EXTENSION_CONTROL_BLOCK::lpszMethod.
	__checkReturn LPCSTR GetMethodString() throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetRequestMethod();
	}

	// Returns an HTTP_METHOD enum value corresponding to the HTTP method of the current request.
	// Returns HTTP_METHOD_UNKNOWN if the request method is not one of the following methods:
	//      GET
	//      POST
	//      HEAD
	//      DELETE
	//      LINK
	//      UNLINK
	__checkReturn HTTP_METHOD GetMethod() throw(...)
	{
		LPCSTR szMethod = GetMethodString();
		if (!szMethod)
			return HTTP_METHOD_UNKNOWN;
		for (int i=0; m_szMethodStrings[i]; i++)
		{
			if (strcmp(szMethod, m_szMethodStrings[i]) == 0)
				return (HTTP_METHOD) i;
		}
		return HTTP_METHOD_UNKNOWN;
	}

	// Returns a nul-terminated string that contains the content type of the data sent by the client.
	// Equivalent to the CONTENT_TYPE server variable or EXTENSION_CONTROL_BLOCK::lpszContentType.
	__checkReturn LPCSTR GetContentType() throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetContentType();
	}


	// Call this function to retrieve a nul-terminated string containing the value of the "AUTH_USER" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetAuthUserName(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("AUTH_USER", szBuff, pdwSize);
	}

	// Call this function to retrieve a nul-terminated string containing the value of the "APPL_PHYSICAL_PATH" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetPhysicalPath(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("APPL_PHYSICAL_PATH", szBuff, pdwSize);
	}

	// Call this function to retrieve a nul-terminated string containing the value of the "AUTH_PASSWORD" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetAuthUserPassword(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("AUTH_PASSWORD", szBuff, pdwSize);
	}

	// Call this function to retrieve a nul-terminated string containing the value of the "URL" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetUrl(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, DWORD *pdwSize) throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("URL", szBuff, pdwSize);
	}

	// Call this function to retrieve a nul-terminated string containing the value of the "REMOTE_HOST" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetUserHostName(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("REMOTE_HOST", szBuff, pdwSize);
	}

	// Call this function to retrieve a nul-terminated string containing the value of the "REMOTE_ADDR" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetUserHostAddress(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("REMOTE_ADDR", szBuff, pdwSize);
	}

	// Call this function to retrieve a nul-terminated string containing the physical path of the script.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	// The script path is the same as GetPathTranslated up to the first .srf or .dll.
	// For example, if GetPathTranslated returns "c:\inetpub\vcisapi\hello.srf\goodmorning",
	// then this function returns "c:\inetpub\vcisapi\hello.srf".
	__checkReturn LPCSTR GetScriptPathTranslated() throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetScriptPathTranslated();
	}

	// Returns a nul-terminated string that contains the physical path of the requested resource on the local server.
	// Equivalent to the PATH_TRANSLATED server variable or EXTENSION_CONTROL_BLOCK::lpszPathTranslated.
	__checkReturn LPCSTR GetPathTranslated() throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetPathTranslated();
	}

	// Returns a nul-terminated string that contains the path of the current request.
	// This is the part of the URL that appears after the server name, but before the query string.
	// Equivalent to the PATH_INFO server variable or EXTENSION_CONTROL_BLOCK::lpszPathInfo.
	__checkReturn LPCSTR GetPathInfo() throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetPathInfo();
	}

	// Call this function to determine whether the current request was authenticated.
	__checkReturn BOOL GetAuthenticated() throw(...)
	{
		// We assume that if we get an authentication type from IIS,
		// then the user has been authenticated.
		CStringA strAuthType;
		if (GetAuthenticationType(strAuthType) &&
			strAuthType.GetLength() > 0)
			return TRUE;

		return FALSE;
	}

	// Call this function to retrieve a nul-terminated string containing the value of the "AUTH_TYPE" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetAuthenticationType(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("AUTH_TYPE", szBuff, pdwSize);
	}

	// Call this function to retrieve a nul-terminated string containing the value of the "REMOTE_USER" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetUserName(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, DWORD __inout *pdwSize) throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("REMOTE_USER", szBuff, pdwSize);
	}

	// Call this function to retrieve a nul-terminated string containing the value of the "HTTP_USER_AGENT" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	 __checkReturn BOOL GetUserAgent(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
	 {
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("HTTP_USER_AGENT", szBuff, pdwSize);
	 }

	// Call this function to retrieve a nul-terminated string containing the value of the "HTTP_ACCEPT_LANGUAGE" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	 __checkReturn BOOL GetUserLanguages(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
	 {
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("HTTP_ACCEPT_LANGUAGE", szBuff, pdwSize);
	 }

	// Call this function to retrieve a nul-terminated string containing the value of the "HTTP_ACCEPT" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetAcceptTypes(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("HTTP_ACCEPT", szBuff, pdwSize);
	}

	// Call this function to retrieve a nul-terminated string containing the value of the "HTTP_ACCEPT_ENCODING" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetAcceptEncodings(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("HTTP_ACCEPT_ENCODING", szBuff, pdwSize);
	}


	// Call this function to retrieve a nul-terminated string containing the value of the "HTTP_REFERER" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetUrlReferer(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("HTTP_REFERER", szBuff, pdwSize);
	}

	// Call this function to retrieve a nul-terminated string containing the value of the "SCRIPT_NAME" server variable.
	//
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	//
	// On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
	// On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
	__checkReturn BOOL GetScriptName(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
	{
		ATLENSURE(m_spServerContext);
		return m_spServerContext->GetServerVariable("SCRIPT_NAME", szBuff, pdwSize);
	}

	// Fills a buffer with the contents of the HTTP_COOKIE headers sent
	// from the browser.
	__checkReturn BOOL GetCookies(__out_ecount_part(*pdwSize,*pdwSize) LPSTR szBuf, __inout LPDWORD pdwSize) const throw(...)
	{		
		ATLASSERT(szBuf != NULL);

		CStringA strCookie;
		if (GetCookies(strCookie))
		{
			ATLENSURE(pdwSize != NULL);
			if (pdwSize && *pdwSize > (DWORD)strCookie.GetLength())
			{
				Checked::strcpy_s(szBuf, *pdwSize, strCookie);
				*pdwSize = strCookie.GetLength();
				return true;
			}
		}
		return false;
	}

	// Fills a CStringA with the contents of the HTTP_COOKIE headers sent
	// from the browser.
	__checkReturn BOOL GetCookies(__inout CStringA& strBuff) const throw()
	{
		return GetServerVariable("HTTP_COOKIE", strBuff);
	}

	// Call this function to retrieve a reference to the specified cookie.
	// Returns a CCookie reference to the specified cookie or a
	// reference to an empty cookie if the name can not be found.
	ATL_NOINLINE const CCookie& Cookies(__in LPCSTR szName) throw(...)
	{
		if (GetRequestCookies())
		{
			// p->m_value is a const CCookie&
			CookieMap::CPair *p = NULL;
			ATLTRY(p = m_requestCookies.Lookup(szName));
			if (p)
			{
				return p->m_value;
			}
		}
		m_EmptyCookie.Empty(); // make sure it is cleared.
		return m_EmptyCookie;
	}


	// Call this function to retrieve the session cookie.
	const CCookie& GetSessionCookie() throw(...)
	{
		return Cookies(SESSION_COOKIE_NAME);
	}

	// Call this function to retrieve the value of the requested server variable in a CStringA object.
	// Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
	// Equivalent to EXTENSION_CONTROL_BLOCK::GetServerVariable.
	BOOL GetServerVariable(__in LPCSTR szVariable, __out CStringA &str) const
	{
		ATLENSURE(m_spServerContext);

		DWORD dwSize = 0;
		BOOL bRet = FALSE;
		_ATLTRY
		{
			m_spServerContext->GetServerVariable(szVariable, NULL, &dwSize);
			bRet = m_spServerContext->GetServerVariable(szVariable, str.GetBuffer(dwSize), &dwSize);
			if (dwSize > 0)
				dwSize--;
			str.ReleaseBuffer(dwSize);
		}
		_ATLCATCHALL()
		{
			bRet = FALSE;
		}
		return bRet;
	}

	// Call this function to retrieve the value of the "APPL_PHYSICAL_PATH" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetPhysicalPath(__out CStringA &str) throw()
	{
		return GetServerVariable("APPL_PHYSICAL_PATH", str);
	}

	// Call this function to retrieve the value of the "REMOTE_HOST" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetUserHostName(__out CStringA &str) throw()
	{
		return GetServerVariable("REMOTE_HOST", str);
	}

	// Call this function to retrieve the value of the "REMOTE_ADDR" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetUserHostAddress(__out CStringA &str) throw()
	{
		return GetServerVariable("REMOTE_ADDR", str);
	}

	// Call this function to retrieve the value of the "AUTH_TYPE" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetAuthenticationType(__out CStringA &str) throw()
	{
		return GetServerVariable("AUTH_TYPE", str);
	}

	// Call this function to retrieve the value of the "REMOTE_USER" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetUserName(__out CStringA &str) throw()
	{
		return GetServerVariable("REMOTE_USER", str);
	}

	// Call this function to retrieve the value of the "HTTP_USER_AGENT" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetUserAgent(__out CStringA &str) throw()
	{
		return GetServerVariable("HTTP_USER_AGENT", str);
	}

	// Call this function to retrieve the value of the "HTTP_ACCEPT_LANGUAGE" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetUserLanguages(__out CStringA &str) throw()
	{
		return GetServerVariable("HTTP_ACCEPT_LANGUAGE", str);
	}

	// Call this function to retrieve the value of the "AUTH_USER" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetAuthUserName(__out CStringA &str) throw()
	{
		return GetServerVariable("AUTH_USER", str);
	}

	// Call this function to retrieve the value of the "AUTH_PASSWORD" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetAuthUserPassword(__out CStringA &str) throw()
	{
		return GetServerVariable("AUTH_PASSWORD", str);
	}

	// Call this function to retrieve the value of the "URL" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetUrl(__out CStringA &str) throw()
	{
		return GetServerVariable("URL", str);
	}

	// Call this function to retrieve the value of the "HTTP_ACCEPT" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetAcceptTypes(__out CStringA &str) throw()
	{
		return GetServerVariable("HTTP_ACCEPT", str);
	}

	// Call this function to retrieve the value of the "HTTP_ACCEPT_ENCODING" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetAcceptEncodings(__out CStringA& str) throw()
	{
		return GetServerVariable("HTTP_ACCEPT_ENCODING", str);
	}

	// Call this function to retrieve the value of the "HTTP_REFERER" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetUrlReferer(__out CStringA &str) throw()
	{
		return GetServerVariable("HTTP_REFERER", str);
	}

	// Call this function to retrieve the value of the "SCRIPT_NAME" server variable.
	// Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
	__checkReturn BOOL GetScriptName(__out CStringA &str) throw()
	{
		return GetServerVariable("SCRIPT_NAME", str);
	}

	// Implementation: Call this function to populate the collection 
	// of CCookie objects with the cookies in the current request.
	// Returns TRUE on success, FALSE on failure.
	__checkReturn BOOL GetRequestCookies() throw()
	{
		BOOL bRet = FALSE;

		if (m_requestCookies.GetCount())
			return TRUE; // we already got the cookies!

		CStringA strCookies;
		if (GetCookies(strCookies))
		{
			bRet = Parse(strCookies);
		}
		return bRet;
	}

private:
	struct Pair
	{
		Pair(__in const CStringA &n, __in const CStringA& v)
		{
			name = n;
			value = v;
		}

		Pair(__in const Pair& rhs)
		{	
			name = rhs.name;
			value = rhs.value;
		}
		
		CStringA name;
		CStringA value;
	};
	static const int INVALID_COOKIE_VERSION = -1;
public:

	// Implementation: Call this function to populate m_requestCookies
	// with a collection of CCookie objects which represents the
	// cookies contained in szCookie header sent from the browser.
	BOOL Parse(__in LPCSTR szCookieIn) 
	{
		ATLENSURE(szCookieIn!=NULL);
		UINT acp = GetACP();
		LPCSTR szStart;
		LPCSTR szEnd;
		CStringA name, value;
		CAtlList<Pair> pair_list;

		// create a list of name=value pairs
		// these are separated by or ;
		szStart = szCookieIn;
		szEnd = szStart;
		while (*szStart)
		{
			name.Empty();
			value.Empty();
			szStart = SkipSpace(szStart, (WORD)acp);
			if (*szStart == '\0')
				break;
			
			szEnd = szStart;
			while (*szEnd && *szEnd != '=' && *szEnd != ';' )
				szEnd++;

			if (szEnd <= szStart)
			{
				if (*szEnd ==';')
				{
					szEnd++;
					szStart = szEnd;
					continue;
				}
				szStart = szEnd;
				break; // no name or error
			}

			CopyToCString(name, szStart, szEnd);
			if (*szEnd == '\0' || *szEnd == ';' )
			{
				// no value expected;
				pair_list.AddTail(Pair(name, value));
				szStart = szEnd;
			}
			else if ( *szEnd == '=')
			{
				szEnd++; // skip '='
				szStart = szEnd;
				while (*szEnd && *szEnd != ';')
					szEnd++;

				if (szEnd <= szStart)
				{
					if (*szEnd == ';')
					{
						szEnd++;
						szStart = szEnd;
						continue;
					}
					szStart = szEnd;
					pair_list.AddTail(Pair(name,value));
					break; // no value
				}

				CopyToCString(value, szStart, szEnd);
				pair_list.AddTail(Pair(name,value));
				szStart = szEnd;
			}
		}

		// now make cookies out of the list
		// The first item could be $Version, which would
		// be the version for all cookies in the list.
		// any other $Version's found in the list will be ignored.
		if (pair_list.GetCount() <= 0)
			return TRUE; // nothing in the list

		// check for $Version
		static const char szVersion[] = "$Version";
		int nVersion = INVALID_COOKIE_VERSION;
		if (!strncmp(pair_list.GetHead().name, szVersion, sizeof(szVersion)+1))
		{
			char *pStop = NULL;
			errno_t errnoValue = AtlStrToNum<int, char>(&nVersion, pair_list.GetHead().value, &pStop, 10);
			if (errnoValue == ERANGE)
				nVersion = INVALID_COOKIE_VERSION; // $Version contained garbage
			pair_list.RemoveHead(); // Remove $Version
		}

		POSITION pos = pair_list.GetHeadPosition();
		bool bInCookie = false;
		CCookie cookie;

		while (pos)
		{
			const Pair &p = pair_list.GetNext(pos);
			if (p.name[0] == '$')
			{
				if (!bInCookie)
					return FALSE; // invalid cookie string
				else
				{
					//add attribute to current cookie
					if(!cookie.AddAttribute(p.name.Right(p.name.GetLength()-1),p.value))
					{
						return FALSE;
					}
				}
			}
			else
			{
				if (!bInCookie)
				{
					bInCookie = true;
					cookie.SetName(p.name);
					cookie.ParseValue(p.value);
					if (nVersion != INVALID_COOKIE_VERSION)
						cookie.SetVersion(nVersion);
				}
				else
				{
					// add previous cookie
					CStringA strPrevName;
					if(!cookie.GetName(strPrevName))
					{
						return FALSE;
					}
					m_requestCookies.SetAt(strPrevName, cookie);

					// empty current cookie and start over
					cookie.Empty();
					cookie.SetName(p.name);
					cookie.ParseValue(p.value);
					if (nVersion != INVALID_COOKIE_VERSION)
						cookie.SetVersion(nVersion);

				}
			}
		}

		if (!cookie.IsEmpty())
		{
			CStringA strName;
			if(!cookie.GetName(strName))
			{
				return FALSE;
			}
			m_requestCookies.SetAt(strName, cookie);
		}
		return TRUE;

	}

private:
	// Call this function to copy a substring to a CString reference and ensure nul-termination.
	inline void CopyToCString(__out CStringA& string, __in_ecount(pEnd-pStart) LPCSTR pStart, __in LPCSTR pEnd) throw( ... )
	{
		ATLENSURE( pStart != NULL );
		ATLENSURE( pEnd != NULL );
		ATLENSURE( pEnd>=pStart );

		string.SetString(pStart, (int)(pEnd-pStart));
		string.Trim();
	}

	inline LPCSTR SkipSpace(__in LPCSTR sz, __in WORD nCodePage) throw()
	{
		if (sz == NULL)
			return NULL;

		while (isspace(static_cast<unsigned char>(*sz)))
			sz = CharNextExA(nCodePage, sz, 0);
		return sz;
	}
public:


	__success(SUCCEEDED(return)) __checkReturn HRESULT STDMETHODCALLTYPE QueryInterface(__in REFIID riid, __deref_out void **ppv)
	{
		if (!ppv)
			return E_POINTER;
		if (InlineIsEqualGUID(riid, __uuidof(IHttpRequestLookup)))
		{
			*ppv = static_cast<IUnknown*>(static_cast<IHttpRequestLookup*>(this));
			AddRef();
			return S_OK;
		}
		if (InlineIsEqualGUID(riid, __uuidof(IUnknown)))
		{
			*ppv = static_cast<IUnknown*>(this);
			AddRef();
			return S_OK;
		}
		return E_NOINTERFACE;
	}

	ULONG STDMETHODCALLTYPE AddRef()
	{
		return 1;
	}

	ULONG STDMETHODCALLTYPE Release()
	{
		return 1;
	}
}; // class CHttpRequest

__declspec(selectany) const char* const CHttpRequest::m_szMethodStrings[] = {
	"GET",
	"POST",
	"HEAD",
	"DELETE",
	"LINK",
	"UNLINK",
	"DEBUG",  // Debugging support for VS7
	NULL
};

// This class provides type conversions via the Write method
// and overloaded left shift << operator for writing
// data to the IWriteStream interface. The IWriteStream interface
// only accepts strings, but this helper class allows you to transparently
// pass many different types by providing automatic type conversions.
//
// Notes on Type Conversions:
//      Numeric types are converted to their decimal representations.
//      Floating point values are output with a precision of 6 decimal places.
//      Currency values are converted according to the locale settings of the current thread.
class CWriteStreamHelper
{
protected:
	// Implementation: The IWriteStream interface.
	IWriteStream *m_pStream;
	UINT m_ulACP;

public:
	CWriteStreamHelper() throw()
		:m_pStream(NULL)
	{
		m_ulACP = _AtlGetConversionACP();
	}

	CWriteStreamHelper(__in_opt IWriteStream *pStream) throw()
	{
		m_pStream = pStream;
		m_ulACP = _AtlGetConversionACP();
	}

	// Attach a IWriteStream
	IWriteStream *Attach(__in IWriteStream *pStream) throw()
	{
		IWriteStream *pRetStream = m_pStream;
		m_pStream = pStream;
		return pRetStream;
	}

	// Call this function to write data to the IWriteStream interface managed by this object.
	// Returns TRUE on success, FALSE on failure.
	BOOL Write(__in_z LPCSTR szOut) throw()
	{
		ATLASSUME(m_pStream != NULL);

		if (!szOut)
			return FALSE;

		DWORD dwWritten;
		return SUCCEEDED(m_pStream->WriteStream(szOut, (int) strlen(szOut), &dwWritten));
	}

	// Call this function to write data to the IWriteStream interface managed by this object.
	// Returns TRUE on success, FALSE on failure.
	BOOL Write(__in int n) throw()
	{
		CHAR szTmp[21];
		Checked::itoa_s(n, szTmp, _countof(szTmp), 10);
		return Write(szTmp);
	}

	// Call this function to write data to the IWriteStream interface managed by this object.
	// Returns TRUE on success, FALSE on failure.
	BOOL Write(__in unsigned int u) throw()
	{
		CHAR szTmp[21];
		Checked::ultoa_s(u, szTmp, _countof(szTmp), 10);
		return Write(szTmp);
	}

	// Call this function to write data to the IWriteStream interface managed by this object.
	// Returns TRUE on success, FALSE on failure.
	BOOL Write(__in short int w) throw()
	{
		return Write((int) w);
	}

	// Call this function to write data to the IWriteStream interface managed by this object.
	// Returns TRUE on success, FALSE on failure.
	BOOL Write(__in unsigned short int w) throw()
	{
		return Write((unsigned int) w);
	}

	// Call this function to write data to the IWriteStream interface managed by this object.
	// Returns TRUE on success, FALSE on failure.
	BOOL Write(__in long int dw) throw()
	{
		CHAR szTmp[21];
		Checked::ltoa_s(dw, szTmp, _countof(szTmp), 10);
		return Write(szTmp);
	}

	// Call this function to write data to the IWriteStream interface managed by this object.
	// Returns TRUE on success, FALSE on failure.
	BOOL Write(__in unsigned long int dw) throw()
	{
		CHAR szTmp[21];
		Checked::ultoa_s(dw, szTmp, _countof(szTmp), 10);
		return Write(szTmp);
	}

	// Call this function to write data to the IWriteStream interface managed by this object.
	// Returns TRUE on success, FALSE on failure.
	BOOL Write(__in double d, __in int nDigitCount=ATL_DEFAULT_PRECISION) throw()
	{
		ATLASSUME(m_pStream != NULL);

		CHAR szTmp[512];
		int nDec = 0;
		int nSign = 0;
		bool fWriteDec=true;
		if (0 != _fcvt_s(szTmp, _countof(szTmp), d, nDigitCount, &nDec, &nSign))
		{
			// too large
			return FALSE;
		}

		if (nSign != 0)
			m_pStream->WriteStream("-", 1, NULL);
		if (nDec < 0)
		{
			nDec *= -1;
			m_pStream->WriteStream("0.", 2, NULL);
			for (int i=0;i<nDec;i++)
			{
				m_pStream->WriteStream("0", 1, NULL);
			}
			nDec = 0;
			fWriteDec=false;
		}

		char *p = szTmp;
		while (*p)
		{
			// if the decimal lies at the end of the number
			// (no digits to the right of the decimal, we don't
			// print it.
			if (nDec == 0 && fWriteDec)
				m_pStream->WriteStream(".", 1, NULL);
			m_pStream->WriteStream(p, 1, NULL);
			nDec--;
			p++;
		}
		return TRUE;
	}

	// Call this function to write data to the IWriteStream interface managed by this object.
	// Returns TRUE on success, FALSE on failure.
	BOOL Write(__in __int64 i) throw()
	{
		CHAR szTmp[21];
		Checked::i64toa_s(i, szTmp, _countof(szTmp), 10);
		return Write(szTmp);
	}

	// Call this function to write data to the IWriteStream interface managed by this object.
	// Returns TRUE on success, FALSE on failure.
	BOOL Write(__in unsigned __int64 i) throw()
	{
		CHAR szTmp[21];
		Checked::ui64toa_s(i, szTmp, _countof(szTmp), 10);
		return Write(szTmp);
	}

	// Call this function to write data to the IWriteStream interface managed by this object.
	// Returns TRUE on success, FALSE on failure.
	BOOL Write(__in CURRENCY c) throw()
	{
		CHAR szDest[256];
		CHAR szNumber[32];

		Checked::i64toa_s(c.int64, szNumber, _countof(szNumber), 10);
		int nLen = (int) strlen(szNumber);
		if (nLen < 5)
		{
			// prepend ascii zeros
			Checked::memmove_s(szNumber+5-nLen, 32-(5-nLen), szNumber, nLen+1);
			memset(szNumber, '0', 5-nLen);
			nLen = 5;
		}

		Checked::memmove_s(szNumber+nLen-3, 32-(nLen-3), szNumber+nLen-4, 5);
		szNumber[nLen-4] = '.';

		int nRet = GetCurrencyFormatA(GetThreadLocale(), 0, szNumber, NULL, szDest, sizeof(szDest));
		if (nRet > 0)
			return Write(szDest);

		ATLASSERT(GetLastError()==ERROR_INSUFFICIENT_BUFFER);

		nRet = GetCurrencyFormatA(GetThreadLocale(), 0, szNumber, NULL, NULL, 0);
		ATLASSERT(nRet > 0);

		if (nRet <= 0)
			return FALSE;

		CAutoVectorPtr<CHAR> szBuffer;
		if (!szBuffer.Allocate(nRet))
		{
			SetLastError(ERROR_NOT_ENOUGH_MEMORY);
			return FALSE;
		}
		nRet = GetCurrencyFormatA(GetThreadLocale(), 0, szNumber, NULL, szBuffer, nRet);

		ATLASSERT(nRet > 0);
		BOOL bRet = FALSE;
		if (nRet > 0)
			bRet = Write(szBuffer);

		return bRet;
	}

	// Call this function to write data to the IWriteStream interface managed by this object.
	// Returns TRUE on success, FALSE on failure.
	BOOL Write(__in LPCWSTR wsz) throw()
	{
		ATLASSUME(m_pStream != NULL);

		BOOL bRet;

		_ATLTRY
		{
			CW2A sz(wsz, m_ulACP);

			if (!sz)
			{
				bRet = FALSE;
			}

			DWORD dwWritten;
			bRet = SUCCEEDED(m_pStream->WriteStream(sz, (int) strlen(sz), &dwWritten));
		}
		_ATLCATCHALL()
		{
			bRet = FALSE;
		}

		return bRet;
	}

	// Use this operator to write data to the IWriteStream interface managed by this object.
	CWriteStreamHelper& operator<<(__in LPCSTR szStr) throw(...)
	{
		if (!Write(szStr))
			AtlThrow(E_FAIL);
		return *this;
	}

	// Use this operator to write data to the IWriteStream interface managed by this object.
	CWriteStreamHelper& operator<<(__in LPCWSTR wszStr) throw(...)
	{
		if (!Write(wszStr))
			AtlThrow(E_FAIL);
		return *this;
	}

	// Use this operator to write data to the IWriteStream interface managed by this object.
	CWriteStreamHelper& operator<<(__in int n) throw(...)
	{
		if (!Write(n))
			AtlThrow(E_FAIL);
		return *this;
	}

	// Use this operator to write data to the IWriteStream interface managed by this object.
	CWriteStreamHelper& operator<<(__in short int w) throw(...)
	{
		if (!Write(w))
			AtlThrow(E_FAIL);
		return *this;
	}

	// Use this operator to write data to the IWriteStream interface managed by this object.
	CWriteStreamHelper& operator<<(__in unsigned int u) throw(...)
	{
		if (!Write(u))
			AtlThrow(E_FAIL);
		return *this;
	}

	// Use this operator to write data to the IWriteStream interface managed by this object.
	CWriteStreamHelper& operator<<(__in long int dw) throw(...)
	{
		if (!Write(dw))
			AtlThrow(E_FAIL);
		return *this;
	}

	// Use this operator to write data to the IWriteStream interface managed by this object.
	CWriteStreamHelper& operator<<(__in unsigned long int dw) throw(...)
	{
		if (!Write(dw))
			AtlThrow(E_FAIL);
		return *this;
	}

	// Use this operator to write data to the IWriteStream interface managed by this object.
	CWriteStreamHelper& operator<<(__in double d) throw(...)
	{
		if (!Write(d))
			AtlThrow(E_FAIL);
		return *this;
	}

	// Use this operator to write data to the IWriteStream interface managed by this object.
	CWriteStreamHelper& operator<<(__in __int64 i) throw(...)
	{
		if (!Write(i))
			AtlThrow(E_FAIL);
		return *this;
	}

	// Use this operator to write data to the IWriteStream interface managed by this object.
	CWriteStreamHelper& operator<<(__in unsigned __int64 i) throw(...)
	{
		if (!Write(i))
			AtlThrow(E_FAIL);
		return *this;
	}

	// Use this operator to write data to the IWriteStream interface managed by this object.
	CWriteStreamHelper& operator<<(__in CURRENCY c) throw(...)
	{
		if (!Write(c))
			AtlThrow(E_FAIL);
		return *this;
	}

	UINT SetConversionCodePage(__in UINT nNewCP)
	{
		UINT nOldCP = m_ulACP;
		m_ulACP = nNewCP;
		return nOldCP;
	}
};


template <DWORD dwSizeT=ATL_ISAPI_BUFFER_SIZE>
class CAtlIsapiBuffer
{
protected:
	char m_szBuffer[dwSizeT];
	LPSTR m_pBuffer;
	DWORD m_dwLen;
	DWORD m_dwAlloc;
	HANDLE m_hProcHeap;

public:
	CAtlIsapiBuffer() throw()
	{
		if (dwSizeT > 0)
			m_szBuffer[0] = 0;

		m_pBuffer = m_szBuffer;
		m_dwLen = 0;
		m_dwAlloc = dwSizeT;
		m_hProcHeap = GetProcessHeap();
	}

	CAtlIsapiBuffer(__in LPCSTR sz)
	{
		m_pBuffer = m_szBuffer;
		m_dwLen = 0;
		m_dwAlloc = dwSizeT;
		m_hProcHeap = GetProcessHeap();

		if (!Append(sz))
			AtlThrow(E_OUTOFMEMORY);
	}

	~CAtlIsapiBuffer() throw()
	{
		Free();
	}

	BOOL Alloc(__in DWORD dwSize) throw()
	{
		if (m_dwAlloc >= dwSize)
		{
			return TRUE;
		}
		if (m_pBuffer != m_szBuffer)
		{
			HeapFree(m_hProcHeap, 0, m_pBuffer);
			m_dwLen = 0;
			m_dwAlloc = 0;
		}
		m_pBuffer = (LPSTR)HeapAlloc(m_hProcHeap, 0, dwSize);
		if (m_pBuffer)
		{
			m_dwAlloc = dwSize;
			return TRUE;
		}
		return FALSE;
	}

	BOOL ReAlloc(__in DWORD dwNewSize) throw()
	{
		if (dwNewSize <= m_dwAlloc)
			return TRUE;

		if (m_pBuffer == m_szBuffer)
		{
			BOOL bRet = Alloc(dwNewSize);
			if (bRet)
			{
				Checked::memcpy_s(m_pBuffer, dwNewSize, m_szBuffer, m_dwLen);
			}
			return bRet;
		}

		LPSTR pvNew = (LPSTR )HeapReAlloc(m_hProcHeap, 0, m_pBuffer, dwNewSize);
		if (pvNew)
		{
			m_pBuffer = pvNew;
			m_dwAlloc = dwNewSize;
			return TRUE;
		}
		return FALSE;
	}

	void Free() throw()
	{
		if (m_pBuffer != m_szBuffer)
		{
			HeapFree(m_hProcHeap,0 , m_pBuffer);
			m_dwAlloc = dwSizeT;
			m_pBuffer = m_szBuffer;
		}
		Empty();
	}

	void Empty() throw()
	{
		if (m_pBuffer)
		{
			m_pBuffer[0]=0;
			m_dwLen  = 0;
		}
	}

	DWORD GetLength() throw()
	{
		return m_dwLen;
	}

	BOOL Append(__in LPCSTR sz, __in int nLen = -1) throw()
	{
		if (!sz)
			return FALSE;

		if (nLen == -1)
			nLen = (int) strlen(sz);

                DWORD newLen = m_dwLen + nLen + 1;
                if (newLen <= m_dwLen || newLen <= (DWORD)nLen)
                {
                        return FALSE;
                }
		if (newLen > m_dwAlloc)
		{
			if (!ReAlloc(m_dwAlloc + (nLen+1 > ATL_ISAPI_BUFFER_SIZE ? nLen+1 : ATL_ISAPI_BUFFER_SIZE)))
				return FALSE;
		}
		Checked::memcpy_s(m_pBuffer + m_dwLen, m_dwAlloc-m_dwLen, sz, nLen);
		m_dwLen += nLen;
		m_pBuffer[m_dwLen]=0;
		return TRUE;
	}

	operator LPCSTR() throw()
	{
		return m_pBuffer;
	}

	CAtlIsapiBuffer& operator+=(__in LPCSTR sz)
	{
		if (!Append(sz))
			AtlThrow(E_OUTOFMEMORY);
		return *this;
	}
}; // class CAtlIsapiBuffer

// This class represents the response that the web server will send back to the client.
//
// CHttpResponse provides friendly functions for building up the headers, cookies, and body of an HTTP response.
// The class derives from IWriteStream and CWriteStreamHelper, allowing you to call those classes' methods
// to build up the body of the response. By default, the class improves performance by buffering the response until it is complete before sending it back to the client.
class CHttpResponse : public IWriteStream, public CWriteStreamHelper
{
private:

	// Implementation: A map of HTTP response headers.
	// The key is the name of the response header.
	// The value is the data for the response header.
	CSimpleMap<CStringA, CStringA> m_headers;

	// Implementation: Determines whether the response is currently being buffered.
	BOOL m_bBufferOutput;

	// Implementation: Determines whether any output should be sent to the client.
	// Intended mainly for HEAD requests, where the client should get the same headers
	// (i.e. Content-Length) as for a GET request
	BOOL m_bSendOutput;

	// Implementation: The limit in bytes of the response buffer.
	// When the limit is reached, the buffer is automatically flushed
	// and data is sent to the client. You can set this to ULONG_MAX
	// to enable full buffering (this is the default, and is required
	// for enabling keep alive connections).
	DWORD m_dwBufferLimit;

	// Implementation: The server context.
	CComPtr<IHttpServerContext> m_spServerContext;

	// Implementation: The HTTP status code for the response.
	int m_nStatusCode;

	// Implementation: Determines whether the response headers have already been sent to the client.
	BOOL m_bHeadersSent;

	// Implementation: Handle of the file being transmitted so it can be closed
	// when the async I/O completes
	HANDLE m_hFile;

public:
	// Implementation: The buffer used to store the response before
	// the data is sent to the client.
	CAtlIsapiBuffer<> m_strContent;

	// Numeric constants for the HTTP status codes used for redirecting client requests.
	enum HTTP_REDIRECT
	{ 
		HTTP_REDIRECT_MULTIPLE=300,
		HTTP_REDIRECT_MOVED=301,
		HTTP_REDIRECT_FOUND=302,
		HTTP_REDIRECT_SEE_OTHER=303,
		HTTP_REDIRECT_NOT_MODIFIED=304,
		HTTP_REDIRECT_USE_PROXY=305,
		HTTP_REDIRECT_TEMPORARY_REDIRECT=307
	};

	CHttpResponse() throw()
	{
		m_bBufferOutput = TRUE;
		m_dwBufferLimit = ULONG_MAX;
		m_nStatusCode = 200;
		m_pStream = this;
		m_bHeadersSent = FALSE;
		m_bSendOutput = TRUE;
		m_hFile = INVALID_HANDLE_VALUE;
	}

	CHttpResponse(__in IHttpServerContext *pServerContext)
	{
		m_bBufferOutput = TRUE;
		m_dwBufferLimit = ULONG_MAX;
		m_nStatusCode = 200;
		m_pStream = this;
		m_bHeadersSent = FALSE;
		ATLENSURE(Initialize(pServerContext));
		m_bSendOutput = TRUE;
		m_hFile = INVALID_HANDLE_VALUE;
	}

	// The destructor flushes the buffer if there is content that
	// hasn't yet been sent to the client.
	virtual ~CHttpResponse() throw()
	{
		Flush(TRUE);
		if (m_hFile && m_hFile != INVALID_HANDLE_VALUE)
			CloseHandle(m_hFile);
	}

	// Call this function to initialize the response object with a pointer to the server context.
	// Returns TRUE on success, FALSE on failure.
	__checkReturn BOOL Initialize(__in IHttpServerContext *pServerContext) throw()
	{
		ATLASSERT(pServerContext != NULL);
		if (!pServerContext)
			return FALSE;

		m_spServerContext = pServerContext;

		return TRUE;
	}

	// This is called to initialize the CHttpResponse for a child handler.  By default, it
	// assumes the parent will be responsible for sending the headers.
	__checkReturn BOOL Initialize(__in IHttpRequestLookup *pLookup) throw(...)
	{
		ATLASSERT(pLookup);
		if (!pLookup)
			return FALSE;

		CComPtr<IHttpServerContext> spContext;
		HRESULT hr = pLookup->GetServerContext(&spContext);
		if (FAILED(hr))
			return FALSE;

		if (!Initialize(spContext))
			return FALSE;

		m_bHeadersSent = TRUE;

		return TRUE;
	}

	// Returns a pointer to the IHttpServerContext interface for the current request.
	__checkReturn HRESULT GetServerContext(__deref_out_opt IHttpServerContext ** ppOut) throw()
	{
		return m_spServerContext.CopyTo(ppOut);
	}

	void Detach()
	{
		m_spServerContext.Release();
		HaveSentHeaders(TRUE);
	}
	// Call this function to set buffering options for the response.
	//
	// This function allows you to turn buffering on or off, and to set a size limit
	// on the amount of data that will be buffered before being sent to the client.
	// 
	// When you turn off buffering, the current contents of the buffer will be sent to the client.
	// If you need to clear the buffer without sending the contents to the client, call ClearContent instead.
	//
	// When the size of the buffer is reduced below the current size of the buffered content,
	// the entire buffer is flushed.
	void SetBufferOutput(__in BOOL bBufferOutput, __in DWORD dwBufferLimit=ATL_ISAPI_BUFFER_SIZE) throw()
	{
		if (m_bBufferOutput && !bBufferOutput)
		{
			// before turning off buffering, flush
			// the current contents
			Flush();
		}
		SetBufferLimit(dwBufferLimit);

		m_bBufferOutput = bBufferOutput;
	}

	// Call this function to determine whether data written to the response object is being buffered or not.
	// Returns TRUE if output is being buffered, FALSE otherwise.
	__checkReturn BOOL GetBufferOutput() throw()
	{
		return m_bBufferOutput;
	}

	// Call this function to determine whether the response headers have been sent
	// Returns TRUE if headers have been sent, FALSE otherwise.
	__checkReturn BOOL HaveSentHeaders() throw()
	{
		return m_bHeadersSent;
	}

	// Call this function to override the m_bHeadersSent state.  This is useful
	// when you want child handlers (e.g. from an include or subhandler) to send the headers
	void HaveSentHeaders(__in BOOL bSent) throw()
	{
		m_bHeadersSent = bSent;
	}

	// Call this function to set a size limit on the amount of data buffered by the reponse object.
	// When the size of the buffer is reduced below the current size of the buffered content,
	// the entire buffer is flushed.
	// See GetBufferLimit.
	void SetBufferLimit(__in DWORD dwBufferLimit) throw()
	{
		if (m_bBufferOutput)
		{
			if (m_strContent.GetLength() >= dwBufferLimit)
			{
				// new buffer limit is less than the
				// size currently buffered.  So flush
				// the current buffer
				Flush();
			}
		}
		m_dwBufferLimit = dwBufferLimit;
	}

	// Returns the current size limit of the buffer in bytes.
	// See SetBufferLimit.
	DWORD GetBufferLimit() throw()
	{
		return m_dwBufferLimit;
	}

	// Returns the current value of the Content-Type header if present, otherwise returns NULL.
	LPCSTR GetContentType() throw()
	{
		// return the content type from the
		// header collection if any
		_ATLTRY
		{
			CStringA strKey("Content-Type");

			int nIndex = m_headers.FindKey(strKey);
			if (nIndex >= 0)
				return m_headers.GetValueAt(nIndex);
		}
		_ATLCATCHALL()
		{
		}
		return NULL;
	}

	// Call this function to set the Content-Type of the HTTP response.
	// Examples of common MIME content types include text/html and text/plain.
	BOOL SetContentType(__in_opt LPCSTR szContentType) throw()
	{
		_ATLTRY
		{
			if (!m_headers.SetAt("Content-Type", szContentType))
				return m_headers.Add("Content-Type", szContentType);
		}
		_ATLCATCHALL()
		{
		}
		return FALSE;
	}

	// Call this function to set the HTTP status code of the response.
	// If not set explicitly, the default status code is 200 (OK).
	// See GetStatusCode.
	void SetStatusCode(__in int nCode) throw()
	{
		m_nStatusCode = nCode;
	}

	// Returns the current HTTP status code of the response.
	// See SetStatusCode.
	int GetStatusCode() throw()
	{
		return m_nStatusCode;
	}

	// Call this function to set the Cache-Control http header of the response.
	// Examples of common Cache-Control header values: public, private, max-age=delta-seconds
	BOOL SetCacheControl(__in_opt LPCSTR szCacheControl) throw()
	{
		_ATLTRY
		{
			if (!m_headers.SetAt("Cache-Control", szCacheControl))
				return m_headers.Add("Cache-Control", szCacheControl);
		}
		_ATLCATCHALL()
		{
		}
		return FALSE;
	}

	// Call this function to set the Expires HTTP header to the absolute date/time
	// specified in the stExpires parameter
	BOOL SetExpiresAbsolute(__in const SYSTEMTIME& stExpires) throw()
	{
		_ATLTRY
		{
			CStringA strExpires;
			SystemTimeToHttpDate(stExpires, strExpires);

			if (!m_headers.SetAt("Expires", strExpires))
				return m_headers.Add("Expires", strExpires);
		}
		_ATLCATCHALL()
		{
		}
		return FALSE;
	}

	// Call this function to set the Expires HTTP header to a relative date/time
	// value specified in minutes;
	BOOL SetExpires(__in long lMinutes) throw()
	{
		CFileTime ft;
		GetSystemTimeAsFileTime(&ft);

		// add the specified number of minutes
		ft += CFileTimeSpan(((ULONGLONG)lMinutes)*60*10000000);

		SYSTEMTIME st;
		FileTimeToSystemTime(&ft, &st);
		return SetExpiresAbsolute(st);
	}

	// Call this function to set whether or not to output to client.
	// Intended primarily for HEAD requests
	BOOL SetWriteToClient(__in BOOL bSendOutput) throw()
	{
		m_bSendOutput = bSendOutput;
		return TRUE;
	}

	// Call this function to determine whether or not the data is
	// sent to the client.  Intended primarily for HEAD requests
	BOOL GetWriteToClient() throw()
	{
		return m_bSendOutput;
	}

	// Call this function to write data to the response object.
	//
	// Returns S_OK on success, E_INVALIDARG or E_FAIL on failure.
	//
	// See WriteClient for comments on buffering.
	//
	// szOut    A pointer to the first byte of the data to be written.
	//
	// nLen     The number of bytes to write. If this parameter is -1,
	//          szOut is assumed to be a nul-terminated string and the
	//          whole string will be written.
	//
	// pdwWritten   A DWORD pointer that can be used to get the number of bytes written.
	//              This parameter can be NULL.
	__checkReturn HRESULT WriteStream(__in_z LPCSTR szOut, __in int nLen, __out_opt DWORD *pdwWritten)
	{
		ATLASSUME(m_spServerContext != NULL);

		if (pdwWritten)
			*pdwWritten = 0;
		if (nLen == -1)
		{
			if (!szOut)
				return E_INVALIDARG;
			nLen = (int) strlen(szOut);
		}
		BOOL bRet = WriteLen(szOut, nLen);
		if (!bRet)
		{
			return AtlHresultFromLastError();
		}
		if (pdwWritten)
			*pdwWritten = nLen;
		return S_OK;
	}

	// Call this function to write data to the response object.
	//
	// Returns TRUE on success. FALSE on failure.
	//
	// If buffering is disabled, data is written directly to the client.
	// If buffering is enabled, this function attempts to write to the buffer.
	// If the buffer is too small to contain its existing data and the new data,
	// the current contents of the buffer are flushed.
	// If the buffer is still too small to contain the new data, that data is written
	// directly to the client. Otherwise the new data is written to the buffer.
	//
	// Any headers that have been set in the response will be sent just before the 
	// data is written to the client if no headers have been sent up to that point.
	//
	// szOut    A pointer to the first byte of the data to be written.
	//
	// nLen     The number of bytes to write.
	__checkReturn BOOL WriteLen(__in_ecount(dwLen) LPCSTR szOut, __in DWORD dwLen) throw()
	{
		ATLASSUME(m_spServerContext != NULL);
		if (!szOut)
			return FALSE;

		if (m_bBufferOutput)
		{
			if (m_strContent.GetLength()+dwLen >= m_dwBufferLimit)
			{
				if (!Flush())
					return FALSE;
			}
			if (dwLen <= m_dwBufferLimit)
				return m_strContent.Append(szOut, dwLen);
		}
		BOOL bRet = SendHeadersInternal();

		if (bRet && m_bSendOutput)
			bRet = m_spServerContext->WriteClient((void *) szOut, &dwLen);

		return bRet;
	}

	// Call this function to redirect the client to a different resource.
	//
	// Returns TRUE on success, FALSE on failure.
	//
	// szURL        A nul-terminated string specifying the resource the client should navigate to. 
	//
	// statusCode   An HTTP status code from the HTTP_REDIRECT enumeration describing the reason
	//              for the redirection.
	//
	// bSendBody    Specifies whether to generate and send a response body with the headers.
	//
	//  This function allows (and RFC 2616 encourages) a response body to be sent
	//  with the following redirect types:
	//      HTTP_REDIRECT_MOVED
	//      HTTP_REDIRECT_FOUND
	//      HTTP_REDIRECT_SEE_OTHER
	//      HTTP_REDIRECT_TEMPORARY_REDIRECT
	// No body will be sent with other redirect types.
	//
	// The response body contains a short hypertext note with a hyperlink to the new resource.
	// A meta refresh tag is also included to allow browsers to automatically redirect
	// the user to the resource even if they don't understand the redirect header.
	//
	// See RFC 2616 section 10.3 for more information on redirection.
	BOOL Redirect(__in LPCSTR szUrl, __in HTTP_REDIRECT statusCode=HTTP_REDIRECT_MOVED, __in BOOL bSendBody=TRUE) throw(...)
	{
		CStringA strBody;
		LPCSTR szBody = NULL;
		if (bSendBody &&
			(HTTP_REDIRECT_MOVED == statusCode  || HTTP_REDIRECT_FOUND == statusCode ||
			HTTP_REDIRECT_SEE_OTHER == statusCode || HTTP_REDIRECT_TEMPORARY_REDIRECT == statusCode))
		{
			_ATLTRY
			{
			ATLENSURE(szUrl!=NULL);
			strBody.Format(
				"<html>\r\n"
				"<head>\r\n"
				"<meta http-equiv=\"refresh\" content=\"0; url=%s\">\r\n"
				"</head>\r\n"
				"<body>Please use the following link to access this resource:"
				" <a href=\"%s\">%s</a>\r\n"
				"</body>\r\n"
				"</html>\r\n",
				szUrl, szUrl, szUrl);
			}
			_ATLCATCHALL()
			{
				return FALSE;
			}
			szBody = (LPCSTR) strBody;
		}
		return Redirect(szUrl, szBody, statusCode);
	}

	// Call this function to redirect the client to a different resource.
	//
	// Returns TRUE on success, FALSE on failure.
	//
	// szURL        A nul-terminated string specifying the resource the client should navigate to.
	//
	// szBody       A nul-terminated string containing the body of the response to be sent to the client.
	//
	// statusCode   An HTTP status code from the HTTP_REDIRECT enumeration describing the reason
	//              for the redirection.
	//
	//  This function allows (and RFC 2616 encourages) a response body to be sent
	//  with the following redirect types:
	//      HTTP_REDIRECT_MOVED
	//      HTTP_REDIRECT_FOUND
	//      HTTP_REDIRECT_SEE_OTHER
	//      HTTP_REDIRECT_TEMPORARY_REDIRECT
	// No body will be sent with other redirect types.
	//
	// The response body should contain a short hypertext note with a hyperlink to the new resource.
	// You can include a meta refresh tag to allow browsers to automatically redirect
	// the user to the resource even if they don't understand the redirect header.
	//
	// See RFC 2616 section 10.3 for more information on redirection.
	BOOL Redirect(__in LPCSTR szUrl, __in LPCSTR szBody, __in HTTP_REDIRECT statusCode=HTTP_REDIRECT_MOVED) throw()
	{
		SetStatusCode(statusCode);
		AppendHeader("Location", szUrl);

		_ATLTRY
		{
			if (!SendHeadersInternal())
				return FALSE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}

		if (szBody &&
			(HTTP_REDIRECT_MOVED == statusCode  || HTTP_REDIRECT_FOUND == statusCode ||
			HTTP_REDIRECT_SEE_OTHER == statusCode || HTTP_REDIRECT_TEMPORARY_REDIRECT == statusCode))
		{
			Write(szBody);
			return Flush();
		}
		return TRUE;
	}

	// Call this function to append a header to the collection of HTTP headers managed by this object.
	//
	// szName   A nul-teminated string containing the name of the HTTP header.
	//
	// szValue  A nul-teminated string containing the value of the HTTP header.
	BOOL AppendHeader(__in LPCSTR szName, __in_opt LPCSTR szValue) throw()
	{
		ATLASSERT(szName);
		BOOL bRet = FALSE;
		_ATLTRY
		{
			bRet = m_headers.Add(szName, szValue);
		}
		_ATLCATCHALL()
		{
			bRet = FALSE;
		}
		return bRet;
	}

	// Call this function to add a Set-Cookie header to the collection of HTTP headers managed by this object.
	// 
	// pCookie      A pointer to a CCookie object describing the cookie to be sent to the client.
	BOOL AppendCookie(__in const CCookie *pCookie)
	{
		ATLENSURE(pCookie);
		return AppendCookie((const CCookie&)*pCookie);
	}

	// Call this function to add a Set-Cookie header to the collection of HTTP headers managed by this object.
	// 
	// cookie       A reference to a CCookie object describing the cookie to be sent to the client.
	BOOL AppendCookie(__in const CCookie& cookie) throw()
	{
		CHAR szCookie[ATL_MAX_COOKIE_LEN];
		DWORD dwBuffSize = ATL_MAX_COOKIE_LEN;
		BOOL bRet = FALSE;
		bRet = cookie.Render(szCookie, &dwBuffSize);
		if (bRet)
		{
			_ATLTRY
			{
				bRet = m_headers.Add("Set-Cookie", szCookie);
			}
			_ATLCATCHALL()
			{
				bRet = FALSE;
				dwBuffSize = 0;
			}
		}

		if (!bRet && dwBuffSize > 0 && dwBuffSize+1 > dwBuffSize)    //static buffer wasn't big enough.
		{   
			//We'll have to try dynamically allocating it
			//allocate a buffer
			CAutoVectorPtr<CHAR> sz;
			if (sz.Allocate(dwBuffSize+1))
			{
				DWORD dwSizeNew = dwBuffSize + 1;
				if (cookie.Render(sz, &dwSizeNew))
				{
					_ATLTRY
					{
						bRet = m_headers.Add("Set-Cookie", (const char *) sz);
					}
					_ATLCATCHALL()
					{
						bRet = FALSE;
					}
				}
			}
		}
		return bRet;
	}

	// Call this function to add a Set-Cookie header to the collection of HTTP headers managed by this object.
	// 
	// szName       A nul-terminated string containing the name of the cookie to be sent to the client.
	//
	// szValue      A nul-terminated string containing the value of the cookie to be sent to the client.
	BOOL AppendCookie(__in LPCSTR szName, __in_opt LPCSTR szValue) throw()
	{
		ATLASSERT(szName);
		BOOL bRet = FALSE;
		_ATLTRY
		{
			CCookie c(szName, szValue);
			bRet = AppendCookie(c);
		}
		_ATLCATCHALL()
		{
			bRet = FALSE;
		}
		return bRet;
	}

	// Call this function to add a Set-Cookie header that removes a cookie value
	// to the collection of HTTP headers managed by this object.
	// 
	// szName       A nul-terminated string containing the name of the cookie to be deleted
	BOOL DeleteCookie(__in LPCSTR szName) throw()
	{
		ATLASSERT(szName);
		BOOL bRet = FALSE;
		_ATLTRY
		{
			CCookie cookie(szName, "");
			bRet=cookie.SetMaxAge(0);
			if(bRet)
			{
				bRet = AppendCookie(cookie);
			}
		}
		_ATLCATCHALL()
		{
			bRet = FALSE;
		}
		return bRet;

	}

	// Call this function to clear the collection of HTTP response headers maintained by this object.
	//
	// Note that clearing the headers includes removing all cookies associated with the response
	// object. Cookies are sent to the client as Set-Cookie HTTP headers.
	void ClearHeaders() throw()
	{
		m_headers.RemoveAll();
	}

	// Call this function to clear theresponse buffer without sending the contents to the client.
	// If you need to empty the buffer but you do want the current contents sent to the client, call Flush instead. 
	void ClearContent() throw()
	{
		m_strContent.Empty();
	}

	// Call this function to send the current headers associated with this object to the client.
	// 
	// Returns TRUE on success, FALSE on failure.
	//
	// The response headers are sent to the client using the current status code for the
	// response object. See SetStatusCode and GetStatusCode.
	BOOL SendHeadersInternal(__in BOOL fKeepConn=FALSE) 
	{
		if (m_bHeadersSent)
			return TRUE;

		ATLENSURE(m_spServerContext != NULL);

		CStringA strHeaders;
		RenderHeaders(strHeaders);

		BOOL bRet = FALSE;
		_ATLTRY
		{
			if (m_nStatusCode == 200)
			{
				bRet = m_spServerContext->SendResponseHeader(strHeaders, "200 OK", fKeepConn);
				if (bRet)
				{
					m_bHeadersSent = TRUE;
				}
				return bRet;
			}

			CFixedStringT<CStringA, 256> strStatus;
			CDefaultErrorProvider prov;
			GetStatusHeader(strStatus, m_nStatusCode, SUBERR_NONE, &prov);
			bRet = m_spServerContext->SendResponseHeader(strHeaders, strStatus, fKeepConn);
			if (bRet)
			{
				m_bHeadersSent = TRUE;
			}
		}
		_ATLCATCHALL()
		{
			bRet = FALSE;
		}
		return bRet;
	}

	// Call this function to get a string containing all the HTTP headers associated with
	// this object in a format suitable for sending to a client.
	//
	// strHeaders   A CStringA reference to which will be appended the HTTP headers.
	void RenderHeaders(CStringA& strHeaders) throw()
	{
		_ATLTRY
		{
			strHeaders.Preallocate(::ATL::AtlMultiplyThrow(m_headers.GetSize(),64));
			for (int i=0; i<m_headers.GetSize(); i++)
			{
				strHeaders += m_headers.GetKeyAt(i);
				strHeaders.Append(": ", sizeof(": ")-1);
				strHeaders += m_headers.GetValueAt(i);
				strHeaders.Append("\r\n", sizeof("\r\n")-1);
			}
			strHeaders.Append("\r\n", sizeof("\r\n")-1);
		}
		_ATLCATCHALL()
		{
		}
	}

	// Call this function to empty the response buffer and send its current
	// contents to the client.
	//
	// Returns S_OK on success, or an error HRESULT on failure.
	HRESULT FlushStream()
	{
		if (!Flush())
			return AtlHresultFromLastError();
		return S_OK;
	}

	// Call this function to empty the response buffer and send its current
	// contents to the client.
	//
	// Returns TRUE on success, or FALSE on failure.
	//
	// Any headers that have been set in the response will be sent just before the 
	// data is written to the client if no headers have been sent up to that point.
	BOOL Flush(BOOL bFinal=FALSE) throw()
	{
		if (!m_spServerContext)
			return FALSE;

		BOOL bRet = TRUE;

		_ATLTRY
		{
			// if the headers haven't been sent,
			// send them now

			if (!m_bHeadersSent)
			{
				char szProtocol[ATL_URL_MAX_URL_LENGTH];
				DWORD dwProtocolLen = sizeof(szProtocol);

				if (bFinal && m_bBufferOutput && m_dwBufferLimit==ULONG_MAX)
				{
					if (m_spServerContext->GetServerVariable("SERVER_PROTOCOL", szProtocol, &dwProtocolLen) &&
						!strcmp(szProtocol, "HTTP/1.0"))
						AppendHeader("Connection", "Keep-Alive");
					Checked::itoa_s(m_strContent.GetLength(), szProtocol, _countof(szProtocol), 10);
					AppendHeader("Content-Length", szProtocol);
					bRet = SendHeadersInternal(TRUE);
				}
				else
					bRet = SendHeadersInternal();
			}
			if (m_bBufferOutput)
			{
				DWORD dwLen = 0;

				dwLen = m_strContent.GetLength();
				if (dwLen)
				{
					if (m_bSendOutput && 
						m_spServerContext->WriteClient((void *) (LPCSTR) m_strContent, &dwLen) != TRUE)
					{
						m_strContent.Empty();
						return FALSE;
					}
					m_strContent.Empty();
				}
			}
		} // _ATLTRY
		_ATLCATCHALL()
		{
			bRet = FALSE;
		}
		return bRet;
	}

	// Call this function to clear the response object of any headers
	// and the contents of the buffer.
	void ClearResponse() throw()
	{
		m_strContent.Empty();
		m_headers.RemoveAll();
	}

	BOOL AsyncPrep(__in BOOL fKeepConn=FALSE) throw()
	{
		ATLASSUME(m_spServerContext != NULL);

		return SendHeadersInternal(fKeepConn);
	}

	BOOL AsyncFlush() throw()
	{
		ATLASSUME(m_spServerContext != NULL);

		BOOL bRet = SendHeadersInternal();

		if (bRet && m_bBufferOutput)
		{
			DWORD dwLen = 0;

			dwLen = m_strContent.GetLength();
			if (dwLen)
			{
				_ATLTRY
				{
					if (m_spServerContext->AsyncWriteClient((void *) (LPCSTR) m_strContent, &dwLen) != TRUE)
					{
						bRet = FALSE;
					}
				}
				_ATLCATCHALL()
				{
					bRet = FALSE;
				}
			}
		}

		return bRet;
	}

	BOOL TransmitFile(__in HANDLE hFile, __in LPCSTR szContentType="text/plain") throw()
	{
		ATLASSUME(m_spServerContext != NULL);
		ATLASSERT(hFile != NULL && hFile != INVALID_HANDLE_VALUE);

		SetContentType(szContentType);

		if (m_strContent.GetLength())
			if (!Flush())
				return FALSE;

		BOOL bRet = SendHeadersInternal();
		if (bRet)
		{
			_ATLTRY
			{
				bRet = m_spServerContext->TransmitFile(hFile, NULL, NULL, NULL, 
					0, 0, NULL, 0, NULL, 0, HSE_IO_ASYNC | HSE_IO_NODELAY);
			}
			_ATLCATCHALL()
			{
				bRet = FALSE;
			}
		}

		return bRet;
	}
}; // class CHttpResponse



#define ATLS_FLAG_NONE      0
#define ATLS_FLAG_ASYNC     1   // handler might do some async handling

// push_macro/pop_macro doesn't work in a template definition.
#pragma push_macro("new")
#undef new
template <class T>
class PerThreadWrapper : public CComObjectNoLock<T>
{
public:
	void *operator new(size_t /*size*/, void *p) throw()
	{
		return p;
	}

	void operator delete(void * /*p*/) throw()
	{
	}

	STDMETHOD_(ULONG, Release)() throw()
	{
		ULONG l = InternalRelease();
		if (l == 0)
		{
			T *pT = static_cast<T*>(this);
			ATLASSERT(pT->m_spExtension != NULL);
			CIsapiWorker *pWorker = pT->m_spExtension->GetThreadWorker();
			ATLASSERT(pWorker);

			delete this;
			if(pWorker)
			{
				HeapFree(pWorker->m_hHeap, HEAP_NO_SERIALIZE, this);
			}
		}
		return l;
	}
};

template <typename THandler>
inline BOOL CreateRequestHandlerSync(__in IIsapiExtension *pExtension, __deref_out_opt IUnknown **ppOut) 
{
	ATLENSURE(pExtension);
	ATLENSURE(ppOut);

	CIsapiWorker *pWorker = pExtension->GetThreadWorker();
	ATLENSURE(pWorker);
	void *pv = HeapAlloc(pWorker->m_hHeap, HEAP_NO_SERIALIZE, sizeof(PerThreadWrapper<THandler>));
	if (!pv)
		return FALSE;

	PerThreadWrapper<THandler> *pHandler = new(pv) PerThreadWrapper<THandler>;
	*ppOut = static_cast<IRequestHandler *>(pHandler);
	pHandler->m_spExtension = pExtension;

	(*ppOut)->AddRef();

	return TRUE;
}
#pragma pop_macro("new")

#define DECLARE_ASYNC_HANDLER() \
	static DWORD GetHandlerFlags() \
	{ \
		return ATLS_FLAG_ASYNC; \
	} \
	DWORD GetAsyncFlags() \
	{ \
		return ATLSRV_INIT_USEASYNC; \
	}

#define DECLARE_ASYNC_HANDLER_EX() \
	static DWORD GetHandlerFlags() \
	{ \
		return ATLS_FLAG_ASYNC; \
	} \
	DWORD GetAsyncFlags() \
	{ \
		return (ATLSRV_INIT_USEASYNC|ATLSRV_INIT_USEASYNC_EX); \
	}


template <typename THandler>
class IRequestHandlerImpl : public IRequestHandler
{
public:
	HINSTANCE m_hInstHandler;
	CComPtr<IServiceProvider> m_spServiceProvider;
	CComPtr<IHttpServerContext> m_spServerContext;
	CComPtr<IIsapiExtension> m_spExtension;
	DWORD m_dwAsyncFlags;

	IRequestHandlerImpl()
		:m_hInstHandler(NULL)
	{
		m_dwAsyncFlags = 0;
	}
	virtual ~IRequestHandlerImpl()
	{
	}

	HTTP_CODE GetFlags(__out DWORD *pdwStatus)
	{
		ATLASSERT(pdwStatus);
		THandler *pT = static_cast<THandler *>(this);

		*pdwStatus = pT->GetAsyncFlags();
		if (pT->CachePage())
			*pdwStatus |= ATLSRV_INIT_USECACHE;

#ifdef _DEBUG
		if (*pdwStatus & (ATLSRV_INIT_USEASYNC|ATLSRV_INIT_USEASYNC_EX))
			ATLASSERT(pT->GetHandlerFlags() & ATLS_FLAG_ASYNC);
#endif

		return HTTP_SUCCESS;
	}

	HTTP_CODE InitializeHandler(__in AtlServerRequest *pRequestInfo, __in IServiceProvider *pProvider)
	{
		ATLENSURE(pRequestInfo != NULL);
		ATLENSURE(pProvider != NULL);
		ATLENSURE(pRequestInfo->hInstDll != NULL);
		ATLENSURE(pRequestInfo->pServerContext != NULL);

		// Initialize our internal references to required services
		m_hInstHandler = pRequestInfo->hInstDll;
		m_spServiceProvider = pProvider;
		m_spServerContext = pRequestInfo->pServerContext;

		return HTTP_SUCCESS;
	}

	HTTP_CODE InitializeChild(__in AtlServerRequest *pRequestInfo, __in IServiceProvider *pProvider, IHttpRequestLookup * /*pLookup*/)
	{
		return InitializeHandler(pRequestInfo, pProvider);
	}

	void UninitializeHandler()
	{
	}

	HTTP_CODE HandleRequest(
		AtlServerRequest* /*pRequestInfo*/,
		IServiceProvider* /*pServiceProvider*/)
	{
		return HTTP_SUCCESS;
	}

	DWORD GetAsyncFlags()
	{
		return m_dwAsyncFlags;
	}

	void SetAsyncFlags(__in DWORD dwAsyncFlags)
	{
		ATLASSERT((dwAsyncFlags & ~(ATLSRV_INIT_USEASYNC|ATLSRV_INIT_USEASYNC_EX)) == 0);
		m_dwAsyncFlags = dwAsyncFlags;
	}

	BOOL CachePage()
	{
		return FALSE;
	}

	static DWORD GetHandlerFlags()
	{
		return ATLS_FLAG_NONE;
	}

	// Used to create new instance of this object. A pointer to this
	// function is stored in the handler map in user's code.
	static BOOL CreateRequestHandler(__in IIsapiExtension *pExtension, __deref_out_opt IUnknown **ppOut)
	{
		ATLASSERT(ppOut != NULL);
		if (ppOut == NULL)
			return false;

		*ppOut = NULL;

		if (THandler::GetHandlerFlags() & ATLS_FLAG_ASYNC)
		{
			THandler *pHandler = NULL;
			ATLTRY(pHandler = new CComObjectNoLock<THandler>);
			if (!pHandler)
				return FALSE;
			*ppOut = static_cast<IRequestHandler *>(pHandler);
			pHandler->m_spExtension = pExtension;
			(*ppOut)->AddRef();
		}
		else
		{
			if (!CreateRequestHandlerSync<THandler>(pExtension, ppOut))
				return FALSE;
		}

		return TRUE;
	}

	// Used to initialize the class
	// function is stored in the handler map in user's code.
	static BOOL InitRequestHandlerClass(IHttpServerContext *pContext, IIsapiExtension *pExt)
	{
		(pContext); // unused
		(pExt); // unused
		return TRUE;
	}

	// Used to uninitialize the class
	// function is stored in the handler map in user's code.
	static void UninitRequestHandlerClass()
	{
		return;
	}
};

struct CRequestStats
{
	long m_lTotalRequests;
	long m_lFailedRequests;
	__int64 m_liTotalResponseTime;
	long m_lAvgResponseTime;
	long m_lCurrWaiting;
	long m_lMaxWaiting;
	long m_lActiveThreads;

	CRequestStats() throw()
	{
		m_lTotalRequests = 0;
		m_lFailedRequests = 0;
		m_liTotalResponseTime = 0;
		m_lAvgResponseTime = 0;
		m_lCurrWaiting = 0;
		m_lMaxWaiting = 0;
		m_lActiveThreads = 0;
	}

	void RequestHandled(__in AtlServerRequest *pRequestInfo, __in BOOL bSuccess)
	{
		ATLENSURE(pRequestInfo);

		InterlockedIncrement(&m_lTotalRequests);
		if (!bSuccess)
			InterlockedIncrement(&m_lFailedRequests);

		long lTicks;

#ifndef ATL_NO_MMSYS
		lTicks = (long) (timeGetTime() - pRequestInfo->dwStartTicks);
#else
		lTicks = (long) (GetTickCount() - pRequestInfo->dwStartTicks);
#endif
		__int64 liTotalResponseTime = Add64(&m_liTotalResponseTime, lTicks);
		long lAv = (long) (liTotalResponseTime / m_lTotalRequests);
		InterlockedExchange(&m_lAvgResponseTime, lAv);

		InterlockedDecrement(&m_lActiveThreads);
	}

	long GetTotalRequests() throw()
	{
		return m_lTotalRequests;
	}

	long GetFailedRequests() throw()
	{
		return m_lFailedRequests;
	}

	long GetAvgResponseTime() throw()
	{
		return m_lAvgResponseTime;
	}

	void OnRequestReceived() throw()
	{
		long nCurrWaiting = InterlockedIncrement(&m_lCurrWaiting);
		AtlInterlockedUpdateMax(nCurrWaiting, &m_lMaxWaiting);
	}

	void OnRequestDequeued() throw()
	{
		InterlockedDecrement(&m_lCurrWaiting);
		InterlockedIncrement(&m_lActiveThreads);
	}

	long GetCurrWaiting() throw()
	{
		return m_lCurrWaiting;
	}

	long GetMaxWaiting() throw()
	{
		return m_lMaxWaiting;
	}

	long GetActiveThreads() throw()
	{
		return m_lActiveThreads;
	}

private:
	// not actually atomic, but it will add safely.

	// the returned value is not 100% guaranteed to be
	// correct, but should be correct more often than
	// just reading the __int64
	// the 2 cases where the return value will not be
	// a valid result value from an add are:
	// * multiple threads wrap the low part in rapid succession
	// * different threads are adding values with different signs

	// this is good enough for our use in RequestHandled -
	// we always add positive values and we shouldn't wrap 32-bits often
	inline __int64 Add64(__inout __int64* pn, long nAdd)
	{
		ATLENSURE(pn != NULL);

#if defined(_WIN64) && defined(_M_CEE)

		// Use System::Threading::Interlocked::Add because InterlockedExchangeAdd is an intrisinc not supported in managed code
		// with 64bits compilers.
		// Also, System::Threading::Interlocked::Add support 64bits operands.
		return System::Threading::Interlocked::Add(*pn, nAdd);

#else

		long* pnLow = (long*)(LPBYTE(pn) + offsetof(LARGE_INTEGER, LowPart));
		long* pnHigh = (long*)(LPBYTE(pn) + offsetof(LARGE_INTEGER, HighPart));

		long nOrigLow = InterlockedExchangeAdd(pnLow, nAdd);
		long nNewLow = nOrigLow + nAdd;
		long nNewHigh = *pnHigh;
		if (nAdd > 0 && nNewLow < nOrigLow)
			nNewHigh = InterlockedIncrement(pnHigh);
		else if (nAdd < 0 && nNewLow > nOrigLow)
			nNewHigh = InterlockedDecrement(pnHigh);

		LARGE_INTEGER li;
		li.LowPart = nNewLow;
		li.HighPart = nNewHigh;
		return li.QuadPart;

#endif
	}
};

class CStdRequestStats : public CRequestStats
{

public:
	HRESULT Initialize() throw()
	{
		return S_OK;
	}

	void Uninitialize() throw()
	{
	}
};

#define PERF_REQUEST_OBJECT 100
 
struct CPerfRequestStatObject : public CPerfObject,
	public CRequestStats
{
	DECLARE_PERF_CATEGORY_EX(PERF_REQUEST_OBJECT, IDS_PERFMON_REQUEST, IDS_PERFMON_REQUEST_HELP, PERF_DETAIL_NOVICE, 0, sizeof(CPerfRequestStatObject), MAX_PATH, -1);
	BEGIN_COUNTER_MAP(CPerfRequestStatObject)
		DEFINE_COUNTER(m_lTotalRequests, IDS_PERFMON_REQUEST_TOTAL, IDS_PERFMON_REQUEST_TOTAL_HELP, PERF_COUNTER_RAWCOUNT, -1)
		DEFINE_COUNTER(m_lFailedRequests, IDS_PERFMON_REQUEST_FAILED, IDS_PERFMON_REQUEST_FAILED_HELP, PERF_COUNTER_RAWCOUNT, -1)
		DEFINE_COUNTER(m_lTotalRequests, IDS_PERFMON_REQUEST_RATE, IDS_PERFMON_REQUEST_RATE_HELP, PERF_COUNTER_COUNTER, -1)
		DEFINE_COUNTER(m_lAvgResponseTime, IDS_PERFMON_REQUEST_AVG_RESPONSE_TIME, IDS_PERFMON_REQUEST_AVG_RESPONSE_TIME_HELP, PERF_COUNTER_RAWCOUNT, -1)
		DEFINE_COUNTER(m_lCurrWaiting, IDS_PERFMON_REQUEST_CURR_WAITING, IDS_PERFMON_REQUEST_CURR_WAITING_HELP, PERF_COUNTER_RAWCOUNT, -1)
		DEFINE_COUNTER(m_lMaxWaiting, IDS_PERFMON_REQUEST_MAX_WAITING, IDS_PERFMON_REQUEST_MAX_WAITING_HELP, PERF_COUNTER_RAWCOUNT, -1)
		DEFINE_COUNTER(m_lActiveThreads, IDS_PERFMON_REQUEST_ACTIVE_THREADS, IDS_PERFMON_REQUEST_ACTIVE_THREADS, PERF_COUNTER_RAWCOUNT, -1)
	END_COUNTER_MAP()
};

class CRequestPerfMon : public CPerfMon
{
public:
	BEGIN_PERF_MAP(_T("ATL Server:Request"))
		CHAIN_PERF_CATEGORY(CPerfRequestStatObject)
	END_PERF_MAP()
};

class CPerfMonRequestStats
{
	CRequestPerfMon m_PerfMon;
	CPerfRequestStatObject * m_pPerfObjectInstance;
	CPerfRequestStatObject * m_pPerfObjectTotal;

public:
	CPerfMonRequestStats() throw()
	{
		m_pPerfObjectInstance = NULL;
		m_pPerfObjectTotal = NULL;
	}

	HRESULT Initialize() throw()
	{
		HRESULT hr;

		m_pPerfObjectInstance = NULL;
		m_pPerfObjectTotal = NULL;

		hr = m_PerfMon.Initialize();
		if (SUCCEEDED(hr))
		{
			CPerfLock lock(&m_PerfMon);
			if (FAILED(hr = lock.GetStatus()))
			{
				return hr;
			}

			HINSTANCE hInst = _AtlBaseModule.GetModuleInstance();
			WCHAR szName[MAX_PATH];
			if (GetModuleFileNameW(hInst, szName, MAX_PATH) == 0)
			{
				return E_FAIL;
			}
			szName[MAX_PATH-1] = 0;

			hr = m_PerfMon.CreateInstanceByName(L"_Total", &m_pPerfObjectTotal);
			if (FAILED(hr))
			{
				return hr;
			}

			hr = m_PerfMon.CreateInstanceByName(szName, &m_pPerfObjectInstance);
			if (FAILED(hr))
			{
				m_PerfMon.ReleaseInstance(m_pPerfObjectTotal);
				m_pPerfObjectTotal = NULL;
				return hr;
			}
		}

		return hr;
	}

	void Uninitialize() throw()
	{
		if (m_pPerfObjectInstance != NULL)
			m_PerfMon.ReleaseInstance(m_pPerfObjectInstance);
		if (m_pPerfObjectTotal != NULL)
			m_PerfMon.ReleaseInstance(m_pPerfObjectTotal);

		m_pPerfObjectInstance = NULL;
		m_pPerfObjectTotal = NULL;

		m_PerfMon.UnInitialize();
	}

	void RequestHandled(__in AtlServerRequest *pRequestInfo, __in BOOL bSuccess) throw()
	{
		if (m_pPerfObjectInstance != NULL)
			m_pPerfObjectInstance->RequestHandled(pRequestInfo, bSuccess);
		if (m_pPerfObjectTotal != NULL)
			m_pPerfObjectTotal->RequestHandled(pRequestInfo, bSuccess);
	}

	long GetTotalRequests() throw()
	{
		if (m_pPerfObjectInstance != NULL)
			return m_pPerfObjectInstance->GetTotalRequests();

		return 0;
	}

	long GetFailedRequests() throw()
	{
		if (m_pPerfObjectInstance != NULL)
			return m_pPerfObjectInstance->GetFailedRequests();

		return 0;
	}

	long GetAvgResponseTime() throw()
	{
		if (m_pPerfObjectInstance != NULL)
			return m_pPerfObjectInstance->GetAvgResponseTime();

		return 0;
	}

	void OnRequestReceived() throw()
	{
		if (m_pPerfObjectInstance != NULL)
			m_pPerfObjectInstance->OnRequestReceived();
		if (m_pPerfObjectTotal != NULL)
			m_pPerfObjectTotal->OnRequestReceived();
	}

	void OnRequestDequeued() throw()
	{
		if (m_pPerfObjectInstance != NULL)
			m_pPerfObjectInstance->OnRequestDequeued();
		if (m_pPerfObjectTotal != NULL)
			m_pPerfObjectTotal->OnRequestDequeued();
	}

	long GetCurrWaiting() throw()
	{
		if (m_pPerfObjectInstance != NULL)
			return m_pPerfObjectInstance->GetCurrWaiting();

		return 0;
	}

	long GetMaxWaiting() throw()
	{
		if (m_pPerfObjectInstance != NULL)
			return m_pPerfObjectInstance->GetMaxWaiting();

		return 0;
	}

	long GetActiveThreads() throw()
	{
		if (m_pPerfObjectInstance != NULL)
			return m_pPerfObjectInstance->GetActiveThreads();

		return 0;
	}
};

class CNoRequestStats
{
protected:

public:

	HRESULT Initialize() throw()
	{
		return S_OK;
	}

	void Uninitialize() throw()
	{
	}

	void RequestHandled(AtlServerRequest * /*pRequestInfo*/, BOOL /*bSuccess*/) throw()
	{
	}

	long GetTotalRequests() throw()
	{
		return 0;
	}

	long GetFailedRequests() throw()
	{
		return 0;
	}

	long GetAvgResponseTime() throw()
	{
		return 0;
	}

	void OnRequestReceived() throw()
	{
	}

	void OnRequestDequeued() throw()
	{
	}

	long GetCurrWaiting() throw()
	{
		return 0;
	}

	long GetMaxWaiting() throw()
	{
		return 0;
	}

	long GetActiveThreads() throw()
	{
		return 0;
	}
};

struct ATLServerDllInfo
{
	GETATLHANDLERBYNAME     pfnGetHandler;
	UNINITIALIZEATLHANDLERS pfnUninitHandlers;
	INITIALIZEATLHANDLERS pfnInitHandlers;
	IIsapiExtension *pExtension;
	IHttpServerContext *pContext;
};

class CDllCachePeer
{
public:
	struct DllInfo : public ATLServerDllInfo
	{
		DllInfo& operator=(__in const DllInfo& right) throw()
		{
			if (this != &right)
			{
	  		  	pfnGetHandler = right.pfnGetHandler;
	   			pfnUninitHandlers = right.pfnUninitHandlers;
				pfnInitHandlers = right.pfnInitHandlers;
		 	   	pExtension = right.pExtension;
		  	 	pContext = right.pContext;
			}
			return *this;
		}
	};

	BOOL Add(__in HINSTANCE hInst, __out DllInfo *pInfo) throw(...)
	{
		ATLENSURE(pInfo!=NULL);
		pInfo->pfnInitHandlers = (INITIALIZEATLHANDLERS) GetProcAddress(hInst, ATLS_FUNCID_INITIALIZEHANDLERS);

		pInfo->pfnGetHandler = (GETATLHANDLERBYNAME) GetProcAddress(hInst, ATLS_FUNCID_GETATLHANDLERBYNAME);
		if (!pInfo->pfnGetHandler)
			return FALSE;

		pInfo->pfnUninitHandlers = (UNINITIALIZEATLHANDLERS) GetProcAddress(hInst, ATLS_FUNCID_UNINITIALIZEHANDLERS);

		if (pInfo->pfnInitHandlers)
		{
			pInfo->pfnInitHandlers(pInfo->pContext, pInfo->pExtension);
			pInfo->pContext = NULL; // won't be valid after this call
		}

		return TRUE;
	}

	void Remove(HINSTANCE /*hInst*/, __in DllInfo *pInfo) throw(...)
	{
		ATLENSURE(pInfo!=NULL);
		if (pInfo->pfnUninitHandlers)
			(*pInfo->pfnUninitHandlers)();
	}

};

inline bool operator==(__in const CDllCachePeer::DllInfo& left, __in const CDllCachePeer::DllInfo& right) throw()
{
	return ( (left.pfnGetHandler == right.pfnGetHandler) &&
			 (left.pfnUninitHandlers == right.pfnUninitHandlers) &&
			 (left.pfnInitHandlers == right.pfnInitHandlers) &&
			 (left.pExtension == right.pExtension) &&
			 (left.pContext == right.pContext)
		   );
}



// Helper function to impersonate the client
// on the current thread
inline BOOL AtlImpersonateClient(__in IHttpServerContext *pServerContext)
{
	ATLENSURE(pServerContext);

	// impersonate the calling client on the current thread
	HANDLE hToken;
	_ATLTRY
	{
		if (!pServerContext->GetImpersonationToken(&hToken))
			return FALSE;
	}
	_ATLCATCHALL()
	{
		return FALSE;
	}

	if (!SetThreadToken(NULL, hToken))
		return FALSE;
	return TRUE;
}

// Helper class to set the thread impersonation token
// This is mainly used internally to ensure that we
// don't forget to revert to the process impersonation
// level
class CSetThreadToken
{
public:
	CSetThreadToken() : m_bShouldRevert(FALSE) {}

	BOOL Initialize(__in AtlServerRequest *pRequestInfo)
	{
		ATLENSURE(pRequestInfo);
		m_bShouldRevert = AtlImpersonateClient(pRequestInfo->pServerContext);
		return m_bShouldRevert;
	}

	~CSetThreadToken() throw()
	{
		if( m_bShouldRevert && !RevertToSelf() )
		{
			_AtlRaiseException( (DWORD)EXCEPTION_ACCESS_VIOLATION );
		}
	}
protected:
	BOOL m_bShouldRevert;
};


// push_macro/pop_macro doesn't work in a template definition.
#pragma push_macro("new")
#undef new


//Base is the user's class that derives from CComObjectRoot and whatever
//interfaces the user wants to support on the object
template <class Base>
class _CComObjectHeapNoLock : public Base
{
	public:
	typedef Base _BaseClass;
	HANDLE m_hHeap;

	_CComObjectHeapNoLock(void* = NULL, HANDLE hHeap = NULL)
	{
		m_hHeap = hHeap;
	}
	// Set refcount to -(LONG_MAX/2) to protect destruction and 
	// also catch mismatched Release in debug builds
	~_CComObjectHeapNoLock()
	{
		m_dwRef = -(LONG_MAX/2);
		FinalRelease();
#ifdef _ATL_DEBUG_INTERFACES
		_AtlDebugInterfacesModule.DeleteNonAddRefThunk(_GetRawUnknown());
#endif
	}

	//If InternalAddRef or InternalRelease is undefined then your class
	//doesn't derive from CComObjectRoot
	STDMETHOD_(ULONG, AddRef)() throw() {return InternalAddRef();}
	STDMETHOD_(ULONG, Release)() throw()
	{
		ULONG l = InternalRelease();
		if (l == 0)
		{
			HANDLE hHeap = m_hHeap;;
			this->~_CComObjectHeapNoLock();
			if (hHeap != NULL)
			{
				HeapFree(hHeap, 0, this);
			}
		}
		return l;
	}
	//if _InternalQueryInterface is undefined then you forgot BEGIN_COM_MAP
	STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) throw()
	{return _InternalQueryInterface(iid, ppvObject);}

	static HRESULT WINAPI CreateInstance(_CComObjectHeapNoLock<Base>** pp, HANDLE hHeap) throw();	
};



template <class Base>
HRESULT WINAPI _CComObjectHeapNoLock<Base>::CreateInstance(__deref_out _CComObjectHeapNoLock<Base>** pp, __in HANDLE hHeap) throw()
{
	ATLASSERT(pp != NULL);
	if (pp == NULL)
		return E_POINTER;
	*pp = NULL;

	HRESULT hRes = E_OUTOFMEMORY;
	// Allocate a fixed block size to avoid fragmentation
	void *pv = HeapAlloc(hHeap, HEAP_ZERO_MEMORY,
		__max(sizeof(AtlServerRequest), sizeof(_CComObjectHeapNoLock<CServerContext>)));
	if (pv == NULL)
	{
		return hRes;
	}
#pragma warning(push)
#pragma warning(disable: 6280)
	/* prefast noise VSW 493229 */
	_CComObjectHeapNoLock<Base>* p = new(pv) _CComObjectHeapNoLock<CServerContext>(NULL, hHeap);
#pragma warning(pop)

	p->SetVoid(NULL);
	p->InternalFinalConstructAddRef();
	hRes = p->_AtlInitialConstruct();
	if (SUCCEEDED(hRes))	
		hRes = p->FinalConstruct();
	if (SUCCEEDED(hRes))
		hRes = p->_AtlFinalConstruct();
	p->InternalFinalConstructRelease();
	if (hRes != S_OK)
	{
		p->~_CComObjectHeapNoLock();	
#pragma warning(push)
#pragma warning(disable: 6280)
		/* prefast noise VSW 493229 */
		HeapFree(hHeap, 0, p);		
#pragma warning(pop)
		p = NULL;
	}
	*pp = p;
	return hRes;
}

inline CServerContext* CreateServerContext(__in HANDLE hRequestHeap) throw()
{
	_CComObjectHeapNoLock<CServerContext>* pContext;
	_CComObjectHeapNoLock<CServerContext>::CreateInstance(&pContext, hRequestHeap);
	return pContext;
}
#pragma pop_macro("new")

// _AtlGetHandlerName
// get handler name from stencil file. Ignore all server side comments
//  szFileName - the file from which to extract the handler name
//  szHandlerName - buffer into which handler name will be copied,
//       it is assumed to be of size MAX_PATH+ATL_MAX_HANDLER_NAME+2
inline HTTP_CODE _AtlGetHandlerName(__in LPCSTR szFileName, __out_ecount(MAX_PATH+ATL_MAX_HANDLER_NAME+2) LPSTR szHandlerName)
{
	ATLASSERT(szFileName);
	ATLENSURE(szHandlerName);

	szHandlerName[0] = '\0';
	CAtlFile cfFile;
	HRESULT hr;

	_ATLTRY
	{
		hr = cfFile.Create(CA2CTEX<MAX_PATH>(szFileName), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING);
		if (FAILED(hr) || cfFile.m_h == NULL || GetFileType(cfFile.m_h) != FILE_TYPE_DISK)
		{
			if (hr == AtlHresultFromWin32(ERROR_FILE_NOT_FOUND))
				return HTTP_NOT_FOUND;
			else if (hr == AtlHresultFromWin32(ERROR_ACCESS_DENIED))
				return HTTP_UNAUTHORIZED;
			else
				return AtlsHttpError(500, IDS_ATLSRV_SERVER_ERROR_STENCILLOADFAIL);
		}
	}
	_ATLCATCHALL()
	{
		return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM); // CA2CTEX threw
	}

	HTTP_CODE hcErr = HTTP_SUCCESS;
	DWORD dwRead=0;
	LPCSTR szHandler = "handler";
	LPCSTR pszHandlerPos = NULL;
	LPSTR pszHandlerName = szHandlerName;
	char szBuf[4097];
	LPSTR szCurly = NULL;
	LPSTR pszBuf = NULL;
	bool bInQuote = false;

	// state:
	//  0 = default/unknown
	//  1 = have "{"
	//  2 = have "{{" -- skip spaces
	//  3 = have "{{" -- check "handler"
	//  4 = have "handler" -- skip spaces
	//  5 = have "handler" -- get name
	//  6 = scan until first '}'
	//  7 = better be '}'
	//  8 = done
	int nState = 0;

	do
	{
		hr = cfFile.Read(szBuf, _countof(szBuf)-1, dwRead);
		if (hr != S_OK)
		{
			return AtlsHttpError(500, ISE_SUBERR_READFILEFAIL); // failed reading
		}

		szBuf[dwRead] = '\0';
		pszBuf = szBuf;

		while (*pszBuf && nState != 8)
		{
			switch (nState)
			{
			case 0: 
				//  0 = default/unknown

				// look for the first curly
				szCurly = strchr(pszBuf, '{');
				if (!szCurly)
				{
					// skip to the end of the buffer
					pszBuf = szBuf+dwRead-1;
				}
				else
				{
					pszBuf = szCurly;
					nState = 1;
				}
				break;
			case 1:
				//  1 = have "{"
				if (*pszBuf == '{') 
				{
					nState = 2;
				}
				else
				{
					nState = 0; // if the next character is not a '{', start over
				}
				break;
			case 2:
				if (!isspace(static_cast<unsigned char>(*pszBuf)))
				{
					pszHandlerPos = szHandler;
					pszBuf--;
					nState = 3;
				}
				break;
			case 3:
				//  3 = partial handler "h..." 
				if (*pszBuf != *pszHandlerPos)
				{
					// not a handler, skip tag
					nState = 6;
				}
				else
				{
					pszHandlerPos++;
					if (!*pszHandlerPos) // at the end of the "handler" part
						nState = 4;
				}
				break;
			case 4:
				//  4 = have "handler" -- skip spaces
				if (!isspace(static_cast<unsigned char>(*pszBuf)))
				{
					if (*pszBuf == '\"')
					{
						bInQuote = true;
					}
					else
					{
						pszBuf--;
					}
					nState = 5;
				}
				break;
			case 5:
				//  5 = have "handler" -- get name
				if (isspace(static_cast<unsigned char>(*pszBuf)) && !bInQuote)
				{
					if (*(pszHandlerName-1) != '/')
					{
						// end of the name -- jump to getting the first '}'
						nState = 6;
					}
					else
					{
						nState = 4;
					}
				}
				else if (*pszBuf == '}')
				{
					// end of the name -- jump to getting the second '}'
					nState = 7;
				}
				else if (*pszBuf == '\"')
				{
					if (bInQuote)
					{
						bInQuote = false;
					}
					else
					{
						hcErr = HTTP_FAIL;
						nState = 8;
					}
				}
				else
				{
					// ensure we don't overwrite the buffer
					if (pszHandlerName-szHandlerName >= MAX_PATH+ATL_MAX_HANDLER_NAME_LEN+1)
					{
						hcErr =  HTTP_FAIL;
						nState = 8;
					}
					else
					{
						*pszHandlerName++ = *pszBuf;
					}
				}
				break;
			case 6:
				//  6 = scan until first '}'
				if (*pszBuf == '}')
					nState = 7;
				break;
			case 7:
				//  7 = better be '}'
				if (*pszBuf != '}')
				{
					hcErr = AtlsHttpError(500, ISE_SUBERR_BAD_HANDLER_TAG);
					nState = 8;
				}
				if (*szHandlerName)
					nState = 8;
				else
					nState = 0;
				break;
			default:
				ATLASSERT(FALSE);
				return HTTP_INTERNAL_SERVER_ERROR;
			}

			pszBuf++;
		}
	} while (dwRead != 0 && nState != 8);

	*pszHandlerName = '\0';

	return hcErr;
}

// _AtlCrackHandler cracks a request path of the form dll_path/handler_name into its
// constituent parts
// szHandlerDllName - the full handler path of the form "dll_path/handler_name"
// szDllPath - the DLL path (should be of length MAX_PATH)
// szHandlerName - the handler name (should be of length ATL_MAX_HANDLER_NAME_LEN+1)
//
inline BOOL _AtlCrackHandler(
	__in_z LPCSTR szHandlerDllName,
	__out_ecount_part(*pdwszDllPathLen,*pdwszDllPathLen) LPSTR szDllPath,
	__inout LPDWORD pdwDllPathLen,
	__out_ecount_part(*pdwHandlerNameLen,*pdwHandlerNameLen) LPSTR szHandlerName,
	__inout LPDWORD pdwHandlerNameLen)
{
	ATLENSURE( szHandlerDllName != NULL );
	ATLASSERT( szDllPath != NULL );
	ATLENSURE( pdwDllPathLen != NULL );
	ATLASSERT( szHandlerName != NULL );
	ATLASSERT( pdwHandlerNameLen != NULL );

	BOOL bRet = TRUE;

	// skip leading spaces
	while (*szHandlerDllName && isspace(static_cast<unsigned char>(*szHandlerDllName)))
		++szHandlerDllName;

	// get the handler name
	LPCSTR szSlash = strchr(szHandlerDllName, '/');
	LPCSTR szEnd = NULL;
	LPCSTR szSlashEnd = NULL;

	// if it is of the form <dll_name>/<handler_name>
	if (szSlash)
	{
		szEnd = szSlash;

		// skip trailing spaces on <dll_name>
		while (szEnd>szHandlerDllName && isspace(static_cast<unsigned char>(*(szEnd-1))))
			--szEnd;

		szSlash++;
		// skip leading whitespace
		while (*szSlash && isspace(static_cast<unsigned char>(*szSlash)))
			szSlash++;

		// right trim szSlash;
		szSlashEnd = szSlash;
		while (*szSlashEnd && !isspace(static_cast<unsigned char>(*szSlashEnd)))
			szSlashEnd++;
	}
	else // only the <dll_name>
	{
		szSlash = ATL_HANDLER_NAME_DEFAULT;
		szSlashEnd = szSlash+sizeof(ATL_HANDLER_NAME_DEFAULT)-1;

		// do it this way to handle paths with spaces
		// (e.g. "some path\subdirectory one\subdirectory two\dll_name.dll")
		szEnd = szHandlerDllName+strlen(szHandlerDllName);

		// skip trailing spaces
		while (szEnd>szHandlerDllName && isspace(static_cast<unsigned char>(*(szEnd-1))))
			--szEnd;
	}

	// if the dll path is quoted, strip the quotes
	if (*szHandlerDllName == '\"' && *(szEnd-1) == '\"' && szEnd > szHandlerDllName+2)
	{
		szHandlerDllName++;
		szEnd--;
	}

	if (*pdwDllPathLen > (DWORD)(szEnd-szHandlerDllName) && (szEnd-szHandlerDllName >= 0))
	{
		Checked::memcpy_s(szDllPath, *pdwDllPathLen, szHandlerDllName, szEnd-szHandlerDllName);
		szDllPath[szEnd-szHandlerDllName] = '\0';
		*pdwDllPathLen = (DWORD)(szEnd-szHandlerDllName);
	}
	else
	{
		*pdwDllPathLen = (DWORD)(szEnd-szHandlerDllName)+1;
		bRet = FALSE;
	}

	if (*pdwHandlerNameLen > (DWORD)(szSlashEnd-szSlash) && (szSlashEnd-szSlash >= 0))
	{
		Checked::memcpy_s(szHandlerName, *pdwHandlerNameLen, szSlash, (szSlashEnd-szSlash));
		szHandlerName[szSlashEnd-szSlash] = '\0';
		*pdwHandlerNameLen = (DWORD)(szSlashEnd-szSlash);
	}
	else
	{
		*pdwHandlerNameLen = (DWORD)(szSlashEnd-szSlash)+1;
		bRet = FALSE;
	}

	return bRet;
}

inline __checkReturn __success(return==HTTP_SUCCESS) HTTP_CODE _AtlLoadRequestHandler(
			__in LPCSTR szDllPath, 
			__in LPCSTR szHandlerName,
			__in IHttpServerContext *pServerContext, 
			__out HINSTANCE *phInstance, 
			__deref_out_opt IRequestHandler **ppHandler,
			__in IIsapiExtension *pExtension,
			__in IDllCache *pDllCache) throw(...)
{
	ATLENSURE(phInstance!=NULL);
	ATLENSURE(ppHandler!=NULL);
	ATLENSURE(pDllCache!=NULL);
	*phInstance = NULL;
	*ppHandler = NULL;

	ATLServerDllInfo DllInfo;
	DllInfo.pExtension = pExtension;
	DllInfo.pContext = pServerContext;
	if (!IsFullPathA(szDllPath))
	{
		CHAR szFileName[MAX_PATH];
		if (!GetScriptFullFileName(szDllPath, szFileName, pServerContext))
		{
			return HTTP_FAIL;
		}
		
		*phInstance = pDllCache->Load(szFileName, (void *)&DllInfo);
	}
	else
	{
		*phInstance = pDllCache->Load(szDllPath, (void *)&DllInfo);
	}
	if (!*phInstance)
	{
		ATLTRACE( "LoadLibrary failed: '%s' with error: %d\r\n", szDllPath, GetLastError() );
		return AtlsHttpError(500, ISE_SUBERR_LOADLIB);
	}

	CComPtr<IUnknown> spUnk;

	if (!DllInfo.pfnGetHandler || 
		!DllInfo.pfnGetHandler(szHandlerName, pExtension, &spUnk) ||
		FAILED(spUnk->QueryInterface(ppHandler)))
	{
		pDllCache->Free(*phInstance);
		*phInstance = NULL;
		return AtlsHttpError(500, ISE_SUBERR_HANDLER_NOT_FOUND);
	}

	return HTTP_SUCCESS;
} // _AtlLoadRequestHandler


class CTransferServerContext : public CComObjectRootEx<CComMultiThreadModel>,
	public CWrappedServerContext
{
public:
	char m_szFileName[MAX_PATH];
	char m_szQueryString[ATL_URL_MAX_PATH_LENGTH+1];
	CStringA m_strUrl;
	IWriteStream *m_pStream;

	BEGIN_COM_MAP(CTransferServerContext)
		COM_INTERFACE_ENTRY(IHttpServerContext)
	END_COM_MAP()

	CTransferServerContext() throw()
	{
		m_pStream = NULL;
	}

	BOOL Initialize(__in CTransferServerContext *pOtherContext)
	{
		ATLENSURE(pOtherContext!=NULL);
		return Initialize(pOtherContext->m_strUrl, pOtherContext->m_pStream, pOtherContext->m_spParent);
	}

	BOOL Initialize(__in LPCSTR szUrl, __in IWriteStream *pStream, __in IHttpServerContext *pParent) throw()
	{
		m_pStream = pStream;
		m_spParent = pParent;

		_ATLTRY
		{
			m_strUrl = szUrl; // we store the URL in case we need to initialize another context from this context
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}

		long nUrlLen = m_strUrl.GetLength();
		m_szFileName[0] = '\0';

		if (!IsFullPathA(szUrl))
		{
			DWORD dwLen = MAX_PATH;
			BOOL bRet = TRUE;
			_ATLTRY
			{
				bRet = m_spParent->GetServerVariable(
										"APPL_PHYSICAL_PATH",
										m_szFileName,
										&dwLen);
			}
			_ATLCATCHALL()
			{
				bRet = FALSE;
			}

			if (!bRet)
			{
				return bRet;
			}
		}

		// check for query params
		LPCSTR szMark = strchr(szUrl, '?');
		if (szMark)
		{
			size_t nPathLen = szMark - szUrl;
			size_t nLen;

			if ((nPathLen >= 0) && (nPathLen < MAX_PATH)) 
			{
				nLen = strlen(m_szFileName) + nPathLen;
				if (nLen < MAX_PATH)
				{
#pragma warning(push)
#pragma warning(disable: 22008)
					/* Prefast false warning about unbound nPathLen in the below fragment - 
					   we already have necessary checks few lines above 
					*/
					if (m_szFileName[0])
					{
						Checked::strcat_s(m_szFileName, MAX_PATH-nLen, szUrl);
					}
					else
					{
						Checked::memcpy_s(m_szFileName, MAX_PATH, szUrl, nPathLen);
						m_szFileName[nPathLen] = '\0';
					}
#pragma warning(pop)
				}
				else
				{
					return FALSE; // path would overwrite buffer
				}
			}
			else
			{
				return FALSE; // path would overwrite buffer
			}

			// save query params
			nLen = strlen(szMark + 1);
			if (nLen < ATL_URL_MAX_PATH_LENGTH)
			{
				Checked::strcpy_s(m_szQueryString, ATL_URL_MAX_PATH_LENGTH, szMark+1);
			}
			else
			{
				return FALSE; // url would overwrite buffer
			}
		}
		else
		{
			// no query string
			size_t nLen = strlen(m_szFileName) + nUrlLen;
			if (nLen < MAX_PATH)
			{
				if (m_szFileName[0])
				{
					Checked::strcat_s(m_szFileName, MAX_PATH-nLen, szUrl);
				}
				else
				{
					Checked::strcpy_s(m_szFileName, MAX_PATH, szUrl);
				}
			}
			else
			{
				return FALSE; // path would be too long
			}
			m_szQueryString[0] = '\0';
		}

		return TRUE;
	}
	BOOL WriteClient(__in_bcount(*pdwBytes) void *pvBuffer, __inout DWORD *pdwBytes)
	{
		ATLENSURE(m_pStream != NULL);

		HRESULT hr = S_OK;
		_ATLTRY
		{
			m_pStream->WriteStream((LPCSTR) pvBuffer, *pdwBytes, pdwBytes);
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}

		return SUCCEEDED(hr);
	}

	LPCSTR GetQueryString()
	{
		ATLASSUME(m_spParent);
		return m_szQueryString;
	}

	LPCSTR GetScriptPathTranslated()
	{
		ATLASSUME(m_spParent);
		return m_szFileName;
	}

	LPCSTR GetPathTranslated()
	{
		ATLASSUME(m_spParent);
		return m_szFileName;
	}

	// Asynchronous writes will not work properly in a child handler
	BOOL AsyncWriteClient(void * /*pvBuffer*/, DWORD * /*pdwBytes*/)
	{
		ATLASSERT(FALSE);
		return FALSE;
	}

	// These next few methods are to protect against attempting to parse form data twice
	// We tell the new handler that it was a GET request
	LPCSTR GetRequestMethod()
	{
		ATLASSUME(m_spParent);
		return "GET";
	}

	// The handler should not query these methods -- they are only useful if attempting to
	// parse form data, which is not allowed in child handlers.
	BOOL ReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/)
	{
		return FALSE;
	}

	BOOL AsyncReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/)
	{
		return FALSE;
	}

	DWORD GetTotalBytes()
	{
		ATLASSERT(FALSE);
		return 0;
	}

	DWORD GetAvailableBytes()
	{
		ATLASSERT(FALSE);
		return 0;
	}

	BYTE *GetAvailableData()
	{
		ATLASSERT(FALSE);
		return NULL;
	}

	LPCSTR GetContentType()
	{
		ATLASSERT(FALSE);
		return NULL;
	}
};

class CAllocContextBase
{
public:
	virtual HTTP_CODE Alloc(IHttpServerContext **ppNewContext) = 0;
};

ATL_NOINLINE inline HTTP_CODE _AtlRenderInclude(
	__in IHttpServerContext *pServerContextNew,
	__in LPCSTR szFileName,
	__in LPCSTR szQueryParams,
	__in WORD wCodePage,
	__in CAllocContextBase *pAllocContext,
	__in IServiceProvider *pServiceProvider,
	__in_opt IHttpRequestLookup *pLookup,
	__inout_opt CStencilState* pState = NULL)
{
	 ATLENSURE(pServiceProvider!=NULL);
	 AtlServerRequest* pRequestInfo = NULL;
	 HTTP_CODE hcErr = HTTP_SUCCESS;

	 // get a pointer to the ISAPI extension
	 CComPtr<IIsapiExtension> spExtension;
	 if (S_OK != pServiceProvider->QueryInterface(&spExtension) ||
		 !spExtension)
	 {
		 return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
	 }

	 // get a pointer to the extension's dll cache
	 CComPtr<IDllCache> spDllCache;
	 if (S_OK != pServiceProvider->QueryService(__uuidof(IDllCache),
												__uuidof(IDllCache),
												(void**)&spDllCache) ||
		 !spDllCache)
	 {
		 return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
	 }

#ifdef _DEBUG
	bool bAsyncAllowed = false;
#endif
	if (pState && pState->pIncludeInfo)
	{
		pRequestInfo = pState->pIncludeInfo;
		pState->pIncludeInfo = NULL;
#ifdef _DEBUG
		bAsyncAllowed = true;
#endif
	}
	else
	{
		ATLASSERT(spDllCache);
		ATLASSERT(spExtension);

		pRequestInfo = spExtension->CreateRequest();
		if (pRequestInfo == NULL)
		{
			return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
		}

		pRequestInfo->dwRequestState = ATLSRV_STATE_BEGIN;
		pRequestInfo->dwRequestType = ATLSRV_REQUEST_STENCIL;
		pRequestInfo->pDllCache = spDllCache;
		pRequestInfo->pExtension = spExtension;
		pRequestInfo->pServerContext = pServerContextNew;
		if (pState && pState->pParentInfo)
		{
			pRequestInfo->pUserData = pState->pParentInfo->pUserData;
		}

		// Extract the file extension of the included file by searching
		// for the first '.' from the right.
		// Can't use _tcsrchr because we have to use the stencil's codepage
		LPCSTR szDot = NULL;
		LPCSTR szMark = szFileName;
		ATLENSURE(szMark!=NULL);
		while (*szMark)
		{
			if (*szMark == '.')
				szDot = szMark;

			LPCSTR szNext = CharNextExA(wCodePage, szMark, 0);
			if (szNext == szMark)
			{
				// embedded null
				pRequestInfo->pServerContext = NULL;
				spExtension->FreeRequest(pRequestInfo);
				return HTTP_FAIL;
			}
			szMark = szNext;
		}

		if (szDot && AsciiStricmp(szDot, c_AtlSRFExtension) == 0)
		{
			hcErr = spExtension->LoadDispatchFile(szFileName, pRequestInfo);
			if (hcErr)
			{
				pRequestInfo->pServerContext = NULL;
				spExtension->FreeRequest(pRequestInfo);
				return hcErr;
			}
		}
		else if (szDot && AsciiStricmp(szDot, c_AtlDLLExtension) == 0)
		{
			// Get the handler name if they used the asdf.dll?Handler=Default notation
			char szHandlerName[ATL_MAX_HANDLER_NAME_LEN+1] = { '\0' };

			LPCSTR szStart = strstr(szQueryParams, "Handler=");
			if (szStart && 
				((szStart == szQueryParams) || 
				 ((szStart > szQueryParams) && (*(szStart-1) == '&'))))
			{
				szStart += 8;  // Skip past "Handler" and the "="
				LPCSTR szEnd = strchr(szStart, '&');
				if (szEnd)
				{
					Checked::memcpy_s(szHandlerName, ATL_MAX_HANDLER_NAME_LEN+1, szStart, __min((szEnd-szStart), ATL_MAX_HANDLER_NAME_LEN));
					szHandlerName[__min((szEnd-szStart), ATL_MAX_HANDLER_NAME_LEN)] = '\0';
				}
				else
				{
					if (!SafeStringCopy(szHandlerName, szStart))
					{
						// handler name too long
						pRequestInfo->pServerContext = NULL;
						spExtension->FreeRequest(pRequestInfo);
						return HTTP_FAIL;
					}
				}
			}
			else
			{
				ATLASSERT( ATL_MAX_HANDLER_NAME_LEN >= sizeof(ATL_HANDLER_NAME_DEFAULT) );
				Checked::memcpy_s(szHandlerName, ATL_MAX_HANDLER_NAME_LEN+1, ATL_HANDLER_NAME_DEFAULT, sizeof(ATL_HANDLER_NAME_DEFAULT));
			}

			pRequestInfo->dwRequestType = ATLSRV_REQUEST_DLL;

			hcErr = spExtension->LoadRequestHandler(szFileName, szHandlerName, pRequestInfo->pServerContext, 
				&pRequestInfo->hInstDll, &pRequestInfo->pHandler);
			if (hcErr)
			{
				pRequestInfo->pServerContext = NULL;
				spExtension->FreeRequest(pRequestInfo);
				return hcErr;
			}
		}
		else
		{
			// unknown extension
			pRequestInfo->pServerContext = NULL;
			spExtension->FreeRequest(pRequestInfo);
			return HTTP_ERROR(500, ISE_SUBERR_UNEXPECTED);
		}

		DWORD dwStatus;
		hcErr = pRequestInfo->pHandler->GetFlags(&dwStatus);

		if (hcErr)
		{
			pRequestInfo->pHandler->UninitializeHandler();
			pRequestInfo->pServerContext = NULL;
			spExtension->FreeRequest(pRequestInfo);
			return hcErr;
		}

		if (dwStatus & (ATLSRV_INIT_USEASYNC | ATLSRV_INIT_USEASYNC_EX))
		{
#ifdef _DEBUG
			bAsyncAllowed = true;
#endif
			ATLENSURE(pAllocContext!=NULL);
			hcErr = pAllocContext->Alloc(&pRequestInfo->pServerContext);
			if (hcErr)
			{
				pRequestInfo->pHandler->UninitializeHandler();
				if (pRequestInfo->pServerContext == pServerContextNew)
				{
					pRequestInfo->pServerContext = NULL;
				}
				spExtension->FreeRequest(pRequestInfo);
				return hcErr;
			}
		}

		hcErr = pRequestInfo->pHandler->InitializeChild(pRequestInfo, pServiceProvider, pLookup);
		if (hcErr)
		{
			pRequestInfo->pHandler->UninitializeHandler();
			if (pRequestInfo->pServerContext == pServerContextNew)
			{
				pRequestInfo->pServerContext = NULL;
			}
			spExtension->FreeRequest(pRequestInfo);
			return hcErr;
		}

		pRequestInfo->pfnHandleRequest = &IRequestHandler::HandleRequest;
	}

	if (pRequestInfo)
	{
		if (!hcErr)
		{
			ATLASSERT(pRequestInfo->pfnHandleRequest != NULL);
			hcErr = (pRequestInfo->pHandler->*pRequestInfo->pfnHandleRequest)(pRequestInfo, pServiceProvider);

#ifdef _DEBUG
			// must use ATLSRV_INIT_USEASYNC to use ASYNC returns
			if (IsAsyncStatus(hcErr))
			{
				ATLASSERT(bAsyncAllowed);
			}
#endif

			if (IsAsyncStatus(hcErr))
			{
				ATLASSERT(pState); // state is required for async
				if (IsAsyncContinueStatus(hcErr))
				{
					pState->pIncludeInfo = pRequestInfo;
					pRequestInfo->dwRequestState = ATLSRV_STATE_CONTINUE;
				}
				else if (IsAsyncDoneStatus(hcErr))
				{
					pRequestInfo->pHandler->UninitializeHandler();
					if (pRequestInfo->pServerContext == pServerContextNew)
					{
						pRequestInfo->pServerContext = NULL;
					}
					spExtension->FreeRequest(pRequestInfo);
				}
			}
			else
			{
				pRequestInfo->pHandler->UninitializeHandler();
				if (pRequestInfo->pServerContext == pServerContextNew)
				{
					pRequestInfo->pServerContext = NULL;
				}
				spExtension->FreeRequest(pRequestInfo);
			}
		}
	}
	else
	{
		hcErr = AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
	}

	return hcErr;
}

// CAllocTransferAsyncContext is an unsupported implementation detail, used
// for implementing _AtlTransferRequest.
class CAllocTransferAsyncContext : 
	public CAllocContextBase
{
public:
	CAllocTransferAsyncContext(CTransferServerContext *pInitialContext):
		m_pInitialContext(pInitialContext)
	{
	}

	HTTP_CODE Alloc(IHttpServerContext** ppNewContext)
	{
		if (!ppNewContext)
			return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
		*ppNewContext = NULL;

		CComObjectNoLock<CTransferServerContext>* pServerContext = NULL;
		ATLTRY(pServerContext = new CComObjectNoLock<CTransferServerContext>);
		if (pServerContext == NULL)
			return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
		pServerContext->Initialize(m_pInitialContext);
		pServerContext->AddRef();
		*ppNewContext  = pServerContext;
		return HTTP_SUCCESS;
	}
private:
	CTransferServerContext *m_pInitialContext;
};

inline HTTP_CODE _AtlTransferRequest(
	__in AtlServerRequest *pRequest, 
	__in IServiceProvider *pServiceProvider,
	__in IWriteStream *pWriteStream,
	__in_opt IHttpRequestLookup *pLookup,
	__in LPCSTR szNewUrl,
	__in WORD nCodePage,
	__in bool bContinueAfterProcess,
	__inout_opt CStencilState *pState)
{
	CComObjectStackEx<CTransferServerContext> serverContext;
	if (!serverContext.Initialize(szNewUrl, pWriteStream, pRequest->pServerContext))
		return AtlsHttpError(500, 0);
	CAllocTransferAsyncContext AsyncAllocObj(&serverContext);
	HTTP_CODE hcErr = _AtlRenderInclude(static_cast<IHttpServerContext*>(&serverContext),
									serverContext.m_szFileName,
									serverContext.m_szQueryString,
									nCodePage,
									&AsyncAllocObj,
									pServiceProvider,
									pLookup,
									pState);
	if (hcErr == HTTP_SUCCESS && bContinueAfterProcess)
		return hcErr;
	return HTTP_SUCCESS_NO_PROCESS;
}

//
// This function is now deprecated. ATL is not using it anymore.
//
inline ATL_DEPRECATED("Do not use this function.")
void __cdecl AtlsSecErrHandlerFunc(int /* nCode */, void * /* pv */)
{
	//
	// a buffer overflow has occurred in your code
	//
	ATLASSERT( FALSE );

	//
	// terminate process (safest thing to do)
	//
	TerminateProcess( GetCurrentProcess(), 1 );
}

//
// Class CIsapiExtension
// The main ISAPI Extension implementation.
// Template parameters
// ThreadPoolClass: Specifies the thread pool that will be used by the 
//		extension to queue incoming requests. CThreadPool is the
//		default and is declared and implemented in ATLUTIL.H. This class
//		templatizes on a worker thread class. The worker thread class
//		represents an abstraction of a thread that will be used to
//		process requests as they are dequeued from the pool's work queue.
//		You would change this parameter if you wanted to use a completely
//		different thread pool, or, more commonly, if you wanted to use 
//		a different worker thread class. Request processing code can
//		access a pointer to the worker thread class, which allows the
//		request handling code to easily access per-thread data.
// CRequestStatClass:	Specifies the class to be used to track request statistics
//		CNoRequestStats is the default which is a noop class.
//		You would change this parameter to provide a class that will
//		track request statistics for you. ATL provides CStdRequestStats
//		and CPerfRequestStatObject but these classes should be used
//		with caution because they require interlocked operations to
//		keep track of request statistics which can affect server performance.
// HttpUserErrorTextProvider: This class provides error text messages
//		and headers, including  resource IDs of error messages to the
//		isapi extension's error handling functions. You would change this
//		parameter if you wanted to provide your own error headers and/or
//		messages in response to error encountered during request processing.
// CPageCacheStats, CStencilCacheStats: These two classes are used to keep
//		statistics about the page and stencil caches. You could change these
//		paramters if you wanted to track statistics for these caches. ATL
//		provides CPerfStatClass and CStdStatClass to store the stat data but
//		using these classes can affect server performance because they use
//		interlocked operations internally to store the data.
template <  class ThreadPoolClass=CThreadPool<CIsapiWorker>, 
			class CRequestStatClass=CNoRequestStats,
			class HttpUserErrorTextProvider=CDefaultErrorProvider,
			class WorkerThreadTraits=DefaultThreadTraits,
			class CPageCacheStats=CNoStatClass,
			class CStencilCacheStats=CNoStatClass>
class CIsapiExtension :
	public IServiceProvider, public IIsapiExtension, public IRequestStats
{
private:

#ifndef ATL_NO_CRITICAL_ISAPI_ERROR

	DWORD m_dwCriticalIsapiError;

#endif // ATL_NO_CRITICAL_ISAPI_ERROR

protected:
	DWORD m_dwTlsIndex;

	typedef CWorkerThread<WorkerThreadTraits> extWorkerType;

	extWorkerType m_WorkerThread;
	ThreadPoolClass m_ThreadPool;

	CDllCache<extWorkerType, CDllCachePeer> m_DllCache;
	CFileCache<extWorkerType, CPageCacheStats, CPageCachePeer> m_PageCache;
	CComObjectGlobal<CStencilCache<extWorkerType, CStencilCacheStats > > m_StencilCache;
	HttpUserErrorTextProvider m_UserErrorProvider;
	HANDLE m_hRequestHeap;
	CComCriticalSection m_critSec;

	// Dynamic services stuff
	struct ServiceNode
	{
		HINSTANCE hInst;
		IUnknown *punk;
		GUID guidService;
		IID riid;

		ServiceNode() throw()
		{
		}

		ServiceNode(const ServiceNode& that) throw()
			:hInst(that.hInst), punk(that.punk), guidService(that.guidService), riid(that.riid)
		{
		}
	};

	class CServiceEqualHelper
	{
	public:
		static bool IsEqual(__in const ServiceNode& t1, __in const ServiceNode& t2) throw()
		{
			return (InlineIsEqualGUID(t1.guidService, t2.guidService) != 0 &&
					InlineIsEqualGUID(t1.riid, t2.riid) != 0);
		}
	};

	CSimpleArray<ServiceNode, CServiceEqualHelper> m_serviceMap;

public:
	CWin32Heap m_heap;

	CRequestStatClass m_reqStats;

	AtlServerRequest *CreateRequest()
	{
		// Allocate a fixed block size to avoid fragmentation
		AtlServerRequest *pRequest = (AtlServerRequest *) HeapAlloc(m_hRequestHeap, 
				HEAP_ZERO_MEMORY, __max(sizeof(AtlServerRequest), sizeof(_CComObjectHeapNoLock<CServerContext>)));
		if (!pRequest)
			return NULL;
		pRequest->cbSize = sizeof(AtlServerRequest);

		return pRequest;
	}

	void FreeRequest(__inout AtlServerRequest *pRequest)
	{
		_ReleaseAtlServerRequest(pRequest);
		HeapFree(m_hRequestHeap, 0, pRequest);
	}

	CIsapiExtension() throw()
	{
		m_hRequestHeap = NULL;
#ifdef _DEBUG
		m_bDebug = FALSE;
#endif

#ifndef ATL_NO_CRITICAL_ISAPI_ERROR

		m_dwCriticalIsapiError = 0;

#endif // ATL_NO_CRITICAL_ISAPI_ERROR
	}

	HTTP_CODE TransferRequest(
		__in AtlServerRequest *pRequest, 
		__in IServiceProvider *pServiceProvider,
		__in IWriteStream *pWriteStream,
		__in_opt IHttpRequestLookup *pLookup,
		__in LPCSTR szNewUrl,
		__in WORD nCodePage,
		__in bool bContinueAfterProcess,
		__inout_opt CStencilState *pState)
	{
		HTTP_CODE hcErr;
		_ATLTRY
		{
			hcErr = _AtlTransferRequest(pRequest, pServiceProvider, pWriteStream,
				pLookup, szNewUrl, nCodePage, bContinueAfterProcess, pState);
		}
		_ATLCATCHALL()
		{
			hcErr = HTTP_FAIL;
		}
		return hcErr;
	}

#ifndef ATL_NO_CRITICAL_ISAPI_ERROR

	DWORD ReturnCriticalError(__in EXTENSION_CONTROL_BLOCK *pECB)
	{
		
		_ATLTRY
		{
			ATLENSURE(pECB);
			UINT uResId = 0;
			LPCSTR szHeader = NULL;

			CStringA strStatus;
			CStringA strBody;
			CStringA strFormat;
			CStringA strError;

			DWORD dwErr = GetCriticalIsapiError();
			if (!strError.LoadString(dwErr))
			{
				strError.Format("Unknown Error %d", dwErr);
			}

#ifdef ATL_CRITICAL_ISAPI_ERROR_LOGONLY
			// we've logged the real error - don't send detailed internal info to the user
			m_UserErrorProvider.GetErrorText(500,
											 SUBERR_NONE,
											 &szHeader,
											 &uResId);

			if (!uResId || !strBody.LoadString(uResId))
			{
				strBody = "<html><body>A server error has occurred.</body></html>";
			}
#else
			m_UserErrorProvider.GetErrorText(500,
											 ISE_SUBERR_ISAPISTARTUPFAILED,
											 &szHeader,
											 &uResId);
			if (!uResId || !strFormat.LoadString(uResId))
			{
				strFormat = "<html><body>A critical error has occurred initializing this ISAPI extension: %s</body></html>";
			}
			strBody.Format(strFormat, strError);
#endif
			strStatus.Format("500 %s", szHeader);

			HSE_SEND_HEADER_EX_INFO hex;
			hex.pszStatus = (LPCSTR)strStatus;
			hex.pszHeader = NULL;
			hex.cchStatus = (DWORD)strStatus.GetLength();
			hex.cchHeader = 0;
			hex.fKeepConn = FALSE;

			pECB->ServerSupportFunction(pECB->ConnID,
										HSE_REQ_SEND_RESPONSE_HEADER_EX,
										&hex,
										NULL,
										NULL);

			DWORD dwBodyLen = strBody.GetLength();
			pECB->WriteClient(pECB->ConnID, 
							 (void *) (LPCSTR) strBody,
							 &dwBodyLen,
							 NULL);
		}
		_ATLCATCHALL()
		{
			return HSE_STATUS_ERROR;
		}
		return HSE_STATUS_SUCCESS;
	}

#endif // ATL_NO_CRITICAL_ISAPI_ERROR

	DWORD HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB) throw()
	{

#ifndef ATL_NO_CRITICAL_ISAPI_ERROR

		if (GetCriticalIsapiError() != 0)
		{
			return ReturnCriticalError(lpECB);
		}

#endif // ATL_NO_CRITICAL_ISAPI_ERROR

		AtlServerRequest *pRequestInfo = NULL;

		_ATLTRY
		{
			pRequestInfo = CreateRequest();
			if (pRequestInfo == NULL)
				return HSE_STATUS_ERROR;

			CServerContext *pServerContext = NULL;
			ATLTRY(pServerContext = CreateServerContext(m_hRequestHeap));
			if (pServerContext == NULL)
			{
				FreeRequest(pRequestInfo);
				return HSE_STATUS_ERROR;
			}

			pServerContext->Initialize(lpECB);
			pServerContext->AddRef();

			pRequestInfo->pServerContext = pServerContext;
			pRequestInfo->dwRequestType = ATLSRV_REQUEST_UNKNOWN;
			pRequestInfo->dwRequestState = ATLSRV_STATE_BEGIN;
			pRequestInfo->pExtension = static_cast<IIsapiExtension *>(this);
			pRequestInfo->pDllCache = static_cast<IDllCache *>(&m_DllCache);
#ifndef ATL_NO_MMSYS
			pRequestInfo->dwStartTicks = timeGetTime();
#else
			pRequestInfo->dwStartTicks = GetTickCount();
#endif
			pRequestInfo->pECB = lpECB;

			m_reqStats.OnRequestReceived();

			if (m_ThreadPool.QueueRequest(pRequestInfo))
				return HSE_STATUS_PENDING;

			if (pRequestInfo != NULL)
			{
				FreeRequest(pRequestInfo);
			}

		}
		_ATLCATCHALL()
		{
		}



		return HSE_STATUS_ERROR;
	}


	BOOL QueueRequest(__in AtlServerRequest * pRequestInfo)
	{
		return m_ThreadPool.QueueRequest(pRequestInfo);
	}

	CIsapiWorker *GetThreadWorker()
	{
		return (CIsapiWorker *) TlsGetValue(m_dwTlsIndex);
	}

	BOOL SetThreadWorker(__in CIsapiWorker *pWorker)
	{
		return TlsSetValue(m_dwTlsIndex, (void*)pWorker);
	}

	// Configuration functions -- override in base class if another value is desired
	virtual LPCSTR GetExtensionDesc() throw() { return "VC Server Classes"; }
	virtual int GetNumPoolThreads() throw() { return 0; }
	virtual int GetPoolStackSize() throw() { return 0; }
	virtual HANDLE GetIOCompletionHandle() throw() { return INVALID_HANDLE_VALUE; }
	virtual DWORD GetDllCacheTimeout() throw() { return ATL_DLL_CACHE_TIMEOUT; }
	virtual DWORD GetStencilCacheTimeout() throw() { return ATL_STENCIL_CACHE_TIMEOUT; }
	virtual LONGLONG GetStencilLifespan() throw() { return ATL_STENCIL_LIFESPAN; }

	BOOL OnThreadAttach()
	{
		return SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
	}

	void OnThreadTerminate()
	{
		CoUninitialize();
	}

#ifndef ATL_NO_CRITICAL_ISAPI_ERROR

	BOOL SetCriticalIsapiError(__in DWORD dwErr = 1) throw()
	{
		m_dwCriticalIsapiError = dwErr;

		// send the error to the event log
		_ATLTRY
		{
			CStringA strBody;
			CStringA strFormat;
			CStringA strError;

			// format an error message
			if (!strError.LoadString(dwErr))
			{
				strError.Format("Unknown Error %d", dwErr);
			}

			if (!strFormat.LoadString(IDS_ATLSRV_CRITICAL_LOGMESSAGE))
			{
				strFormat = "A critical error has occurred initializing the ISAPI extension: %s";
			}
			strBody.Format(strFormat, strError);

			// take the base module name as the app name
			CPath path;
			{
				CStrBuf buf(path, MAX_PATH);
				DWORD dwLen = ::GetModuleFileName(_AtlBaseModule.GetModuleInstance(), buf, MAX_PATH);
				if (dwLen == MAX_PATH)
					buf.SetLength(MAX_PATH);
			}
			path.StripPath();

			// log the event
			HANDLE h = RegisterEventSource(NULL, path);
			if (h)
			{
				LPCSTR szBody = strBody;
				ReportEventA(h, EVENTLOG_ERROR_TYPE, 0, 0, NULL, 1, 0, &szBody, NULL);
				DeregisterEventSource(h);
			}
		}
		_ATLCATCHALL()
		{
		}

		return TRUE;
	}

	DWORD GetCriticalIsapiError() throw()
	{
		return m_dwCriticalIsapiError;
	}

#else

	BOOL SetCriticalIsapiError(__in DWORD dwErr = 1) throw()
	{
		dwErr; // not used
		return FALSE;
	}

	DWORD GetCriticalIsapiError() throw()
	{
		return 0;
	}

#endif // ATL_NO_CRITICAL_ISAPI_ERROR


	BOOL GetExtensionVersion(__out HSE_VERSION_INFO* pVer) throw()
	{
		ATLASSERT(pVer!=NULL);
		if(pVer==NULL)
		{
			return FALSE;
		}
		// allocate a Tls slot for storing per thread data
		m_dwTlsIndex = TlsAlloc();

		// create a private heap for request data
		// this heap has to be thread safe to allow for
		// async processing of requests
		m_hRequestHeap = HeapCreate(0, 0, 0);
		if (!m_hRequestHeap)
		{
			ATLTRACE(atlTraceISAPI, 0, _T("Failed creating request heap.  Using process heap\n"));
			m_hRequestHeap = GetProcessHeap();
			if (!m_hRequestHeap)
			{
				return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_HEAPCREATEFAILED);
			}

		}

		// create a private heap (synchronized) for
		// allocations.  This reduces fragmentation overhead
		// as opposed to the process heap
		HANDLE hHeap = HeapCreate(0, 0, 0);
		if (!hHeap)
		{
			ATLTRACE(atlTraceISAPI, 0, _T("Failed creating extension heap.  Using process heap\n"));
			hHeap = GetProcessHeap();
			m_heap.Attach(hHeap, false);
		}
		else
		{
			m_heap.Attach(hHeap, true);
		}
		hHeap = NULL;

		if (S_OK != m_reqStats.Initialize())
		{
			ATLTRACE(atlTraceISAPI,
					 0,
					 _T("Initialization failed for request statistics perfmon support.\n")
					 _T("Check request statistics perfmon dll registration\n") );
		}

		if (S_OK != m_WorkerThread.Initialize())
		{
			return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_WORKERINITFAILED);
		}

		if (m_critSec.Init() != S_OK)
		{
			HRESULT hrIgnore=m_WorkerThread.Shutdown();
			(hrIgnore);
			return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_CRITSECINITFAILED);
		}

		if (S_OK != m_ThreadPool.Initialize(static_cast<IIsapiExtension*>(this), GetNumPoolThreads(), GetPoolStackSize(), GetIOCompletionHandle()))
		{
			HRESULT hrIgnore=m_WorkerThread.Shutdown();
			(hrIgnore);
			m_critSec.Term();
			return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_THREADPOOLFAILED);
		}

		if (FAILED(m_DllCache.Initialize(&m_WorkerThread, GetDllCacheTimeout())))
		{
			HRESULT hrIgnore=m_WorkerThread.Shutdown();
			(hrIgnore);
			m_ThreadPool.Shutdown();
			m_critSec.Term();
			return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_DLLCACHEFAILED);
		}

		if (FAILED(m_PageCache.Initialize(&m_WorkerThread)))
		{
			HRESULT hrIgnore=m_WorkerThread.Shutdown();
			(hrIgnore);
			m_ThreadPool.Shutdown();
			m_DllCache.Uninitialize();
			m_critSec.Term();
			return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_PAGECACHEFAILED);
		}

		if (S_OK != m_StencilCache.Initialize(static_cast<IServiceProvider*>(this),
										  &m_WorkerThread, 
										  GetStencilCacheTimeout(),
										  GetStencilLifespan()))
		{
			HRESULT hrIgnore=m_WorkerThread.Shutdown();
			(hrIgnore);
			m_ThreadPool.Shutdown();
			m_DllCache.Uninitialize();
			m_PageCache.Uninitialize();
			m_critSec.Term();
			return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_STENCILCACHEFAILED);
		}

		pVer->dwExtensionVersion = HSE_VERSION;
		Checked::strncpy_s(pVer->lpszExtensionDesc, HSE_MAX_EXT_DLL_NAME_LEN, GetExtensionDesc(), _TRUNCATE);
		pVer->lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN - 1] = '\0';

		return TRUE;
	}

	BOOL TerminateExtension(DWORD /*dwFlags*/) throw()
	{
		m_critSec.Lock();
		for (int i=0; i < m_serviceMap.GetSize(); i++)
		{
			ATLASSUME(m_serviceMap[i].punk != NULL);
			if(m_serviceMap[i].punk != NULL)
			{
				m_serviceMap[i].punk->Release();
			}
			m_DllCache.ReleaseModule(m_serviceMap[i].hInst);
		}
		m_critSec.Unlock();

		m_ThreadPool.Shutdown();
		m_StencilCache.Uninitialize();
		m_DllCache.Uninitialize();
		m_PageCache.Uninitialize();
		HRESULT hrShutdown=m_WorkerThread.Shutdown();
		m_reqStats.Uninitialize();
		m_critSec.Term();

		// free the request heap
		if (m_hRequestHeap != GetProcessHeap())
			HeapDestroy(m_hRequestHeap);

		// free the Tls slot that we allocated
		TlsFree(m_dwTlsIndex);

		return SUCCEEDED(hrShutdown);
	}

	static void WINAPI AsyncCallback(LPEXTENSION_CONTROL_BLOCK /*lpECB*/,
									 __in PVOID pContext,
									 __in DWORD cbIO,
									 __in DWORD dwError) throw(...)
	{
		AtlServerRequest *pRequestInfo = reinterpret_cast<AtlServerRequest*>(pContext);
		ATLENSURE(pRequestInfo);
		if (pRequestInfo->m_hMutex)
		{
			// synchronize in case the previous async_noflush call isn't finished
			// setting up state for the next call.
			DWORD dwStatus = WaitForSingleObject(pRequestInfo->m_hMutex, ATLS_ASYNC_MUTEX_TIMEOUT);
			if (dwStatus != WAIT_OBJECT_0 && dwStatus != WAIT_ABANDONED)
			{
				_ATLTRY
				{
					pRequestInfo->pExtension->RequestComplete(pRequestInfo, 500, ISE_SUBERR_UNEXPECTED);
				}
				_ATLCATCHALL()
				{
					ATLTRACE(_T("Warning: Uncaught user exception thrown and caught in AsyncCallback.\n"));
					_ATLRETHROW;
				}
				return;
			}
		}

		if (pRequestInfo->pfnAsyncComplete != NULL)
			ATLTRY((*pRequestInfo->pfnAsyncComplete)(pRequestInfo, cbIO, dwError));

		if (pRequestInfo->dwRequestState == ATLSRV_STATE_DONE)
		{
			pRequestInfo->pExtension->RequestComplete(pRequestInfo, HTTP_ERROR_CODE(HTTP_SUCCESS), 0);
		}
		else if (pRequestInfo->dwRequestState == ATLSRV_STATE_CACHE_DONE)
		{
			CloseHandle(pRequestInfo->hFile);
			pRequestInfo->pFileCache->ReleaseFile(pRequestInfo->hEntry);
			pRequestInfo->pExtension->RequestComplete(pRequestInfo, HTTP_ERROR_CODE(HTTP_SUCCESS), 0);
		}
		else 
		{
			HANDLE hMutex = pRequestInfo->m_hMutex;
			pRequestInfo->pExtension->QueueRequest(pRequestInfo);
			if (hMutex)
				ReleaseMutex(hMutex);
		}
	}

	void HandleError(__in IHttpServerContext *pServerContext, __in DWORD dwStatus, __in DWORD dwSubStatus) throw()
	{
		RenderError(pServerContext, dwStatus, dwSubStatus, &m_UserErrorProvider);
	}

	void RequestComplete(__inout AtlServerRequest *pRequestInfo, __in DWORD dwStatus, __in DWORD dwSubStatus)
	{
		ATLASSERT(pRequestInfo);

		if (pRequestInfo->pHandler != NULL)
			pRequestInfo->pHandler->UninitializeHandler();

		DWORD dwReqStatus = dwStatus;
		if (!dwReqStatus)
			dwReqStatus = 200;

		if (dwStatus >= 400)
		{
			if (dwSubStatus != SUBERR_NO_PROCESS)
				HandleError(pRequestInfo->pServerContext, dwStatus, dwSubStatus);
			m_reqStats.RequestHandled(pRequestInfo, FALSE);
		}
		else
			m_reqStats.RequestHandled(pRequestInfo, TRUE);

		CComPtr<IHttpServerContext> spServerContext = pRequestInfo->pServerContext;

		FreeRequest(pRequestInfo);

		spServerContext->DoneWithSession(dwReqStatus);
	}

	HTTP_CODE GetHandlerName(__in LPCSTR szFileName, __out_ecount(MAX_PATH+ATL_MAX_HANDLER_NAME+2) LPSTR szHandlerName) throw()
	{
		return _AtlGetHandlerName(szFileName, szHandlerName);
	}

	HTTP_CODE LoadDispatchFile(__in LPCSTR szFileName, __out AtlServerRequest *pRequestInfo)
	{
		ATLASSERT(szFileName);
		ATLASSERT(pRequestInfo);

		CStencil *pStencil = NULL;
		HCACHEITEM hStencil = NULL;

		// Must have space for the path to the handler + the maximum size
		// of the handler, plus the '/' plus the '\0'
		CHAR szDllPath[MAX_PATH];
		CHAR szHandlerName[ATL_MAX_HANDLER_NAME_LEN+1];

		pRequestInfo->pHandler = NULL;
		pRequestInfo->hInstDll = NULL;

		m_StencilCache.LookupStencil(szFileName, &hStencil);

		// Stencil was found, check to see if it needs to be refreshed
		if (hStencil)
		{
			m_StencilCache.GetStencil(hStencil, (void **) &pStencil);
			pStencil->GetHandlerName(szDllPath, MAX_PATH, szHandlerName, ATL_MAX_HANDLER_NAME_LEN + 1);

			CFileTime cftCurr;
			CFileTime cftLastChecked;
			cftCurr = CFileTime::GetCurrentTime();

			pStencil->GetLastChecked(&cftLastChecked);

			CFileTimeSpan span(ATL_STENCIL_CHECK_TIMEOUT * CFileTime::Millisecond);

			if (cftLastChecked + span < cftCurr)
			{
				CComPtr<IStencilCacheControl> spCacheCtrl;
				m_StencilCache.QueryInterface(__uuidof(IStencilCacheControl), reinterpret_cast<void**>(&spCacheCtrl));
				if (spCacheCtrl)
				{
					CFileTime cftLastModified;
					pStencil->GetLastModified(&cftLastModified);

					// Resource based stencils have a last modified filetime of 0
					if (cftLastModified != 0)
					{
						// for file base stencils, we check whether the file
						// has been modified since being put in the cache
						WIN32_FILE_ATTRIBUTE_DATA fad;
						pStencil->SetLastChecked(&cftCurr);
						BOOL bRet = GetFileAttributesExA(szFileName, GetFileExInfoStandard, &fad);

						if ((bRet && cftLastModified < fad.ftLastWriteTime) ||
							!bRet)
						{
							// the file has changed or an error has occurred trying to read the file, 
							// so remove it from the cache and force a reload
							spCacheCtrl->RemoveStencil(hStencil);
							pStencil = NULL;
							hStencil = NULL;
						}
					}
				}   
			}
		}


		if (!hStencil)
		{
			CHAR szHandlerDllName[MAX_PATH+ATL_MAX_HANDLER_NAME_LEN+1];
			*szHandlerDllName = '\0';

			// not in the cache, so open the file
			HTTP_CODE hcErr = GetHandlerName(szFileName, szHandlerDllName);
			if (hcErr)
				return hcErr;
			DWORD dwDllPathLen = MAX_PATH;
			DWORD dwHandlerNameLen = ATL_MAX_HANDLER_NAME_LEN+1;
			if (!_AtlCrackHandler(szHandlerDllName, szDllPath, &dwDllPathLen, szHandlerName, &dwHandlerNameLen))
			{
				return AtlsHttpError(500, ISE_SUBERR_HANDLER_NOT_FOUND);
			}
			ATLASSERT(*szHandlerName);
			ATLASSERT(*szDllPath);
			if (!*szHandlerName)
			{
				return AtlsHttpError(500, ISE_SUBERR_HANDLER_NOT_FOUND);
			}
		}
		else
		{
			m_StencilCache.ReleaseStencil(hStencil);
		}


		return LoadRequestHandler(szDllPath, szHandlerName, pRequestInfo->pServerContext, 
			&pRequestInfo->hInstDll, &pRequestInfo->pHandler);
	}

	HTTP_CODE LoadDllHandler(__in LPCSTR szFileName, __in AtlServerRequest *pRequestInfo)
	{
		ATLASSERT(szFileName);
		ATLENSURE(pRequestInfo);

		_ATLTRY
		{
			HTTP_CODE hcErr = HTTP_SUCCESS;
			CHAR szHandler[ATL_MAX_HANDLER_NAME_LEN+1] = { 'D', 'e', 'f', 'a', 'u', 'l', 't', '\0' };
			LPCSTR szQueryString = pRequestInfo->pServerContext->GetQueryString();
			if (szQueryString != NULL)
			{
				LPCSTR szHdlr = strstr(szQueryString, "Handler=");
				if (szHdlr != NULL)
				{
					if ((szHdlr == szQueryString) ||
						((szHdlr > szQueryString) && (*(szHdlr-1) == '&')))
					{
						int nCnt = 0;
						LPSTR pszHandler = szHandler;
						szHdlr += sizeof("Handler=")-1;
						while (*szHdlr && *szHdlr != '&')
						{
							if (nCnt < ATL_MAX_HANDLER_NAME_LEN)
							{
								*pszHandler++ = *szHdlr++;
								nCnt++;
							}
							else
							{
								hcErr = AtlsHttpError(500, ISE_SUBERR_HANDLER_NOT_FOUND);
								break;
							}
						}
						if (hcErr == HTTP_SUCCESS)
						{
							*pszHandler = '\0';
						}
					}
				}
			}

			if (hcErr == HTTP_SUCCESS)
			{
				CHAR szFile[MAX_PATH+ATL_MAX_HANDLER_NAME_LEN+1];
				if (SafeStringCopy(szFile, szFileName))
				{
					hcErr = LoadRequestHandler(szFile, szHandler, pRequestInfo->pServerContext, &pRequestInfo->hInstDll, &pRequestInfo->pHandler);
				}
				else
				{
					hcErr = AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
				}
			}

			return hcErr;
		}
		_ATLCATCHALL()
		{
			return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
		}
	}

#pragma warning(push)
#pragma warning(disable: 6014)
	virtual __success(return) __checkReturn BOOL GetCacheServerContext(__in AtlServerRequest *pRequestInfo, __in IFileCache *pCache, __deref_out_opt IHttpServerContext **pCacheCtx)
	{
		ATLENSURE(pCacheCtx);
		*pCacheCtx = NULL;

		CComObjectNoLock<CCacheServerContext> *pCacheServerContext = NULL;
		ATLTRY(pCacheServerContext = new CComObjectNoLock<CCacheServerContext>);
		if (!pCacheServerContext)
			return FALSE;

		if (!pCacheServerContext->Initialize(pRequestInfo->pServerContext, pCache))
		{
			delete pCacheServerContext;
			return FALSE;
		}

		pCacheServerContext->QueryInterface(__uuidof(IHttpServerContext), (void **) pCacheCtx);

		if (*pCacheCtx)
			return TRUE;

	 	delete pCacheServerContext;
		return FALSE;
	}
#pragma warning(pop)

	virtual BOOL TransmitFromCache(__in AtlServerRequest* pRequestInfo, __out BOOL *pbAllowCaching)
	{
		ATLENSURE(pRequestInfo);
		ATLENSURE(pbAllowCaching);

		*pbAllowCaching = TRUE;

		_ATLTRY
		{
			if (strcmp(pRequestInfo->pServerContext->GetRequestMethod(), "GET"))
				return FALSE;

			char szUrl[ATL_URL_MAX_URL_LENGTH + 1];
			LPCSTR szPathInfo = pRequestInfo->pServerContext->GetPathInfo();
			LPCSTR szQueryString = pRequestInfo->pServerContext->GetQueryString();

			int nSize = 0;
			LPSTR szTo = szUrl;
			ATLENSURE(szPathInfo!=NULL);
			while (*szPathInfo && nSize < ATL_URL_MAX_URL_LENGTH)
			{
				*szTo++ = *szPathInfo++;
				nSize++;
			}
			if (nSize >= ATL_URL_MAX_URL_LENGTH)
			{
				return FALSE;
			}
			*szTo++ = '?';
			nSize++;
			ATLENSURE(szQueryString!=NULL);
			while (*szQueryString && nSize < ATL_URL_MAX_URL_LENGTH)
			{
				*szTo++ = *szQueryString++;
				nSize++;
			}
			if (nSize >= ATL_URL_MAX_URL_LENGTH)
			{
				return FALSE;
			}
			*szTo = '\0';

			HCACHEITEM hEntry;

			if (S_OK == m_PageCache.LookupFile(szUrl, &hEntry))
			{ 
				LPSTR szFileName;
				CPageCachePeer::PeerInfo *pInfo;
				m_PageCache.GetFile(hEntry, &szFileName, (void **)&pInfo);
				ATLENSURE(pInfo);
				CAtlFile file;
				HRESULT hr = E_FAIL;

				CA2CTEX<MAX_PATH> strFile(szFileName);
				hr = file.Create(strFile,
								GENERIC_READ,
								FILE_SHARE_READ,
								OPEN_EXISTING,
								FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED);

				if (FAILED(hr) || GetFileType(file) != FILE_TYPE_DISK)
				{
					m_PageCache.ReleaseFile(hEntry);
					*pbAllowCaching = FALSE;
					return FALSE;
				}

				pRequestInfo->pServerContext->SendResponseHeader(
					pInfo->strHeader, pInfo->strStatus, FALSE);
				HANDLE hFile = file.Detach();
				BOOL bRet = FALSE;

				pRequestInfo->dwRequestState = ATLSRV_STATE_CACHE_DONE;
				pRequestInfo->hFile = hFile;
				pRequestInfo->hEntry = hEntry;
				pRequestInfo->pFileCache = &m_PageCache;

				bRet = pRequestInfo->pServerContext->TransmitFile(
					hFile,                          // The file to transmit
					AsyncCallback, pRequestInfo,    // The async callback and context
					pInfo->strStatus,               // HTTP status code
					0,                              // Send entire file
					0,                              // Start at the beginning of the file
					NULL, 0,                        // Head and length
					NULL, 0,                        // Tail and length
					HSE_IO_ASYNC | HSE_IO_DISCONNECT_AFTER_SEND | HSE_IO_NODELAY // Send asynchronously
					);

				if (!bRet)
				{
					m_PageCache.ReleaseFile(hEntry);
					CloseHandle(hFile);
					*pbAllowCaching = FALSE;
					return FALSE;
				}
				return TRUE;
			}
		}
		_ATLCATCHALL()
		{
		}

		return FALSE;
	}

#if defined(_DEBUG) || defined(ATLS_ENABLE_DEBUGGING)

	BOOL m_bDebug;
	// F5 debugging support for VS7
	BOOL ProcessDebug(__inout AtlServerRequest *pRequestInfo)
{
		ATLENSURE(pRequestInfo);
		static GUID clsidDebugger[] = {
			{0x70F65411, 0xFE8C, 0x4248, {0xBC,0xFF,0x70,0x1C,0x8B,0x2F,0x45,0x29}}, // debugger clsid
			{0x62A78AC2, 0x7D9A, 0x4377, {0xB9,0x7E,0x69,0x65,0x91,0x9F,0xDD,0x02}}, // reserved
			{0xCC23651F, 0x4574, 0x438F, {0xB4,0xAA,0xBC,0xB2,0x8B,0x6B,0x3E,0xCF}}, // reserved
			{0xDBFDB1D0, 0x04A4, 0x4315, {0xB1,0x5C,0xF8,0x74,0xF6,0xB6,0xE9,0x0B}}, // reserved
			{0xA4FCB474, 0x2687, 0x4924, {0xB0,0xAD,0x7C,0xAF,0x33,0x1D,0xB8,0x26}}, // reserved
			{0xBEB261F6, 0xD5F0, 0x43BA, {0xBA,0xF4,0x8B,0x79,0x78,0x5F,0xFF,0xAF}}, // reserved
			{0x8E2F5E28, 0xD4E2, 0x44C0, {0xAA,0x02,0xF8,0xC5,0xBE,0xB7,0x0C,0xAC}}, // reserved
			{0x08100915, 0x0F41, 0x4CCF, {0x95,0x64,0xEB,0xAA,0x5D,0x49,0x44,0x6C}}  // reserved
		};
		_ATLTRY
		{
			if (!_stricmp(pRequestInfo->pServerContext->GetRequestMethod(), "debug"))
			{
				// Debugger must be able to validate the client we are impersonating
				// on an NT Domain so the client needs to use either NTLM or Negotiate.
				DWORD dwAuthTypeSize = 64;
				char szAuthType[64] = { 0 };

				if ( !pRequestInfo->pServerContext->GetServerVariable("AUTH_TYPE", szAuthType, &dwAuthTypeSize) )
				{
					// error retrieving authentication type
					RequestComplete(pRequestInfo, 501, 0);
					return FALSE;
				}

				// if it's empty or not NTLM or negotiate we fail.
				if ( !( *szAuthType != '\0 ' && 
					   ( !_stricmp(szAuthType, "NTLM") ||
					     !_stricmp(szAuthType, "Negotiate"))
					) )
				{
					// wrong authorization type or not authorized
					RequestComplete(pRequestInfo, 401, 0);
					return FALSE;
				}
				
				DWORD dwHeadersLen = 0;
				CStringA strHeaders;
				pRequestInfo->pServerContext->GetServerVariable("ALL_HTTP", NULL, &dwHeadersLen);
				BOOL bRet = pRequestInfo->pServerContext->GetServerVariable("ALL_HTTP", strHeaders.GetBuffer(dwHeadersLen), &dwHeadersLen);
				if (!bRet)
				{
					RequestComplete(pRequestInfo, 501, 0);
					return FALSE;
				}
				strHeaders.ReleaseBuffer(dwHeadersLen - 1);
				LPCSTR szCur = strHeaders;

				while(*szCur)
				{
					if (!strncmp(szCur, "HTTP_COMMAND:", 13))
					{
						szCur += 13;
						break;
					}

					szCur = strchr(szCur, '\n');
					if (!szCur)
					{
						RequestComplete(pRequestInfo, 501, 0);
						return FALSE;
					}

					szCur++;
				}


				if (!_strnicmp(szCur, "start-debug", sizeof("start-debug")-sizeof('\0')))
				{
					CCritSecLock Lock(m_critSec.m_sec);
					if (m_bDebug)
					{
						HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_ALREADY_DEBUGGING);
						RequestComplete(pRequestInfo, 204, DBG_SUBERR_ALREADY_DEBUGGING);   // Already being debugged by another process
						return FALSE;
					}
					CHttpRequest HttpRequest;
					HttpRequest.Initialize(pRequestInfo->pServerContext);
					HttpRequest.InitFromPost();
					LPCSTR szString;
					szString = HttpRequest.FormVars.Lookup("DebugSessionID");
					if (!szString || !*szString)
					{
						HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_INVALID_SESSION);
						RequestComplete(pRequestInfo, 204, DBG_SUBERR_INVALID_SESSION);
						return FALSE;
					}
					CA2W szSessionID(szString);
					if (!szSessionID)
					{
						HandleError(pRequestInfo->pServerContext, 500, ISE_SUBERR_OUTOFMEM);
						RequestComplete(pRequestInfo, 500, ISE_SUBERR_OUTOFMEM);
						return FALSE;
					}
					DWORD dwPid = GetCurrentProcessId();
					LPWSTR szPoint = szSessionID;
					while (szPoint && *szPoint && wcsncmp(szPoint, L"autoattachclsid=", 16))
					{
						szPoint = wcschr(szPoint, ';');
						if (szPoint)
							szPoint++;
					}

					if (!szPoint || !*szPoint)
					{
						HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_BAD_ID);
						RequestComplete(pRequestInfo, 204, DBG_SUBERR_BAD_ID);
						return FALSE;
					}

					szPoint += (sizeof("autoattachclsid=") - 1);
					WCHAR szClsid[39];
					szClsid[38] = '\0';
					Checked::wcsncpy_s(szClsid, _countof(szClsid), szPoint, _TRUNCATE);
					if (szClsid[38] != '\0')
					{
						HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_BAD_ID);
						RequestComplete(pRequestInfo, 204, DBG_SUBERR_BAD_ID);
						return FALSE;
					}
					szClsid[38] = '\0';

					CLSID clsidDebugAutoAttach = CLSID_NULL;
					HRESULT hr = CLSIDFromString(szClsid, &clsidDebugAutoAttach);

					if (hr != S_OK)
					{
						HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_BAD_ID);
						RequestComplete(pRequestInfo, 204, DBG_SUBERR_BAD_ID);
						return FALSE;
					}

					size_t i=0,
						   nArrSize = sizeof(clsidDebugger)/sizeof(GUID);
					for (i=0; i<nArrSize; i++)
					{
						if( InlineIsEqualGUID(clsidDebugAutoAttach, clsidDebugger[i]) )
							break;
					}
					if (i >= nArrSize)
					{
						HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_BAD_ID);
						RequestComplete(pRequestInfo, 204, DBG_SUBERR_BAD_ID);
						return FALSE;
					}

					CComPtr<IDebugAutoAttach> spDebugAutoAttach;
					hr = CoCreateInstance(clsidDebugAutoAttach, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER, __uuidof(IDebugAutoAttach), (void**)&spDebugAutoAttach);
					if (FAILED(hr))
					{
						if (hr == E_ACCESSDENIED)
							RequestComplete(pRequestInfo, 401, 0);
						else
						{
							HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_COCREATE);
							RequestComplete(pRequestInfo, 204, DBG_SUBERR_COCREATE);
						}
						return FALSE;
					}
					hr = spDebugAutoAttach->AutoAttach(GUID_NULL, dwPid, AUTOATTACH_PROGRAM_WIN32, 0, szSessionID);
					if (FAILED(hr))
					{
						char szRetBuf[256];
						int nLen = sprintf_s(szRetBuf, _countof(szRetBuf), "204 HRESULT=0x%.08X;ErrorString=Unable to attach to worker process", hr);
						if (nLen > 0)
						{
							DWORD dwLen = nLen;
							pRequestInfo->pServerContext->SendResponseHeader(NULL, szRetBuf, FALSE);
							pRequestInfo->pServerContext->WriteClient(szRetBuf, &dwLen);
							RequestComplete(pRequestInfo, 204, DBG_SUBERR_ATTACH);
						}
						return FALSE;
					}
					m_bDebug = TRUE;
					HandleError(pRequestInfo->pServerContext, 200, SUBERR_NONE);
					RequestComplete(pRequestInfo, 200, SUBERR_NONE);
					return FALSE;
				}
				else if (!_strnicmp(szCur, "stop-debug", sizeof("stop-debug")-sizeof('\0')))
				{
					m_bDebug = FALSE;
					HandleError(pRequestInfo->pServerContext, 200, SUBERR_NONE);
					RequestComplete(pRequestInfo, 200, SUBERR_NONE);
					return FALSE;
				}
				else
				{
					RequestComplete(pRequestInfo, 501, SUBERR_NONE);   // Not Implemented
					return FALSE;
				}
			}
			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}
#endif // defined(_DEBUG) || defined(ATLS_ENABLE_DEBUGGING)

	BOOL DispatchStencilCall(__inout AtlServerRequest *pRequestInfo)
	{
		ATLENSURE(pRequestInfo!=NULL);
		CSetThreadToken sec;

		m_reqStats.OnRequestDequeued();

		if (!sec.Initialize(pRequestInfo))
		{
			RequestComplete(pRequestInfo, 500, ISE_SUBERR_IMPERSONATIONFAILED);
			return FALSE;
		}

#if defined(_DEBUG) || defined(ATLS_ENABLE_DEBUGGING)
		if (!ProcessDebug(pRequestInfo))
			return TRUE;
#endif // defined(ATLS_ENABLE_DEBUGGING)

		if (pRequestInfo->m_hMutex)
		{
			// synchronize in case the previous async_noflush call isn't finished
			// setting up state for the next call.
			DWORD dwStatus = WaitForSingleObject(pRequestInfo->m_hMutex, ATLS_ASYNC_MUTEX_TIMEOUT);
			if (dwStatus != WAIT_OBJECT_0 && dwStatus != WAIT_ABANDONED)
			{
				RequestComplete(pRequestInfo, 500, ISE_SUBERR_UNEXPECTED);
				return FALSE;
			}
		}

#ifdef _DEBUG
		bool bAsyncAllowed = false;
#endif
		HTTP_CODE hcErr = HTTP_SUCCESS;
		if (pRequestInfo->dwRequestState == ATLSRV_STATE_BEGIN)
		{
			BOOL bAllowCaching = TRUE;
			if (TransmitFromCache(pRequestInfo, &bAllowCaching))    // Page is in the cache, send it and bail
			{                                       // Async Callback will handle freeing pRequestInfo
				return TRUE;
			}

			// get the srf filename
			LPCSTR szFileName = pRequestInfo->pServerContext->GetScriptPathTranslated();

			if (!szFileName)
			{
				RequestComplete(pRequestInfo, 500, ISE_SUBERR_UNEXPECTED);
				return FALSE;
			}

			LPCSTR szDot = szFileName + strlen(szFileName) - 1;

			// load a handler
			if (AsciiStricmp(szDot - ATLS_EXTENSION_LEN, c_AtlSRFExtension) == 0)
			{
				pRequestInfo->dwRequestType = ATLSRV_REQUEST_STENCIL;
				hcErr = LoadDispatchFile(szFileName, pRequestInfo);
			}
			else if (AsciiStricmp(szDot - ATLS_DLL_EXTENSION_LEN, c_AtlDLLExtension) == 0)
			{
				pRequestInfo->dwRequestType = ATLSRV_REQUEST_DLL;
				hcErr = LoadDllHandler(szFileName, pRequestInfo);
			}
			else
			{
				hcErr = HTTP_FAIL;
			}

			if (hcErr)
			{
				RequestComplete(pRequestInfo, HTTP_ERROR_CODE(hcErr), HTTP_SUBERROR_CODE(hcErr));
				return TRUE;
			}

			pRequestInfo->pfnHandleRequest = &IRequestHandler::HandleRequest;

			// initialize the handler
			DWORD dwStatus = 0;

			hcErr = pRequestInfo->pHandler->GetFlags(&dwStatus);
			if (hcErr)
			{
				RequestComplete(pRequestInfo, HTTP_ERROR_CODE(hcErr), HTTP_SUBERROR_CODE(hcErr));
				return FALSE;
			}

			if (bAllowCaching && ((dwStatus & ATLSRV_INIT_USECACHE) != 0) &&
				!strcmp(pRequestInfo->pServerContext->GetRequestMethod(), "GET"))
			{
				CComPtr<IHttpServerContext> spCacheCtx;
				if (!GetCacheServerContext(pRequestInfo, &m_PageCache, &spCacheCtx) ||
					!spCacheCtx)
				{
					RequestComplete(pRequestInfo, 500, ISE_SUBERR_OUTOFMEM);
					return FALSE;
				}

				pRequestInfo->pServerContext->Release();
				pRequestInfo->pServerContext = spCacheCtx.Detach();
			}

			if (dwStatus & (ATLSRV_INIT_USEASYNC | ATLSRV_INIT_USEASYNC_EX))
			{
#ifdef _DEBUG
				bAsyncAllowed = true;
#endif
				if (!pRequestInfo->pServerContext->RequestIOCompletion(AsyncCallback, (DWORD *)pRequestInfo))
				{
					RequestComplete(pRequestInfo, 500, SUBERR_NONE);
					return FALSE;
				}
			}

			if (dwStatus & ATLSRV_INIT_USEASYNC_EX)
			{
				pRequestInfo->m_hMutex = CreateMutex(NULL, FALSE, NULL);
				if (pRequestInfo->m_hMutex == NULL)
				{
					RequestComplete(pRequestInfo, 500, ISE_SUBERR_SYSOBJFAIL);
					return FALSE;
				}

				DWORD dwMutexStatus = WaitForSingleObject(pRequestInfo->m_hMutex, 10000);
				if (dwMutexStatus != WAIT_OBJECT_0 && dwMutexStatus != WAIT_ABANDONED)
				{
					RequestComplete(pRequestInfo, 500, ISE_SUBERR_UNEXPECTED);
					return FALSE;
				}
			}
			hcErr = pRequestInfo->pHandler->InitializeHandler(pRequestInfo, static_cast<IServiceProvider*>(this));
		}
#ifdef _DEBUG
		else // pRequestInfo->dwRequestState != ATLSRV_STATE_BEGIN
		{
			bAsyncAllowed = true;
		}
#endif

		ATLENSURE(pRequestInfo->pfnHandleRequest != NULL);

		if (hcErr == HTTP_SUCCESS)
		   hcErr = (pRequestInfo->pHandler->*pRequestInfo->pfnHandleRequest)(pRequestInfo, static_cast<IServiceProvider*>(this));

	   if (hcErr == HTTP_SUCCESS_NO_CACHE)
		{
			CComPtr<IPageCacheControl> spControl;
			HRESULT hr = pRequestInfo->pServerContext->QueryInterface(__uuidof(IPageCacheControl), reinterpret_cast<void**>(&spControl));
			if (hr == S_OK)
				spControl->Cache(FALSE);
		}

#ifdef _DEBUG
		// must use ATLSRV_INIT_USEASYNC to use ASYNC returns
		if (IsAsyncStatus(hcErr))
			ATLASSERT(bAsyncAllowed);

		// must use ATLSRV_INIT_USEASYNC_EX to use NOFLUSH returns
		if (IsAsyncNoFlushStatus(hcErr))
			ATLASSERT(pRequestInfo->m_hMutex);
#endif

		// save hMutex in case pRequestInfo is deleted by AsyncCallback after
		// we call StartAsyncFlush but before we check to see if we need to
		// call ReleaseMutex
		HANDLE hMutex = pRequestInfo->m_hMutex;

		if (IsAsyncStatus(hcErr))
		{
			if (IsAsyncDoneStatus(hcErr))
				pRequestInfo->dwRequestState = ATLSRV_STATE_DONE;
			else
				pRequestInfo->dwRequestState = ATLSRV_STATE_CONTINUE;

			if (IsAsyncFlushStatus(hcErr) && !StartAsyncFlush(pRequestInfo))
			{
				RequestComplete(pRequestInfo, 500, SUBERR_NONE);
				pRequestInfo = NULL;
			}
		}
		else
		{
			RequestComplete(pRequestInfo, HTTP_ERROR_CODE(hcErr), HTTP_SUBERROR_CODE(hcErr));
			pRequestInfo = NULL;
		}

		if (hMutex)
			ReleaseMutex(hMutex);

		return TRUE;
	}

	BOOL StartAsyncFlush(__in AtlServerRequest *pRequestInfo)
	{
		ATLENSURE(pRequestInfo);
		if (pRequestInfo->pszBuffer == NULL || pRequestInfo->dwBufferLen == 0)
		{
			ATLASSERT(FALSE);
			return FALSE;
		}

		ATLENSURE(pRequestInfo->pServerContext);
		BOOL bRet = TRUE;
		_ATLTRY
		{
			bRet = pRequestInfo->pServerContext->AsyncWriteClient(
				LPVOID(pRequestInfo->pszBuffer),
				&pRequestInfo->dwBufferLen);
		}
		_ATLCATCHALL()
		{
			bRet = FALSE;
		}

		return bRet;
	}

	long GetTotalRequests()
	{
		return m_reqStats.GetTotalRequests();
	}

	long GetFailedRequests()
	{
		return m_reqStats.GetFailedRequests();
	}

	long GetAvgResponseTime()
	{
		return m_reqStats.GetAvgResponseTime();
	}

	long GetCurrWaiting()
	{
		return m_reqStats.GetCurrWaiting();
	}

	long GetMaxWaiting()
	{
		return m_reqStats.GetMaxWaiting();
	}

	long GetActiveThreads()
	{
		return m_reqStats.GetActiveThreads();
	}

	__success(SUCCEEDED(return)) __checkReturn HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, __deref_out void **ppv)
	{
		if (!ppv)
			return E_POINTER;
		if (InlineIsEqualGUID(riid, __uuidof(IRequestStats)))
		{
			*ppv = static_cast<IRequestStats*>(this);
			AddRef();
			return S_OK;
		}
		if (InlineIsEqualGUID(riid, __uuidof(IUnknown)) ||
			InlineIsEqualGUID(riid, __uuidof(IServiceProvider)))
		{
			*ppv = static_cast<IServiceProvider*>(this);
			AddRef();
			return S_OK;
		}
		if (InlineIsEqualGUID(riid, __uuidof(IIsapiExtension)))
		{
			*ppv = static_cast<IIsapiExtension*>(this);
			AddRef();
			return S_OK;
		}
		return E_NOINTERFACE;
	}

	ULONG STDMETHODCALLTYPE AddRef()
	{
		return 1;
	}

	ULONG STDMETHODCALLTYPE Release()
	{
		return 1;
	}

	virtual __checkReturn HRESULT STDMETHODCALLTYPE QueryService(
		__in REFGUID guidService,
		__in REFIID riid,
		__deref_out void **ppvObject)
	{
		if (!ppvObject)
			return E_POINTER;

		if (InlineIsEqualGUID(guidService, __uuidof(IDllCache)))
			return m_DllCache.QueryInterface(riid, ppvObject);
		else if (InlineIsEqualGUID(guidService, __uuidof(IStencilCache)))
			return m_StencilCache.QueryInterface(riid, ppvObject);
		else if (InlineIsEqualGUID(guidService, __uuidof(IThreadPoolConfig)))
			return m_ThreadPool.QueryInterface(riid, ppvObject);
		else if (InlineIsEqualGUID(guidService, __uuidof(IAtlMemMgr)))
		{
			*ppvObject = static_cast<IAtlMemMgr *>(&m_heap);
			return S_OK;
		}
#ifndef ATL_NO_SOAP
		else if (InlineIsEqualGUID(guidService, __uuidof(ISAXXMLReader)))
		{
			CIsapiWorker *p = GetThreadWorker();
			ATLENSURE( p != NULL );
			return p->m_spReader->QueryInterface(riid, ppvObject);
		}
#endif

		// otherwise look it up in the servicemap
		return GetService(guidService, riid, ppvObject);
	}

	virtual HRESULT AddService(REFGUID guidService, REFIID riid, IUnknown *punkService, HINSTANCE hInstance)
	{
		if (!punkService)
			return E_INVALIDARG;

		if (!m_DllCache.AddRefModule(hInstance))
			return E_FAIL;

		ServiceNode srvNode;
		srvNode.hInst = hInstance;
		srvNode.punk = punkService;
		Checked::memcpy_s(&srvNode.guidService, sizeof(GUID), &guidService, sizeof(guidService));
		Checked::memcpy_s(&srvNode.riid, sizeof(IID), &riid, sizeof(riid));

		CComCritSecLock<CComCriticalSection> lock(m_critSec, false);
		HRESULT hr = lock.Lock();
		if (FAILED(hr))
		{
			return hr;
		}

		// if the service is already there, return S_FALSE
		int nIndex = m_serviceMap.Find(srvNode);
		if (nIndex >= 0)
			return S_FALSE;

		if (!m_serviceMap.Add(srvNode))
			return E_OUTOFMEMORY;

		punkService->AddRef();
		return S_OK;
	}

	virtual HRESULT RemoveService(__in REFGUID guidService, __in REFIID riid)
	{
		ServiceNode srvNode;
		Checked::memcpy_s(&srvNode.guidService, sizeof(GUID), &guidService, sizeof(guidService));
		Checked::memcpy_s(&srvNode.riid, sizeof(IID), &riid, sizeof(riid));

		CComCritSecLock<CComCriticalSection> lock(m_critSec, false);
		HRESULT hr = lock.Lock();
		if (FAILED(hr))
		{
			return hr;
		}

		int nIndex = m_serviceMap.Find(srvNode);
		if (nIndex < 0)
			return S_FALSE;

		ATLASSUME(m_serviceMap[nIndex].punk != NULL);
		m_serviceMap[nIndex].punk->Release();

		HINSTANCE hInstRemove = m_serviceMap[nIndex].hInst;

		m_serviceMap.RemoveAt(nIndex);

		if (!m_DllCache.ReleaseModule(hInstRemove))
			return S_FALSE;

		return S_OK;
	}

	__success(SUCCEEDED(return)) __checkReturn HRESULT GetService(__in REFGUID guidService, __in REFIID riid, __deref_out void **ppvObject) throw()
	{
		if (!ppvObject)
			return E_POINTER;

		*ppvObject = NULL;
		if (!m_serviceMap.GetSize())
			return E_NOINTERFACE;

		ServiceNode srvNode;
		Checked::memcpy_s(&srvNode.guidService, sizeof(GUID), &guidService, sizeof(guidService));
		Checked::memcpy_s(&srvNode.riid, sizeof(IID), &riid, sizeof(riid));

		CComCritSecLock<CComCriticalSection> lock(m_critSec, false);
		HRESULT hr = lock.Lock();
		if (FAILED(hr))
		{
			return hr;
		}

		int nIndex = m_serviceMap.Find(srvNode);
		if (nIndex < 0)
			return E_NOINTERFACE;

		ATLASSUME(m_serviceMap[nIndex].punk != NULL);
		return m_serviceMap[nIndex].punk->QueryInterface(riid, ppvObject);
	}

	HTTP_CODE LoadRequestHandler(__in LPCSTR szDllPath, __in LPCSTR szHandlerName, __in IHttpServerContext *pServerContext,
		__out HINSTANCE *phInstance, __deref_out IRequestHandler **ppHandler)
	{
		return _AtlLoadRequestHandler(szDllPath, szHandlerName, pServerContext, 
			phInstance, ppHandler, this, static_cast<IDllCache*>(&m_DllCache));
	} // LoadRequestHandler

}; // class CIsapiExtension


//===========================================================================================
// IMPORTANT NOTE TO USERS: 
// DO NOT ASSUME *ANYTHING* ABOUT THE STRUCTURE OF THESE MAPS/ENTRIES/FUNCTIONS--THEY CAN 
// AND *WILL* CHANGE IN THE FUTURE. CORRECT USAGE MANDATES THAT YOU USE THE MACROS PROVIDED.
// ABSOLUTELY NO GUARANTEES ABOUT BACKWARD COMPATABILITY ARE MADE FOR MANUALLY DEFINED 
// HANDLERS OR FUNCTIONS.
//===========================================================================================

typedef BOOL (*CREATEHANDLERFUNC)(IIsapiExtension *pExtension, IUnknown **ppOut);
typedef BOOL (*INITHANDLERFUNC)(IHttpServerContext*, IIsapiExtension*);
typedef void (*UNINITHANDLERFUNC)();

struct _HANDLER_ENTRY
{
	LPCSTR szName;
	CREATEHANDLERFUNC pfnCreate;
	INITHANDLERFUNC pfnInit;
	UNINITHANDLERFUNC pfnUninit;
};
// definitions of data segments and _HANDLER_ENTRY delimiters
#pragma section("ATLS$A", read, shared)
#pragma section("ATLS$Z", read, shared)
#pragma section("ATLS$C", read, shared)
extern "C"
{
__declspec(selectany) __declspec(allocate("ATLS$A")) ATL::_HANDLER_ENTRY * __phdlrA = NULL;
__declspec(selectany) __declspec(allocate("ATLS$Z")) ATL::_HANDLER_ENTRY * __phdlrZ = NULL;
}

#if !defined(_M_IA64)
#pragma comment(linker, "/merge:ATLS=.rdata")
#endif

#ifndef HANDLER_ENTRY_PRAGMA

#if defined(_M_IX86)
#define HANDLER_ENTRY_PRAGMA(class, line) __pragma(comment(linker, "/include:___phdlrEntry_" #class "_" #line));
#elif defined(_M_IA64)
#define HANDLER_ENTRY_PRAGMA(class, line) __pragma(comment(linker, "/include:__phdlrEntry_" #class "_" #line));
#elif defined(_M_AMD64)
#define HANDLER_ENTRY_PRAGMA(class, line) __pragma(comment(linker, "/include:__phdlrEntry_" #class "_" #line));
#else
#error Unknown Platform. define HANDLER_ENTRY_PRAGMA
#endif

#endif // HANDLER_ENTRY_PRAGMA

// DECLARE_REQUEST_HANDLER macro
#define __DECLARE_REQUEST_HANDLER_INTERNAL(handlerName, className, classQName, lineNum) \
__declspec(selectany) ATL::_HANDLER_ENTRY __hdlrEntry_ ## className ## _ ## lineNum = { handlerName, classQName::CreateRequestHandler, classQName::InitRequestHandlerClass, classQName::UninitRequestHandlerClass }; \
extern "C" __declspec(allocate("ATLS$C")) __declspec(selectany) \
ATL::_HANDLER_ENTRY * const __phdlrEntry_ ## className ## _ ## lineNum = &__hdlrEntry_ ## className ## _ ## lineNum; \
HANDLER_ENTRY_PRAGMA(className, lineNum) \
__if_not_exists(GetAtlHandlerByName) \
{ \
extern "C" ATL_NOINLINE inline BOOL __declspec(dllexport) __stdcall GetAtlHandlerByName(LPCSTR szHandlerName, IIsapiExtension *pExtension, IUnknown **ppHandler) throw() \
{ \
	*ppHandler = NULL; \
	ATL::_HANDLER_ENTRY **pEntry = &__phdlrA; \
	while (pEntry != &__phdlrZ) \
	{ \
		if (*pEntry && (*pEntry)->szName) \
		{ \
			if (strcmp((*pEntry)->szName, szHandlerName)==0) \
			{ \
				return (*(*pEntry)->pfnCreate)(pExtension, ppHandler); \
			} \
		} \
		pEntry++; \
	} \
	return FALSE; \
} \
extern "C" ATL_NOINLINE inline  BOOL __declspec(dllexport) __stdcall InitializeAtlHandlers(IHttpServerContext *pContext, IIsapiExtension *pExt) throw() \
{ \
	ATL::_HANDLER_ENTRY **pEntry = &__phdlrA; \
	BOOL bRet = TRUE; \
	while (pEntry != &__phdlrZ) \
	{ \
		if (*pEntry && (*pEntry)->szName && (*pEntry)->pfnInit) \
		{ \
			bRet = (*(*pEntry)->pfnInit)(pContext, pExt); \
			if (!bRet) \
				break; \
		} \
		pEntry++; \
	} \
	if (!bRet) \
	{ \
		if (pEntry == &__phdlrA) \
			return FALSE; \
		do \
		{ \
			pEntry--; \
			if(*pEntry) \
				(*(*pEntry)->pfnUninit)(); \
		} \
		while (pEntry != &__phdlrA); \
	} \
	return bRet; \
} \
extern "C" ATL_NOINLINE inline void __declspec(dllexport) __stdcall UninitializeAtlHandlers() throw() \
{\
	ATL::_HANDLER_ENTRY **pEntry = &__phdlrA; \
	while (pEntry != &__phdlrZ) \
	{ \
		if (*pEntry && (*pEntry)->szName && (*pEntry)->pfnUninit) \
		{ \
			(*(*pEntry)->pfnUninit)(); \
		} \
		pEntry++; \
	} \
} \
}

#define __DECLARE_REQUEST_HANDLER(handlerName, className, classQName, lineNum) __DECLARE_REQUEST_HANDLER_INTERNAL(handlerName, className, classQName, lineNum)
#define DECLARE_REQUEST_HANDLER(handlerName, className, classQName) __DECLARE_REQUEST_HANDLER(handlerName, className, classQName, __COUNTER__)

#define BEGIN_HANDLER_MAP()
#define HANDLER_ENTRY(handlerName, className) DECLARE_REQUEST_HANDLER(handlerName, className, className)
#define HANDLER_ENTRY_EX(handlerName, className, classQName) DECLARE_REQUEST_HANDLER(handlerName, className, classQName)
#define END_HANDLER_MAP()

#define __HANDLER_ENTRY_SDL_INTERNAL(handlerString, handlerClass, handlerQClass, sdlClassName, lineNum)\
_ATLSOAP_DECLARE_WSDL_SRF() \
extern __declspec(selectany) const char const s_szClassName##sdlClassName##lineNum[]=handlerString;\
typedef ATL::CSDLGenerator<handlerQClass, s_szClassName##sdlClassName##lineNum> sdlClassName; \
HANDLER_ENTRY_EX(handlerString, handlerClass, handlerQClass)\
HANDLER_ENTRY(#sdlClassName, sdlClassName)

#define __HANDLER_ENTRY_SDL(handlerString, handlerClass, handlerQClass, sdlClassName, lineNum) __HANDLER_ENTRY_SDL_INTERNAL(handlerString, handlerClass, handlerQClass, sdlClassName, lineNum)
#define HANDLER_ENTRY_SDL(handlerString, handlerClass, handlerQClass, sdlClassName) __HANDLER_ENTRY_SDL(handlerString, handlerClass, handlerQClass, sdlClassName, __COUNTER__)
// 
// Use this class to check the authorization level of a client who is making
// a request to this application. This class checks for the stronger authentication
// levels (NTLM and Negotiate). You can call it directly from an implementation
// of HandleRequest to check authorization before handling a request.
#define MAX_AUTH_TYPE 50
#define MAX_NAME_LEN 255

template <class T>
class CVerifyAuth
{
public:
	HTTP_CODE IsAuthorized(__in AtlServerRequest *pInfo, __in_opt const SID* psidAuthGroup)
	{
		ATLENSURE(pInfo);
		ATLENSURE(pInfo->pServerContext);
		ATLASSERT(psidAuthGroup);
		ATLASSERT(::IsValidSid((PSID) psidAuthGroup));

		HTTP_CODE hcErr = HTTP_UNAUTHORIZED;
		char szAuthType[MAX_AUTH_TYPE];
		DWORD dwSize = MAX_AUTH_TYPE;
		_ATLTRY
		{
			if (pInfo->pServerContext->GetServerVariable("AUTH_TYPE", 
				szAuthType, &dwSize))
			{
				if (szAuthType[0] && (!_stricmp(szAuthType, "NTLM") 
					|| !_stricmp(szAuthType, "Negotiate")))
				{
					// if we were passed a group name
					// we check to see that the logged on user is part
					// of that group, else we just return success.
					if (psidAuthGroup)
					{
						T* pT = static_cast<T*>(this);
						if (pT->CheckAccount(pInfo->pServerContext, psidAuthGroup))
							hcErr = HTTP_SUCCESS;
						else
							hcErr = pT->HandleError(pInfo);
					}
					else
						hcErr = HTTP_SUCCESS;
				}
			}
		}
		_ATLCATCHALL()
		{
			hcErr = HTTP_FAIL;
		}

		return hcErr;
	}

	virtual bool CheckAccount(IHttpServerContext *pContext, const SID *psidAuthGroup) throw()
	{
		(pContext); // unused
		(psidAuthGroup); // unused
		return false;
	}

	HTTP_CODE HandleError(AtlServerRequest *pRequestInfo) throw()
	{
		pRequestInfo; // unused
		return HTTP_FAIL;
	}

	__checkReturn bool CheckAuthAccount(__in IHttpServerContext *pContext, __in const SID* psidAuthGroup) throw()
	{
		ATLASSERT(pContext);
		ATLASSERT(psidAuthGroup);
		if (!pContext || !psidAuthGroup)
			return false;

		HANDLE hToken = INVALID_HANDLE_VALUE;
		bool bIsMember;
		_ATLTRY
		{
			if (!pContext->GetImpersonationToken(&hToken) ||
							hToken == INVALID_HANDLE_VALUE)
				return false;

			CAccessToken tok;
			tok.Attach(hToken);
			bool bRet = tok.CheckTokenMembership(CSid(psidAuthGroup), &bIsMember);
			tok.Detach();

			if (!bRet)
				return false;
		}
		_ATLCATCHALL()
		{
			bIsMember = false;
		}

		return bIsMember;
	}
};

// Checks that the user that is logging on is in the required group
class CDefaultAuth :
	public CVerifyAuth<CDefaultAuth>
{
public:
	virtual bool CheckAccount(__in IHttpServerContext *pContext, __in const SID* psidAuthGroup) throw()
	{
		return CheckAuthAccount(pContext, psidAuthGroup);
	}

	HTTP_CODE HandleError(__in AtlServerRequest *pRequestInfo) throw()
	{
		ATLASSERT(pRequestInfo); // should always be valid
		if(!pRequestInfo)
		{
			return HTTP_FAIL;
		}

		_ATLTRY
		{
			CHttpResponse response(pRequestInfo->pServerContext);
			response.Write(GetErrorResponse());
			response.Flush();
		}
		_ATLCATCHALL()
		{
			return HTTP_FAIL;
		}

		return HTTP_SUCCESS_NO_PROCESS;
	}

	virtual LPCSTR GetErrorResponse()
	{
		static const char *szResponse = "<html><body>"
			"<H1 align=center>NOT AUTHORIZED</H1><p>"
			"</body></html>";
		return szResponse;
	}

};

} // namespace ATL
#pragma pack(pop)

#pragma warning(pop)

#endif // __ATLISAPI_H__
