mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
9376 lines
292 KiB
9376 lines
292 KiB
// This is a part of the Active Template Library.
|
|
// Copyright (C) 1996-2001 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 <atlbase.inl>
|
|
#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>
|
|
#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)
|
|
#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
|
|
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 4291) // allow placement new
|
|
#pragma warning(disable: 4127)
|
|
#pragma warning(disable: 4511) // copy constructor could not be generated
|
|
#pragma warning(disable: 4512) // assignment operator could not be generated
|
|
#pragma warning(disable: 4702) // unreachable code
|
|
|
|
|
|
|
|
/* REVIEW: Remove these at some point in the future */
|
|
#include <initguid.h>
|
|
#include <dbgautoattach.h>
|
|
|
|
#ifndef SESSION_COOKIE_NAME
|
|
#define SESSION_COOKIE_NAME "SESSIONID"
|
|
#endif
|
|
|
|
#ifndef ATLS_MAX_HTTP_DATE
|
|
#define ATLS_MAX_HTTP_DATE 64
|
|
#endif
|
|
|
|
|
|
// 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(TComp type) throw()
|
|
{
|
|
return type == 0;
|
|
}
|
|
|
|
namespace ATL {
|
|
|
|
// Default file extension for server response files
|
|
#ifndef ATL_DEFAULT_STENCIL_EXTENSION
|
|
#define ATL_DEFAULT_STENCIL_EXTENSION ".srf"
|
|
#endif
|
|
__declspec(selectany) LPCSTR c_AtlSRFExtension = ATL_DEFAULT_STENCIL_EXTENSION;
|
|
__declspec(selectany) LPCTSTR c_tAtlSRFExtension = _T(ATL_DEFAULT_STENCIL_EXTENSION);
|
|
#define ATLS_EXTENSION_LEN (sizeof(ATL_DEFAULT_STENCIL_EXTENSION)-2)
|
|
|
|
// maximum handler name length
|
|
#ifndef ATL_MAX_HANDLER_NAME_LEN
|
|
#define ATL_MAX_HANDLER_NAME_LEN 64
|
|
#endif
|
|
|
|
// maximum timeout for async guard mutex
|
|
#ifndef ATLS_ASYNC_MUTEX_TIMEOUT
|
|
#define ATLS_ASYNC_MUTEX_TIMEOUT 10000
|
|
#endif
|
|
|
|
#if defined(_M_IA64)
|
|
#define ATLS_FUNCID_INITIALIZEHANDLERS "InitializeAtlHandlers"
|
|
#define ATLS_FUNCID_GETATLHANDLERBYNAME "GetAtlHandlerByName"
|
|
#define ATLS_FUNCID_UNINITIALIZEHANDLERS "UninitializeAtlHandlers"
|
|
#else
|
|
#define ATLS_FUNCID_INITIALIZEHANDLERS "_InitializeAtlHandlers@8"
|
|
#define ATLS_FUNCID_GETATLHANDLERBYNAME "_GetAtlHandlerByName@12"
|
|
#define ATLS_FUNCID_UNINITIALIZEHANDLERS "_UninitializeAtlHandlers@0"
|
|
#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
|
|
|
|
|
|
// Returns the number of instances of a particular character between the specified start and end points inclusive.
|
|
inline int CountOf(CHAR c, LPCSTR pBegin, LPCSTR pEnd) throw()
|
|
{
|
|
int nCount = 0;
|
|
while (pBegin < pEnd && *pBegin)
|
|
{
|
|
if (*pBegin == c)
|
|
nCount++;
|
|
pBegin++;
|
|
}
|
|
return nCount;
|
|
}
|
|
|
|
// Call this function to copy a substring to a CString reference and ensure nul-termination.
|
|
ATL_NOINLINE inline bool CopyToCString(CStringA& string, LPCSTR pStart, LPCSTR pEnd) throw()
|
|
{
|
|
_ATLTRY
|
|
{
|
|
if (pStart > pEnd)
|
|
return true; //nothing to do
|
|
int nLen = ((int)(pEnd-pStart))+1;
|
|
LPSTR pBuff = string.GetBuffer(nLen+1);
|
|
if (pBuff)
|
|
{
|
|
memcpy(pBuff, pStart, nLen);
|
|
pBuff[nLen]=0;
|
|
string.ReleaseBuffer(nLen);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 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(CStringA& string, LPCSTR szBuf) throw()
|
|
{
|
|
ATLASSERT( szBuf != NULL );
|
|
|
|
_ATLTRY
|
|
{
|
|
CHAR szEscaped[512];
|
|
LPSTR pszStr = szEscaped;
|
|
DWORD dwLen = 0;
|
|
|
|
while (*szBuf)
|
|
{
|
|
if (dwLen+4 >= 512)
|
|
{
|
|
*pszStr = '\0';
|
|
string+= szEscaped;
|
|
pszStr = szEscaped;
|
|
dwLen = 0;
|
|
}
|
|
if (AtlIsUnsafeUrlChar(*szBuf))
|
|
{
|
|
if (*szBuf == ' ')
|
|
{
|
|
dwLen++;
|
|
*pszStr++ = '+';
|
|
}
|
|
else
|
|
{
|
|
DWORD dwEsc = sprintf(pszStr, "%%%.2X", (unsigned char)*szBuf);
|
|
pszStr+= dwEsc;
|
|
dwLen+= dwEsc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*pszStr++ = *szBuf;
|
|
dwLen++;
|
|
}
|
|
szBuf++;
|
|
}
|
|
|
|
*pszStr = '\0';
|
|
string+= szEscaped;
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// UNICODE overload for EscapeToCString
|
|
// follow specifications detailed in RFC document on
|
|
// Internationalized Uniform Resource Identifiers (IURI)
|
|
inline bool EscapeToCString(CStringA& string, LPCWSTR wszBuf) throw()
|
|
{
|
|
_ATLTRY
|
|
{
|
|
// convert string to UTF8
|
|
CFixedStringT<CStringA, 2048> strConvert;
|
|
|
|
// get the required length for conversion
|
|
int nLen = WideCharToMultiByte(CP_UTF8, 0, wszBuf, -1, NULL, 0, NULL, NULL);
|
|
if (!nLen)
|
|
return false; // error -- most likely CP_UTF8 not supported on the OS (e.g. Win98)
|
|
|
|
// allocate MBCS conversion string
|
|
LPSTR sz = strConvert.GetBuffer(nLen);
|
|
if (!sz)
|
|
return false;
|
|
|
|
// do the UNICODE to UTF8 conversion
|
|
nLen = WideCharToMultiByte(CP_UTF8, 0, wszBuf, -1, sz, nLen, NULL, NULL);
|
|
if (!nLen)
|
|
return false;
|
|
|
|
// null-terminate
|
|
sz[nLen] = '\0';
|
|
|
|
// delegate to ANSI version of EscapeToCString
|
|
if (!EscapeToCString(string, sz))
|
|
return false;
|
|
|
|
strConvert.ReleaseBuffer();
|
|
}
|
|
_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(UINT uError, UINT uSubErr, LPCSTR *ppszHeader, 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},
|
|
|
|
{ 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>
|
|
CStringA GetStatusHeader(DWORD dwStatus, DWORD dwSubStatus, HttpUserErrorTextProvider* pErrorProvider, UINT *puResId = NULL) throw(...)
|
|
{
|
|
pErrorProvider;
|
|
|
|
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 = "";
|
|
|
|
CStringA strStatus;
|
|
strStatus.Format("%d %s", dwStatus, szHeadErr);
|
|
return strStatus;
|
|
}
|
|
|
|
template<class HttpUserErrorTextProvider>
|
|
void RenderError(IHttpServerContext *pServerContext, DWORD dwStatus, DWORD dwSubStatus, HttpUserErrorTextProvider* pErrorProvider) throw()
|
|
{
|
|
_ATLTRY
|
|
{
|
|
UINT uResId = 0;
|
|
|
|
CStringA strStatus = GetStatusHeader(dwStatus, dwSubStatus, pErrorProvider, &uResId);
|
|
pServerContext->SendResponseHeader(NULL, strStatus, FALSE);
|
|
|
|
CStringA strBody = strStatus;
|
|
if (uResId)
|
|
{
|
|
// load the body string from a resource
|
|
CStringA strTemp;
|
|
if (strTemp.LoadString(uResId))
|
|
{
|
|
strBody = strTemp;
|
|
}
|
|
}
|
|
|
|
DWORD dwBodyLen = strBody.GetLength();
|
|
pServerContext->WriteClient((void *) (LPCSTR) strBody, &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);
|
|
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(
|
|
LPCSTR szFile,
|
|
LPSTR szFullFileName,
|
|
IHttpServerContext* pServerContext) throw()
|
|
{
|
|
ATLASSERT(szFile != NULL);
|
|
ATLASSERT(szFullFileName != NULL);
|
|
|
|
char szTmpScriptPath[MAX_PATH+1];
|
|
LPCSTR szTmp = pServerContext->GetScriptPathTranslated();
|
|
|
|
if (!szTmp)
|
|
{
|
|
return FALSE;
|
|
}
|
|
strcpy(szTmpScriptPath, szTmp);
|
|
|
|
CHAR *szScriptPath = szTmpScriptPath;
|
|
|
|
LPSTR szBackslash;
|
|
if (*szFile != '\\')
|
|
{
|
|
szBackslash = strrchr(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);
|
|
|
|
if (nScriptPathLen + nFileLen > MAX_PATH)
|
|
{
|
|
return FALSE;
|
|
}
|
|
CHAR szTemp[MAX_PATH + 1];
|
|
if (nScriptPathLen)
|
|
memcpy(szTemp, szScriptPath, nScriptPathLen);
|
|
memcpy(szTemp + nScriptPathLen, szFile, nFileLen);
|
|
*(szTemp + nScriptPathLen + nFileLen) = 0;
|
|
PathCanonicalizeA(szFullFileName, szTemp);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
interface IStencilCache;
|
|
|
|
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(AtlServerRequest* pRequest) throw()
|
|
{
|
|
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;
|
|
}
|
|
|
|
~CIsapiWorker() throw()
|
|
{
|
|
ATLASSERT(m_hHeap == NULL);
|
|
}
|
|
|
|
virtual BOOL Initialize(void *pvParam) throw(...)
|
|
{
|
|
IIsapiExtension* pExtension = (IIsapiExtension*) pvParam;
|
|
ATLASSERT(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(__uuidof(SAXXMLReader30))))
|
|
{
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
return pExtension->SetThreadWorker(this);
|
|
}
|
|
|
|
virtual void Terminate(void* pvParam) throw()
|
|
{
|
|
if (m_hHeap)
|
|
{
|
|
if (HeapDestroy(m_hHeap))
|
|
m_hHeap = NULL;
|
|
else
|
|
{
|
|
ATLASSERT(FALSE);
|
|
}
|
|
}
|
|
|
|
#ifndef ATL_NO_SOAP
|
|
m_spReader.Release();
|
|
#endif
|
|
|
|
(static_cast<IIsapiExtension*>(pvParam))->OnThreadTerminate();
|
|
}
|
|
|
|
void Execute(AtlServerRequest *pRequestInfo, void *pvParam, OVERLAPPED *pOverlapped) throw()
|
|
{
|
|
ATLASSERT(pRequestInfo != NULL);
|
|
ATLASSERT(pvParam != NULL);
|
|
pOverlapped; // unused
|
|
ATLASSERT(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(
|
|
LPCSTR szPathTranslated,
|
|
CFixedStringT<CStringA, MAX_PATH>& strScriptPathTranslated) throw()
|
|
{
|
|
LPCSTR szEnd = szPathTranslated;
|
|
|
|
while (TRUE)
|
|
{
|
|
while (*szEnd != '.' && *szEnd != '\0')
|
|
szEnd++;
|
|
if (*szEnd == '\0')
|
|
break;
|
|
|
|
szEnd++;
|
|
|
|
size_t nLen(0);
|
|
if (!_strnicmp(szEnd, "dll", sizeof("dll")-sizeof('\0')))
|
|
nLen = 3;
|
|
else if (!_strnicmp(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 = strScriptPathTranslated.GetBuffer(dwResult);
|
|
if (szScriptPathTranslated)
|
|
{
|
|
memcpy(szScriptPathTranslated, 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()
|
|
{
|
|
}
|
|
|
|
CWrappedServerContext(IHttpServerContext *pParent) throw()
|
|
{
|
|
m_spParent = pParent;
|
|
}
|
|
|
|
LPCSTR GetRequestMethod() throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->GetRequestMethod();
|
|
}
|
|
|
|
LPCSTR GetQueryString() throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->GetQueryString();
|
|
}
|
|
|
|
LPCSTR GetPathInfo() throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->GetPathInfo();
|
|
}
|
|
|
|
LPCSTR GetScriptPathTranslated() throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->GetScriptPathTranslated();
|
|
}
|
|
|
|
LPCSTR GetPathTranslated() throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->GetPathTranslated();
|
|
}
|
|
|
|
DWORD GetTotalBytes() throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->GetTotalBytes();
|
|
}
|
|
|
|
DWORD GetAvailableBytes() throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->GetAvailableBytes();
|
|
}
|
|
|
|
BYTE *GetAvailableData() throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->GetAvailableData();
|
|
}
|
|
|
|
LPCSTR GetContentType() throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->GetContentType();
|
|
}
|
|
|
|
BOOL GetServerVariable(LPCSTR pszVariableName, LPSTR pvBuffer, DWORD *pdwSize) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->GetServerVariable(pszVariableName, pvBuffer, pdwSize);
|
|
}
|
|
|
|
BOOL WriteClient(void *pvBuffer, DWORD *pdwBytes) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->WriteClient(pvBuffer, pdwBytes);
|
|
}
|
|
|
|
BOOL AsyncWriteClient(void * pvBuffer, DWORD * pdwBytes) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->AsyncWriteClient(pvBuffer, pdwBytes);
|
|
}
|
|
|
|
BOOL ReadClient(void * pvBuffer, DWORD * pdwSize) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->ReadClient(pvBuffer, pdwSize);
|
|
}
|
|
|
|
BOOL AsyncReadClient(void * pvBuffer, DWORD * pdwSize) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->AsyncReadClient(pvBuffer, pdwSize);
|
|
}
|
|
|
|
BOOL SendRedirectResponse(LPCSTR pszRedirectUrl) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->SendRedirectResponse(pszRedirectUrl);
|
|
}
|
|
|
|
BOOL GetImpersonationToken(HANDLE * pToken) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->GetImpersonationToken(pToken);
|
|
}
|
|
|
|
BOOL SendResponseHeader(LPCSTR pszHeader, LPCSTR pszStatusCode, BOOL fKeepConn) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->SendResponseHeader(pszHeader, pszStatusCode, fKeepConn);
|
|
}
|
|
|
|
BOOL DoneWithSession(DWORD dwHttpStatusCode) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->DoneWithSession(dwHttpStatusCode);
|
|
}
|
|
|
|
BOOL RequestIOCompletion(PFN_HSE_IO_COMPLETION pfn, DWORD * pdwContext) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->RequestIOCompletion(pfn, pdwContext);
|
|
}
|
|
|
|
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) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->TransmitFile(hFile, pfn, pContext, szStatusCode,
|
|
dwBytesToWrite, dwOffset, pvHead, dwHeadLen, pvTail, dwTailLen,
|
|
dwFlags);
|
|
}
|
|
|
|
BOOL AppendToLog(LPCSTR szMessage, DWORD* pdwLen) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->AppendToLog(szMessage, pdwLen);
|
|
}
|
|
|
|
BOOL MapUrlToPathEx(LPCSTR szLogicalPath, DWORD dwLen, HSE_URL_MAPEX_INFO *pumInfo) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->MapUrlToPathEx(szLogicalPath, dwLen, pumInfo);
|
|
}
|
|
|
|
};
|
|
|
|
// 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()
|
|
|
|
// The constructor.
|
|
CServerContext() throw()
|
|
{
|
|
m_pECB = NULL;
|
|
m_bHeadersHaveBeenSent = false;
|
|
}
|
|
|
|
void Initialize(EXTENSION_CONTROL_BLOCK *pECB) throw()
|
|
{
|
|
ATLASSERT(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() throw()
|
|
{
|
|
ATLASSERT(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() throw()
|
|
{
|
|
ATLASSERT(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() throw()
|
|
{
|
|
ATLASSERT(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() throw()
|
|
{
|
|
ATLASSERT(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() throw()
|
|
{
|
|
ATLASSERT(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() throw()
|
|
{
|
|
ATLASSERT(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() throw()
|
|
{
|
|
ATLASSERT(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() throw()
|
|
{
|
|
ATLASSERT(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() throw()
|
|
{
|
|
ATLASSERT(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.
|
|
BOOL GetServerVariable(
|
|
LPCSTR pszVariableName,
|
|
LPSTR pvBuffer,
|
|
DWORD *pdwSize) throw()
|
|
{
|
|
ATLASSERT(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).
|
|
BOOL WriteClient(void *pvBuffer, DWORD *pdwBytes) throw()
|
|
{
|
|
ATLASSERT(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).
|
|
BOOL AsyncWriteClient(void *pvBuffer, DWORD *pdwBytes) throw()
|
|
{
|
|
ATLASSERT(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.
|
|
BOOL ReadClient(void *pvBuffer, DWORD *pdwSize) throw()
|
|
{
|
|
ATLASSERT(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.
|
|
BOOL AsyncReadClient(void *pvBuffer, DWORD *pdwSize) throw()
|
|
{
|
|
// To call this function successfully someone has to have already
|
|
// called RequestIOCompletion specifying the callback function
|
|
// to be used for IO completion.
|
|
ATLASSERT(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.
|
|
BOOL SendRedirectResponse(LPCSTR pszRedirectUrl) throw()
|
|
{
|
|
ATLASSERT(m_pECB);
|
|
ATLASSERT(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.
|
|
BOOL GetImpersonationToken(HANDLE * pToken) throw()
|
|
{
|
|
ATLASSERT(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.
|
|
BOOL SendResponseHeader(
|
|
LPCSTR pszHeader = "Content-Type: text/html\r\n\r\n",
|
|
LPCSTR pszStatusCode = "200 OK",
|
|
BOOL fKeepConn=FALSE) throw()
|
|
{
|
|
ATLASSERT(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.
|
|
BOOL DoneWithSession(DWORD dwHttpStatusCode) throw()
|
|
{
|
|
ATLASSERT(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.
|
|
BOOL RequestIOCompletion(PFN_HSE_IO_COMPLETION pfn, DWORD *pdwContext) throw()
|
|
{
|
|
ATLASSERT(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(
|
|
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) throw()
|
|
{
|
|
ATLASSERT(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(LPCSTR szMessage, DWORD *pdwLen) throw()
|
|
{
|
|
DWORD dwLen = 0;
|
|
if (!pdwLen)
|
|
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(LPCSTR szLogicalPath, DWORD dwLen, HSE_URL_MAPEX_INFO *pumInfo)
|
|
{
|
|
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
|
|
// char m_szScriptPathTranslated[MAX_PATH];
|
|
CFixedStringT<CStringA, MAX_PATH> m_strScriptPathTranslated;
|
|
|
|
}; // class CServerContext
|
|
|
|
class CPageCachePeer
|
|
{
|
|
public:
|
|
|
|
struct PeerInfo
|
|
{
|
|
CStringA strHeader;
|
|
CStringA strStatus;
|
|
};
|
|
|
|
static BOOL Add(PeerInfo * pDest, void * pSrc) throw()
|
|
{
|
|
PeerInfo *pIn = (PeerInfo *)pSrc;
|
|
pDest->strHeader = pIn->strHeader;
|
|
pDest->strStatus = pIn->strStatus;
|
|
return TRUE;
|
|
}
|
|
|
|
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()
|
|
|
|
// The constructor.
|
|
CCacheServerContext() throw()
|
|
{
|
|
}
|
|
|
|
void Initialize(IHttpServerContext *pParent, IFileCache *pCache) throw()
|
|
{
|
|
ATLASSERT(pParent);
|
|
m_spParent = pParent;
|
|
m_spCache = pCache;
|
|
m_cacheFile.Create();
|
|
|
|
LPCSTR szPathInfo = pParent->GetPathInfo();
|
|
LPCSTR szQueryString = pParent->GetQueryString();
|
|
|
|
LPSTR szTo = m_szFullUrl;
|
|
while (*szPathInfo)
|
|
{
|
|
*szTo++ = *szPathInfo++;
|
|
}
|
|
*szTo++ = '?';
|
|
while (*szQueryString)
|
|
{
|
|
*szTo++ = *szQueryString++;
|
|
}
|
|
*szTo = '\0';
|
|
|
|
memset(&m_ftExpiration, 0x00, sizeof(FILETIME));
|
|
m_bIsCached = TRUE;
|
|
}
|
|
|
|
// IPageCacheControl methods
|
|
HRESULT GetExpiration(FILETIME *pftExpiration) throw()
|
|
{
|
|
if (!pftExpiration)
|
|
return E_INVALIDARG;
|
|
|
|
*pftExpiration = m_ftExpiration;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT SetExpiration(FILETIME ftExpiration) throw()
|
|
{
|
|
m_ftExpiration = ftExpiration;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
BOOL IsCached() throw()
|
|
{
|
|
return m_bIsCached;
|
|
}
|
|
|
|
BOOL Cache(BOOL bCache) throw()
|
|
{
|
|
BOOL bRet = m_bIsCached;
|
|
m_bIsCached = bCache;
|
|
return bRet;
|
|
}
|
|
|
|
|
|
BOOL WriteClient(void *pvBuffer, DWORD *pdwBytes) throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
if (S_OK != m_cacheFile.Write(pvBuffer, *pdwBytes))
|
|
return FALSE;
|
|
|
|
return m_spParent->WriteClient(pvBuffer, pdwBytes);
|
|
}
|
|
|
|
BOOL DoneWithSession(DWORD dwHttpStatusCode) throw()
|
|
{
|
|
ATLASSERT(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);
|
|
}
|
|
|
|
BOOL GetImpersonationToken(HANDLE * pToken) throw()
|
|
{
|
|
ATLTRACE(atlTraceISAPI, 0, _T("Getting impersonation token for cached page -- Possible security problem"));
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->GetImpersonationToken(pToken);
|
|
}
|
|
|
|
BOOL AppendToLog(LPCSTR szMessage, DWORD* pdwLen) throw()
|
|
{
|
|
ATLTRACE(atlTraceISAPI, 0, _T("Logging on cached page -- future hits will not log"));
|
|
ATLASSERT(m_spParent);
|
|
return m_spParent->AppendToLog(szMessage, pdwLen);
|
|
}
|
|
|
|
BOOL SendResponseHeader(
|
|
LPCSTR pszHeader = "Content-Type: text/html\r\n\r\n",
|
|
LPCSTR pszStatusCode = "200 OK",
|
|
BOOL fKeepConn=FALSE) throw()
|
|
{
|
|
ATLASSERT(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.
|
|
BOOL AsyncWriteClient(void * /*pvBuffer*/, DWORD * /*pdwBytes*/) throw()
|
|
{
|
|
// Asynchronous calls will not work
|
|
ATLASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL ReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/) throw()
|
|
{
|
|
// Nobody should be reading from this client if the page is being cached
|
|
// Also, only GET's are cached anyway
|
|
ATLASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL AsyncReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/) throw()
|
|
{
|
|
ATLASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL SendRedirectResponse(LPCSTR /*pszRedirectUrl*/) throw()
|
|
{
|
|
ATLASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL RequestIOCompletion(PFN_HSE_IO_COMPLETION /*pfn*/, DWORD * /*pdwContext*/) throw()
|
|
{
|
|
ATLASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
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*/) throw()
|
|
{
|
|
ATLASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL MapUrlToPathEx(LPCSTR szLogicalPath, DWORD dwLen, HSE_URL_MAPEX_INFO *pumInfo) throw()
|
|
{
|
|
return CWrappedServerContext::MapUrlToPathEx(szLogicalPath, dwLen, pumInfo);
|
|
}
|
|
};
|
|
|
|
|
|
// 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.
|
|
#define EMPTY_PARAMS_ARE_FAILURES 0x00000001
|
|
|
|
class CValidateContext :
|
|
public CSimpleMap<CStringA, DWORD>
|
|
{
|
|
public:
|
|
CValidateContext(DWORD dwFlags=0) throw()
|
|
{
|
|
m_bFailures = false;
|
|
m_dwFlags = dwFlags;
|
|
}
|
|
|
|
// 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(LPCSTR szName, DWORD type, bool bOnlyFailures = true) throw()
|
|
{
|
|
_ATLTRY
|
|
{
|
|
if (!VALIDATION_SUCCEEDED(type) ||
|
|
(type == VALIDATION_S_EMPTY && (m_dwFlags & EMPTY_PARAMS_ARE_FAILURES)))
|
|
m_bFailures = true;
|
|
|
|
if (!bOnlyFailures)
|
|
return TRUE == Add(szName, type); // add everything
|
|
|
|
else if (bOnlyFailures &&
|
|
(!VALIDATION_SUCCEEDED(type) ||
|
|
(type == VALIDATION_S_EMPTY && (m_dwFlags & EMPTY_PARAMS_ARE_FAILURES))))
|
|
return TRUE == Add(szName, type); // only add failures
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Returns true if there are no validation failures in the collection,
|
|
// returns false otherwise.
|
|
bool ParamsOK() throw()
|
|
{
|
|
return !m_bFailures;
|
|
}
|
|
|
|
// Returns the number of validation results in the collection.
|
|
int GetResultCount() throw()
|
|
{
|
|
return 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.
|
|
bool GetResultAt(int i, CStringA& strName, DWORD& type) throw()
|
|
{
|
|
if ( i >= 0 && i < GetSize())
|
|
{
|
|
_ATLTRY
|
|
{
|
|
strName = GetKeyAt(i);
|
|
type = GetValueAt(i);
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DWORD m_dwFlags;
|
|
protected:
|
|
bool m_bFailures;
|
|
}; // CValidateContext
|
|
|
|
|
|
|
|
class CAtlValidator
|
|
{
|
|
public:
|
|
template <class T, class TCompType>
|
|
static DWORD Validate(
|
|
T value,
|
|
TCompType nMinValue,
|
|
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( LPCSTR pszValue, int nMinChars, int nMaxChars) throw()
|
|
{
|
|
DWORD dwRet = VALIDATION_S_OK;
|
|
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( double dblValue, double dblMinValue, 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 DWORD Exchange(
|
|
LPCSTR szParam,
|
|
T* pValue,
|
|
CValidateContext *pContext = NULL) const throw()
|
|
{
|
|
DWORD dwRet = VALIDATION_E_PARAMNOTFOUND;
|
|
if (pValue)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
dwRet = VALIDATION_E_FAIL; // invalid input
|
|
|
|
if (pContext)
|
|
pContext->AddResult(szParam, dwRet);
|
|
return dwRet;
|
|
}
|
|
|
|
template<>
|
|
ATL_NOINLINE DWORD Exchange(
|
|
LPCSTR szParam,
|
|
CString* pstrValue,
|
|
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 DWORD Exchange(
|
|
LPCSTR szParam,
|
|
LPCSTR* ppszValue,
|
|
CValidateContext *pContext) const throw()
|
|
{
|
|
DWORD dwRet = VALIDATION_E_PARAMNOTFOUND;
|
|
if (ppszValue)
|
|
{
|
|
*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;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
dwRet = VALIDATION_E_FAIL; // invalid input
|
|
|
|
if (pContext)
|
|
pContext->AddResult(szParam, dwRet);
|
|
return dwRet;
|
|
}
|
|
|
|
template<>
|
|
ATL_NOINLINE DWORD Exchange(
|
|
LPCSTR szParam,
|
|
GUID* pValue,
|
|
CValidateContext *pContext) const throw()
|
|
{
|
|
DWORD dwRet = VALIDATION_E_PARAMNOTFOUND;
|
|
if (pValue)
|
|
{
|
|
const TLookupClass *pT = static_cast<const TLookupClass*>(this);
|
|
LPCSTR szValue = pT->Lookup(szParam);
|
|
if (szValue)
|
|
{
|
|
if (*szValue=='\0')
|
|
dwRet = VALIDATION_S_EMPTY;
|
|
else
|
|
{
|
|
USES_CONVERSION;
|
|
if (S_OK != CLSIDFromString(A2OLE(szValue), pValue))
|
|
{
|
|
dwRet = VALIDATION_E_INVALIDPARAM;
|
|
}
|
|
else
|
|
dwRet = VALIDATION_S_OK;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
dwRet = VALIDATION_E_FAIL; // invalid input
|
|
|
|
if (pContext)
|
|
pContext->AddResult(szParam, dwRet);
|
|
return dwRet;
|
|
}
|
|
|
|
template<>
|
|
ATL_NOINLINE DWORD Exchange(
|
|
LPCSTR szParam,
|
|
bool* pbValue,
|
|
CValidateContext *pContext) const throw()
|
|
{
|
|
DWORD dwRet = VALIDATION_S_OK;
|
|
if (pbValue)
|
|
{
|
|
const TLookupClass *pT = static_cast<const TLookupClass*>(this);
|
|
LPCSTR szValue = pT->Lookup(szParam);
|
|
*pbValue = false;
|
|
if (szValue)
|
|
{
|
|
if (*szValue != '\0')
|
|
*pbValue = true;
|
|
}
|
|
}
|
|
else
|
|
dwRet = VALIDATION_E_FAIL; // invalid input
|
|
|
|
if (pContext)
|
|
pContext->AddResult(szParam, dwRet);
|
|
|
|
return dwRet;
|
|
}
|
|
|
|
DWORD ConvertNumber(LPCSTR szVal, ULONGLONG *pnVal) const throw()
|
|
{
|
|
if (!pnVal)
|
|
return VALIDATION_E_FAIL;
|
|
char *pEnd = NULL;
|
|
ULONGLONG n = _strtoui64(szVal, &pEnd, 10);
|
|
if (pEnd == szVal || errno == ERANGE)
|
|
{
|
|
errno = 0;
|
|
return VALIDATION_E_INVALIDPARAM;
|
|
}
|
|
*pnVal = n;
|
|
return VALIDATION_S_OK;
|
|
}
|
|
|
|
DWORD ConvertNumber(LPCSTR szVal, LONGLONG *pnVal) const throw()
|
|
{
|
|
if (!pnVal)
|
|
return VALIDATION_E_FAIL;
|
|
char *pEnd = NULL;
|
|
LONGLONG n = _strtoi64(szVal, &pEnd, 10);
|
|
if (pEnd == szVal || errno == ERANGE)
|
|
{
|
|
errno = 0;
|
|
return VALIDATION_E_INVALIDPARAM;
|
|
}
|
|
*pnVal = n;
|
|
return VALIDATION_S_OK;
|
|
}
|
|
|
|
DWORD ConvertNumber(LPCSTR szVal, double *pdblVal) const throw()
|
|
{
|
|
if (!pdblVal)
|
|
return VALIDATION_E_FAIL;
|
|
char *pEnd = NULL;
|
|
double d = strtod(szVal, &pEnd);
|
|
if (pEnd == szVal || errno == ERANGE)
|
|
{
|
|
errno = 0;
|
|
return VALIDATION_E_INVALIDPARAM;
|
|
}
|
|
*pdblVal = d;
|
|
return VALIDATION_S_OK;
|
|
}
|
|
|
|
DWORD ConvertNumber(LPCSTR szVal, int *pnVal) const throw()
|
|
{
|
|
return ConvertNumber(szVal, (long*)pnVal);
|
|
}
|
|
|
|
DWORD ConvertNumber(LPCSTR szVal, unsigned int *pnVal) const throw()
|
|
{
|
|
return ConvertNumber(szVal, (unsigned long*)pnVal);
|
|
}
|
|
|
|
DWORD ConvertNumber(LPCSTR szVal, long *pnVal) const throw()
|
|
{
|
|
if (!pnVal)
|
|
return VALIDATION_E_FAIL;
|
|
char *pEnd = NULL;
|
|
long n = strtol(szVal, &pEnd, 10);
|
|
if (pEnd == szVal || errno == ERANGE)
|
|
{
|
|
errno = 0;
|
|
return VALIDATION_E_INVALIDPARAM;
|
|
}
|
|
*pnVal = n;
|
|
return VALIDATION_S_OK;
|
|
}
|
|
|
|
DWORD ConvertNumber(LPCSTR szVal, unsigned long *pnVal) const throw()
|
|
{
|
|
if (!pnVal)
|
|
return VALIDATION_E_FAIL;
|
|
char *pEnd = NULL;
|
|
long n = strtoul(szVal, &pEnd, 10);
|
|
if (pEnd == szVal || errno == ERANGE)
|
|
{
|
|
errno = 0;
|
|
return VALIDATION_E_INVALIDPARAM;
|
|
}
|
|
*pnVal = n;
|
|
return VALIDATION_S_OK;
|
|
}
|
|
|
|
DWORD ConvertNumber(LPCSTR szVal, short *pnVal) const throw()
|
|
{
|
|
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;
|
|
};
|
|
|
|
DWORD ConvertNumber(LPCSTR szVal, unsigned short *pnVal) const throw()
|
|
{
|
|
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 DWORD Validate(
|
|
LPCSTR Param,
|
|
T *pValue,
|
|
TCompType nMinValue,
|
|
TCompType nMaxValue,
|
|
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->SetAt(Param, VALIDATION_E_LENGTHMIN);
|
|
}
|
|
}
|
|
|
|
return dwRet;
|
|
}
|
|
|
|
// Specialization for strings. Comparison is for number of characters.
|
|
template<>
|
|
ATL_NOINLINE DWORD Validate(
|
|
LPCSTR Param,
|
|
LPCSTR* ppszValue,
|
|
int nMinChars,
|
|
int nMaxChars,
|
|
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->SetAt(Param, VALIDATION_E_LENGTHMIN);
|
|
}
|
|
}
|
|
|
|
|
|
return dwRet;
|
|
}
|
|
|
|
// Specialization for CString so caller doesn't have to cast CString
|
|
template<>
|
|
ATL_NOINLINE DWORD Validate(
|
|
LPCSTR Param,
|
|
CString* pstrValue,
|
|
int nMinChars,
|
|
int nMaxChars,
|
|
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 DWORD Validate(
|
|
LPCSTR Param,
|
|
double* pdblValue,
|
|
double dblMinValue,
|
|
double dblMaxValue,
|
|
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->SetAt(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 CStringA elemType;
|
|
|
|
typedef CAtlMap<elemType, elemType, CStringElementTraits<elemType>,
|
|
CStringElementTraits<elemType> > mapType;
|
|
public:
|
|
// Constructs a named cookie.
|
|
CCookie(LPCSTR szName) throw(...)
|
|
{
|
|
SetName(szName);
|
|
}
|
|
|
|
// Constructs a single-valued cookie.
|
|
CCookie(LPCSTR szName, LPCSTR szValue) throw(...)
|
|
{
|
|
SetName(szName);
|
|
SetValue(szValue);
|
|
}
|
|
|
|
CCookie(const CCookie& thatCookie) throw(...)
|
|
{
|
|
Copy(thatCookie);
|
|
}
|
|
|
|
CCookie& operator=(const CCookie& thatCookie) throw(...)
|
|
{
|
|
return Copy(thatCookie);
|
|
}
|
|
|
|
CCookie() throw()
|
|
{
|
|
|
|
}
|
|
|
|
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.
|
|
BOOL SetName(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.
|
|
BOOL GetName(LPSTR szBuff, 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.
|
|
BOOL GetName(CStringA &szName) const throw(...)
|
|
{
|
|
szName = m_strName;
|
|
return TRUE;
|
|
}
|
|
|
|
// 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.
|
|
BOOL SetValue(LPCSTR szValue) throw(...)
|
|
{
|
|
if (m_Values.GetCount())
|
|
return FALSE; //already dictionary values in the cookie
|
|
|
|
if (!szValue)
|
|
m_strValue.Empty();
|
|
else
|
|
m_strValue = szValue;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// 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).
|
|
BOOL GetValue(LPSTR szBuff, 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.
|
|
BOOL GetValue(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.
|
|
BOOL AddValue(LPCSTR szName, 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.
|
|
BOOL ModifyValue(LPCSTR szName, 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.
|
|
BOOL RemoveValue(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.
|
|
BOOL AddAttribute(LPCSTR szName, 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.
|
|
BOOL ModifyAttribute(LPCSTR szName, 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.
|
|
BOOL RemoveAttribute(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.
|
|
BOOL SetComment(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.
|
|
BOOL SetCommentUrl(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.
|
|
BOOL SetDiscard(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.
|
|
BOOL SetDomain(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.
|
|
BOOL SetMaxAge(UINT nMaxAge) throw()
|
|
{
|
|
BOOL bRet = FALSE;
|
|
bRet = SetVersion(1);
|
|
if (bRet)
|
|
{
|
|
CHAR buff[20];
|
|
if (_itoa(nMaxAge, 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.
|
|
BOOL SetPath(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.
|
|
BOOL SetPort(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.
|
|
BOOL SetSecure(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.
|
|
BOOL SetVersion(UINT nVersion) throw()
|
|
{
|
|
BOOL bRet = FALSE;
|
|
CHAR buff[20];
|
|
if (_itoa(nVersion, 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.
|
|
BOOL SetExpires(LPCSTR szExpires) throw()
|
|
{
|
|
return AddAttribute("expires", szExpires);
|
|
}
|
|
|
|
BOOL SetExpires(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.
|
|
LPCSTR Lookup(LPCSTR szName=NULL) const throw()
|
|
{
|
|
if (IsEmpty())
|
|
return NULL;
|
|
|
|
if (!szName && m_strValue.GetLength())
|
|
return m_strValue;
|
|
|
|
if (m_Values.GetCount())
|
|
{
|
|
const mapType::CPair *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 = "";
|
|
m_strValue = "";
|
|
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
|
|
//
|
|
// review: support MBCS cookie values?
|
|
ATL_NOINLINE bool Parse(LPSTR pstart) throw()
|
|
{
|
|
//pStart points to the beginning of the cookie
|
|
//pEnd points to the character just past the end of the cookie.
|
|
//cookie is in the form name=value
|
|
LPSTR pEnd = pstart;
|
|
LPSTR pStart = pstart;
|
|
int index = 0;
|
|
LPSTR pTokens[16];
|
|
CStringA strName, name, value;
|
|
|
|
while (*pEnd != '&' && *pEnd != ';' && *pEnd != '\0')
|
|
pEnd++;
|
|
|
|
int nCount = CountOf('=', pStart, pEnd);
|
|
|
|
if (nCount == 2)
|
|
{
|
|
//first token is name, next tokens are first name/value in
|
|
//the Values collection
|
|
pEnd = pStart;
|
|
index = 1;
|
|
pTokens[0]=pStart;
|
|
while (*pEnd != '&' && *pEnd != ';' && *pEnd != '\0')
|
|
{
|
|
if (*pEnd == '=')
|
|
{
|
|
pTokens[index++] = pEnd;
|
|
}
|
|
pEnd++;
|
|
}
|
|
|
|
CopyToCString(strName, pTokens[0], pTokens[1]-1);
|
|
CopyToCString(name, pTokens[1]+1, pTokens[2]-1);
|
|
CopyToCString(value, pTokens[2]+1, pEnd-1);
|
|
_ATLTRY
|
|
{
|
|
m_strName = strName;
|
|
AddValue(name, value);
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if (nCount == 1)
|
|
{
|
|
LPSTR pCurr = pStart;
|
|
index = 1;
|
|
pTokens[0] = pStart;
|
|
while (pCurr != pEnd)
|
|
{
|
|
if (*pCurr == '=')
|
|
{
|
|
pTokens[index] = pCurr;
|
|
}
|
|
pCurr++;
|
|
}
|
|
CopyToCString(name, pTokens[0], pTokens[1]-1);
|
|
CopyToCString(value, pTokens[1]+1, pEnd-1);
|
|
_ATLTRY
|
|
{
|
|
m_strName = name;
|
|
m_strValue = value;
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
}
|
|
else if (nCount == 0)
|
|
{
|
|
// no value
|
|
if (pEnd > pStart)
|
|
{
|
|
CopyToCString(name, pStart, pEnd-1);
|
|
_ATLTRY
|
|
{
|
|
m_strName = name;
|
|
m_strValue = "";
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
return false; //error in cookie
|
|
|
|
if (*pEnd == '&')
|
|
{
|
|
//still have name/values to parse
|
|
pStart = pEnd+1;
|
|
pEnd = pStart;
|
|
while(1)
|
|
{
|
|
if (*pEnd == '=') //separates the name from the value
|
|
pTokens[0] = pEnd;
|
|
//Marks either the end of the name/values or
|
|
//the end of the name/value statement
|
|
if (*pEnd == '&' || *pEnd == ';' || *pEnd == '\0')
|
|
{
|
|
CopyToCString(name, pStart, pTokens[0]-1);
|
|
CopyToCString(value, pTokens[0]+1, pEnd-1);
|
|
AddValue(name, value);
|
|
pStart = pEnd+1;
|
|
}
|
|
|
|
if (*pEnd == ';' || *pEnd =='\0')
|
|
break;
|
|
|
|
pEnd++;
|
|
}
|
|
}
|
|
|
|
m_strName.TrimRight();
|
|
m_strName.TrimLeft();
|
|
m_strValue.TrimRight();
|
|
m_strValue.TrimLeft();
|
|
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.
|
|
ATL_NOINLINE BOOL Render(LPSTR szCookieBuffer, DWORD *pdwLen) const throw()
|
|
{
|
|
CStringA strCookie;
|
|
CStringA name, value;
|
|
DWORD nLenBuff = *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();
|
|
|
|
//add value or name/value pairs.
|
|
if (m_strValue.GetLength())
|
|
{
|
|
strCookie += '=';
|
|
strCookie += m_strValue;
|
|
}
|
|
else 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 += '&';
|
|
}
|
|
}
|
|
|
|
CStringA strAttributes;
|
|
if (!RenderAttributes(strAttributes))
|
|
return FALSE;
|
|
if (strAttributes.GetLength() > 0)
|
|
{
|
|
strCookie += "; ";
|
|
strCookie += strAttributes;
|
|
}
|
|
|
|
DWORD dwLenCookie = strCookie.GetLength() + 1;
|
|
if (dwLenCookie > nLenBuff)
|
|
{
|
|
*pdwLen = dwLenCookie;
|
|
return FALSE; //buffer wasn't big enough
|
|
}
|
|
|
|
*pdwLen = dwLenCookie - 1;
|
|
strcpy(szCookieBuffer, strCookie);
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
POSITION GetFirstAttributePos() const throw()
|
|
{
|
|
return m_Attributes.GetStartPosition();
|
|
}
|
|
|
|
const elemType& GetNextAttributeName(POSITION& pos) const throw()
|
|
{
|
|
return m_Attributes.GetNextKey(pos);
|
|
}
|
|
|
|
const elemType& GetAttributeValueAt(POSITION pos) const throw()
|
|
{
|
|
return m_Attributes.GetValueAt(pos);
|
|
}
|
|
|
|
BOOL GetNextAttrAssoc(POSITION& pos, elemType& key,
|
|
elemType& val) const throw()
|
|
{
|
|
_ATLTRY
|
|
{
|
|
m_Attributes.GetNextAssoc(pos, key, val);
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
POSITION GetFirstValuePos() const throw()
|
|
{
|
|
return m_Values.GetStartPosition();
|
|
}
|
|
|
|
const elemType& GetNextValueName(POSITION& pos) const throw()
|
|
{
|
|
return m_Values.GetNextKey(pos);
|
|
}
|
|
|
|
const elemType& GetValueAt(POSITION pos) const throw()
|
|
{
|
|
return m_Values.GetValueAt(pos);
|
|
}
|
|
|
|
BOOL GetNextValueAssoc(POSITION& pos, elemType& key,
|
|
elemType& val) const throw()
|
|
{
|
|
_ATLTRY
|
|
{
|
|
m_Values.GetNextAssoc(pos, key, val);
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
protected:
|
|
// Implementation
|
|
BOOL RenderAttributes(CStringA& strAttributes) const throw()
|
|
{
|
|
_ATLTRY
|
|
{
|
|
strAttributes = "";
|
|
|
|
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(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;
|
|
}
|
|
|
|
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( INARGTYPE cookie )
|
|
{
|
|
return CStringElementTraits<CStringA>::Hash( cookie.m_strName );
|
|
}
|
|
|
|
static bool CompareElements( INARGTYPE cookie1, INARGTYPE cookie2 )
|
|
{
|
|
return( cookie1.m_strName == cookie2.m_strName );
|
|
}
|
|
|
|
static int CompareElementsOrdered( INARGTYPE cookie1, 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 KTraits::INARGTYPE KINARGTYPE;
|
|
typedef KTraits::OUTARGTYPE KOUTARGTYPE;
|
|
typedef VTraits::INARGTYPE VINARGTYPE;
|
|
typedef VTraits::OUTARGTYPE VOUTARGTYPE;
|
|
|
|
typedef 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(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( KINARGTYPE key, 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( 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( 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( POSITION& pos, KOUTARGTYPE key, VOUTARGTYPE value ) const throw(...)
|
|
{
|
|
m_map.GetNextAssoc(pos, key, value);
|
|
}
|
|
|
|
const CPair* GetNext( POSITION& pos ) const throw()
|
|
{
|
|
return m_map.GetNext(pos);
|
|
}
|
|
|
|
CPair* GetNext( POSITION& pos ) throw()
|
|
{
|
|
return m_map.GetNext(pos);
|
|
}
|
|
|
|
const K& GetNextKey( POSITION& pos ) const throw()
|
|
{
|
|
return m_map.GetNextKey(pos);
|
|
}
|
|
|
|
const V& GetNextValue( POSITION& pos ) const throw()
|
|
{
|
|
return m_map.GetNextValue(pos);
|
|
}
|
|
|
|
V& GetNextValue( POSITION& pos ) throw()
|
|
{
|
|
return m_map.GetNextValue(pos);
|
|
}
|
|
|
|
void GetAt( POSITION pos, KOUTARGTYPE key, VOUTARGTYPE value ) const throw(...)
|
|
{
|
|
return m_map.GetAt(pos, key, value);
|
|
}
|
|
|
|
CPair* GetAt( POSITION pos ) throw()
|
|
{
|
|
return m_map.GetAt(pos);
|
|
}
|
|
|
|
const CPair* GetAt( POSITION pos ) const throw()
|
|
{
|
|
return m_map.GetAt(pos);
|
|
}
|
|
|
|
const K& GetKeyAt( POSITION pos ) const throw()
|
|
{
|
|
return m_map.GetKeyAt(pos);
|
|
}
|
|
|
|
const V& GetValueAt( POSITION pos ) const throw()
|
|
{
|
|
return m_map.GetValueAt(pos);
|
|
}
|
|
|
|
V& GetValueAt( POSITION pos ) throw()
|
|
{
|
|
return m_map.GetValueAt(pos);
|
|
}
|
|
|
|
// modification wrappers
|
|
POSITION SetAt( KINARGTYPE key, 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
|
|
}
|
|
|
|
virtual void RemoveAll() throw()
|
|
{
|
|
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() throw()
|
|
{
|
|
if (!IsShared())
|
|
{
|
|
POSITION pos = GetStartPosition();
|
|
while (pos)
|
|
{
|
|
GetNextValue(pos)->Release();
|
|
}
|
|
}
|
|
Base::RemoveAll();
|
|
}
|
|
|
|
~CHttpPtrMap() throw()
|
|
{
|
|
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 CSimpleMap 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(LPCSTR szName) const throw()
|
|
{
|
|
if (!szName)
|
|
return NULL;
|
|
|
|
const CPair *p = BaseMap::Lookup(szName);
|
|
if (p)
|
|
{
|
|
return p->m_value;
|
|
}
|
|
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 BOOL Parse(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))
|
|
{
|
|
CHAR szCharCode[3];
|
|
szCharCode[0] = *(szQueryString+1);
|
|
szCharCode[1] = *(szQueryString+2);
|
|
szCharCode[2] = '\0';
|
|
LPSTR szEnd;
|
|
*szUrlCurrent = (CHAR) strtoul(szCharCode, &szEnd, 16);
|
|
szQueryString += 2;
|
|
}
|
|
else
|
|
*szUrlCurrent = '\0';
|
|
}
|
|
else
|
|
*szUrlCurrent = *szQueryString;
|
|
|
|
szQueryString++;
|
|
szUrlCurrent++;
|
|
}
|
|
|
|
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))
|
|
{
|
|
CHAR szCharCode[3];
|
|
szCharCode[0] = *(szQueryString+1);
|
|
szCharCode[1] = *(szQueryString+2);
|
|
szCharCode[2] = '\0';
|
|
LPSTR szEnd;
|
|
*szUrlCurrent = (CHAR) strtoul(szCharCode, &szEnd, 16);
|
|
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 BOOL Render(LPSTR szParameters, LPDWORD pdwLen) throw()
|
|
{
|
|
ATLASSERT(szParameters);
|
|
ATLASSERT(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--;
|
|
memcpy(szParameters, static_cast<LPCSTR>(strParams), dwLen);
|
|
szParameters[dwLen] = '\0';
|
|
}
|
|
|
|
*pdwLen = dwLen;
|
|
return bRet;
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
}; // class CHttpRequestParams
|
|
|
|
#define MAX_TOKEN_LENGTH (MAX_PATH)
|
|
|
|
// This class represents the information about a file that has been uploaded to the web server.
|
|
class CHttpRequestFile : public IHttpFile
|
|
{
|
|
protected:
|
|
|
|
// 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:
|
|
|
|
// The constructor.
|
|
CHttpRequestFile(
|
|
LPCSTR pParamName,
|
|
LPCSTR pFileName,
|
|
LPCSTR pTempFileName,
|
|
LPCSTR pContentType,
|
|
const ULONGLONG& nFileSize) throw()
|
|
{
|
|
ATLASSERT(pFileName);
|
|
|
|
m_szParamName[0] = 0;
|
|
m_szFileName[0] = 0;
|
|
m_szTempFileName[0] = 0;
|
|
m_szFullFileName[0] = 0;
|
|
m_szContentType[0] = 0;
|
|
m_nFileSize = nFileSize;
|
|
|
|
strcpy(m_szParamName, pParamName);
|
|
strcpy(m_szFullFileName, pFileName);
|
|
strcpy(m_szTempFileName, pTempFileName);
|
|
|
|
if (pContentType && *pContentType)
|
|
{
|
|
strcpy(m_szContentType, pContentType);
|
|
}
|
|
|
|
// 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;
|
|
|
|
while (*szTmp)
|
|
{
|
|
if (*szTmp == '\\')
|
|
{
|
|
szFile = m_szFileName;
|
|
}
|
|
else
|
|
{
|
|
*szFile++ = *szTmp;
|
|
}
|
|
szTmp++;
|
|
}
|
|
*szFile = 0;
|
|
}
|
|
|
|
//=======================================
|
|
// IHttpFile interface
|
|
//=======================================
|
|
LPCSTR GetParamName() throw()
|
|
{
|
|
return m_szParamName;
|
|
}
|
|
|
|
LPCSTR GetFileName() throw()
|
|
{
|
|
return m_szFileName;
|
|
}
|
|
|
|
LPCSTR GetFullFileName() throw()
|
|
{
|
|
return m_szFullFileName;
|
|
}
|
|
|
|
LPCSTR GetContentType() throw()
|
|
{
|
|
return m_szContentType;
|
|
}
|
|
|
|
LPCSTR GetTempFileName() throw()
|
|
{
|
|
return m_szTempFileName;
|
|
}
|
|
|
|
ULONGLONG GetFileSize() throw()
|
|
{
|
|
return m_nFileSize;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) throw()
|
|
{
|
|
if (!ppv)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (InlineIsEqualGUID(riid, __uuidof(IUnknown)) ||
|
|
InlineIsEqualGUID(riid, __uuidof(IHttpFile)))
|
|
{
|
|
*ppv = static_cast<IUnknown*>(static_cast<IHttpFile*>(this));
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE AddRef() throw()
|
|
{
|
|
ATLASSERT( FALSE );
|
|
return 1;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE Release() throw()
|
|
{
|
|
delete this;
|
|
|
|
return 1;
|
|
}
|
|
|
|
}; // class CHttpRequestFile
|
|
|
|
|
|
// utility function to ReadData from a ServerContext
|
|
ATL_NOINLINE inline
|
|
BOOL ReadClientData(IHttpServerContext *pServerContext, LPSTR pbDest, LPDWORD pdwLen, DWORD dwBytesRead) throw()
|
|
{
|
|
ATLASSERT(pServerContext != NULL);
|
|
ATLASSERT(pbDest != NULL);
|
|
ATLASSERT(pdwLen != NULL);
|
|
|
|
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);
|
|
memcpy(pbDest, 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;
|
|
pbDest+= dwClientBytesRead;
|
|
|
|
} while (dwClientBytesToRead != 0 && dwClientBytesRead != 0);
|
|
|
|
dwRead+= dwToRead-dwClientBytesToRead;
|
|
}
|
|
|
|
*pdwLen = dwRead;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#define FORM_BUFFER_SIZE 2048
|
|
#define MAX_MIME_LINE_LEN 1024
|
|
#define MAX_MIME_BOUNDARY_LEN 128
|
|
#define MAX_PARAM_LEN _MAX_PATH
|
|
#define MAX_CONTENT_TYPE_LEN 512
|
|
|
|
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];
|
|
CHAR m_szSearchBoundary[MAX_MIME_BOUNDARY_LEN+4];
|
|
DWORD m_dwBoundaryLen;
|
|
BOOL m_bFinished;
|
|
CComPtr<IHttpServerContext> m_spServerContext;
|
|
|
|
public:
|
|
// The constructor.
|
|
CMultiPartFormParser(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()
|
|
{
|
|
// free memory if necessary
|
|
if (m_spServerContext->GetTotalBytes() > m_spServerContext->GetAvailableBytes())
|
|
{
|
|
free(m_pStart);
|
|
}
|
|
}
|
|
|
|
// 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(CHttpMap<CStringA, IHttpFile*, CStringElementTraits<CStringA> >& Files,
|
|
CHttpRequestParams* pQueryParams,
|
|
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) || !bFound)
|
|
{
|
|
ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
|
|
return FALSE;
|
|
}
|
|
|
|
// see if it's a file
|
|
if (!GetMimeData(strFileName, "filename=", sizeof("filename=")-1, &bFound))
|
|
{
|
|
ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (bFound)
|
|
{
|
|
if (dwFlags & ATL_FORM_FLAG_REFUSE_FILES)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!strFileName.GetLength())
|
|
{
|
|
ReadUntilBoundary();
|
|
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;
|
|
}
|
|
|
|
//REVIEW: if exceptions are thrown, the temp file created by ctf is never removed
|
|
ctf.HandsOff();
|
|
|
|
//REVIEW: pFile always leaks
|
|
CHttpRequestFile* pFile = NULL;
|
|
|
|
CT2AEX<MAX_PATH+1> szTempFileNameA(ctf.TempFileName());
|
|
|
|
ATLTRY(pFile = new CHttpRequestFile(strParamName, strFileName, szTempFileNameA, strContentType, nFileSize));
|
|
if (!pFile)
|
|
return FALSE;
|
|
|
|
Files.SetAt(szTempFileNameA, pFile);
|
|
pQueryParams->SetAt(strParamName, szTempFileNameA);
|
|
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;
|
|
|
|
pQueryParams->SetAt(strParamName, strData);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
protected:
|
|
|
|
// case insensitive substring search -- does not handle multibyte characters
|
|
// allows searching up to a maximum point in a string
|
|
inline char AtlCharLower(char ch) throw()
|
|
{
|
|
if (ch > 64 && ch < 91)
|
|
{
|
|
return ch+32;
|
|
}
|
|
|
|
return ch;
|
|
}
|
|
|
|
inline char * _stristrex (const char * str1, const char * str2, const char * str1End) throw()
|
|
{
|
|
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 (s1 == str1End)
|
|
{
|
|
return (NULL);
|
|
}
|
|
|
|
if (!*s2)
|
|
{
|
|
return (cp);
|
|
}
|
|
|
|
cp++;
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
inline char * _strstrex (const char * str1, const char * str2, const char * str1End) throw()
|
|
{
|
|
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 (s1 == str1End)
|
|
{
|
|
return (NULL);
|
|
}
|
|
|
|
if (!*s2)
|
|
{
|
|
return (cp);
|
|
}
|
|
|
|
cp++;
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
ATL_NOINLINE BOOL InitializeParser() throw()
|
|
{
|
|
DWORD dwBytesTotal = m_spServerContext->GetTotalBytes();
|
|
|
|
// if greater than bytes available, allocate necessary space
|
|
if (dwBytesTotal > m_spServerContext->GetAvailableBytes())
|
|
{
|
|
ATLTRYALLOC(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 ? m_spServerContext->GetContentType() : NULL;
|
|
ATLASSERT(pszContentType != NULL);
|
|
|
|
LPCSTR pszTmp = strstr(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';
|
|
m_szSearchBoundary[0] = '\r';
|
|
m_szSearchBoundary[1] = '\n';
|
|
strcpy(m_szSearchBoundary+2, m_szBoundary);
|
|
return TRUE;
|
|
}
|
|
|
|
inline 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 BOOL GetMimeData(CStringA &str, LPCSTR szField, DWORD dwFieldLen, LPBOOL pbFound, BOOL bIgnoreCase = FALSE) throw()
|
|
{
|
|
_ATLTRY
|
|
{
|
|
ATLASSERT( szField != NULL );
|
|
ATLASSERT( 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;
|
|
}
|
|
|
|
LPSTR szOut = str.GetBuffer((int)(szDataEnd-szDataStart)+1);
|
|
if (!szOut)
|
|
{
|
|
str.ReleaseBuffer();
|
|
return FALSE;
|
|
}
|
|
memcpy(szOut, szDataStart, szDataEnd-szDataStart);
|
|
szOut[szDataEnd-szDataStart] = '\0';
|
|
str.ReleaseBuffer((int)(szDataEnd-szDataStart));
|
|
*pbFound = TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
ATL_NOINLINE BOOL ReadUntilBoundary(CStringA* pStrData=NULL, CAtlTemporaryFile* pCtf=NULL) throw()
|
|
{
|
|
_ATLTRY
|
|
{
|
|
LPSTR szBoundaryStart = m_pCurrent;
|
|
LPSTR szBoundaryEnd = NULL;
|
|
|
|
do
|
|
{
|
|
szBoundaryStart = _strstrex(szBoundaryStart, m_szBoundary, m_pEnd);
|
|
if (szBoundaryStart != m_pStart)
|
|
{
|
|
if ((szBoundaryStart-m_pStart) >= 2)
|
|
{
|
|
if (*(szBoundaryStart-1) != 0x0a && *(szBoundaryStart-2) != 0x0d)
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
} while (szBoundaryStart);
|
|
|
|
if (!szBoundaryStart)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
szBoundaryStart-= 2;
|
|
if (pStrData)
|
|
{
|
|
LPSTR szData = pStrData->GetBuffer((int)(szBoundaryStart-m_pCurrent)+1);
|
|
if (!szData)
|
|
{
|
|
pStrData->ReleaseBuffer();
|
|
return FALSE;
|
|
}
|
|
memcpy(szData, m_pCurrent, (int)(szBoundaryStart-m_pCurrent));
|
|
szData[szBoundaryStart-m_pCurrent] = '\0';
|
|
pStrData->ReleaseBuffer((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 BOOL IsStandardBoundaryChar(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 IsValidTokenChar(CHAR ch) throw()
|
|
{
|
|
return ( (ch != 0) && (ch != 0xd) && (ch != 0xa) && (ch != ' ') && (ch != '\"') );
|
|
}
|
|
|
|
private:
|
|
// Prevents copying.
|
|
CMultiPartFormParser(const CMultiPartFormParser& /*that*/) throw()
|
|
{
|
|
ATLASSERT(FALSE);
|
|
}
|
|
|
|
const CMultiPartFormParser& operator=(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 LPCSTR 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;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// The default constructor.
|
|
CHttpRequest() throw()
|
|
:m_pFormVars(NULL)
|
|
{
|
|
Construct();
|
|
}
|
|
|
|
// Implementation: The destructor.
|
|
~CHttpRequest() throw()
|
|
{
|
|
DeleteFiles();
|
|
ClearFilesAndCookies();
|
|
|
|
if (m_pFormVars != &m_QueryParams)
|
|
{
|
|
delete m_pFormVars;
|
|
m_pFormVars = NULL;
|
|
}
|
|
}
|
|
|
|
// Constructs and initializes the object.
|
|
CHttpRequest(
|
|
IHttpServerContext *pServerContext,
|
|
DWORD dwMaxFormSize=DEFAULT_MAX_FORM_SIZE,
|
|
DWORD dwFlags=ATL_FORM_FLAG_NONE) throw(...)
|
|
:m_pFormVars(NULL)
|
|
{
|
|
Construct();
|
|
if (!Initialize(pServerContext, dwMaxFormSize, dwFlags))
|
|
AtlThrow(E_FAIL);
|
|
}
|
|
|
|
CHttpRequest(IHttpRequestLookup *pRequestLookup) throw(...)
|
|
:m_pFormVars(NULL)
|
|
{
|
|
if (!Initialize(pRequestLookup)) // Calls Construct for you
|
|
AtlThrow(E_FAIL);
|
|
}
|
|
|
|
//=========================================================================================
|
|
// BEGIN IHttpRequestLoookup interface
|
|
//=========================================================================================
|
|
POSITION GetFirstQueryParam(LPCSTR *ppszName, LPCSTR *ppszValue) const throw()
|
|
{
|
|
ATLASSERT(ppszName != NULL);
|
|
ATLASSERT(ppszValue != NULL);
|
|
|
|
POSITION pos = m_QueryParams.GetStartPosition();
|
|
if (pos != NULL)
|
|
{
|
|
*ppszName = m_QueryParams.GetKeyAt(pos);
|
|
*ppszValue = m_QueryParams.GetValueAt(pos);
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
POSITION GetNextQueryParam(POSITION pos, LPCSTR *ppszName, LPCSTR *ppszValue) const throw()
|
|
{
|
|
ATLASSERT(pos != NULL);
|
|
ATLASSERT(ppszName != NULL);
|
|
ATLASSERT(ppszValue != NULL);
|
|
|
|
POSITION posNext(pos);
|
|
m_QueryParams.GetNext(posNext);
|
|
if (posNext != NULL)
|
|
{
|
|
*ppszName = m_QueryParams.GetKeyAt(posNext);
|
|
*ppszValue = m_QueryParams.GetValueAt(posNext);
|
|
}
|
|
|
|
return posNext;
|
|
}
|
|
|
|
POSITION GetFirstFormVar(LPCSTR *ppszName, LPCSTR *ppszValue) const throw()
|
|
{
|
|
ATLASSERT(ppszName != NULL);
|
|
ATLASSERT(ppszValue != NULL);
|
|
|
|
// 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)
|
|
{
|
|
*ppszName = m_pFormVars->GetKeyAt(pos);
|
|
*ppszValue = m_pFormVars->GetValueAt(pos);
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
POSITION GetNextFormVar(POSITION pos, LPCSTR *ppszName, LPCSTR *ppszValue) const throw()
|
|
{
|
|
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(LPCSTR *ppszName, IHttpFile **ppFile) const throw()
|
|
{
|
|
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;
|
|
}
|
|
|
|
POSITION GetNextFile(POSITION pos, LPCSTR *ppszName, IHttpFile **ppFile) const throw()
|
|
{
|
|
ATLASSERT(pos != NULL);
|
|
ATLASSERT(ppszName != NULL);
|
|
ATLASSERT(ppFile != NULL);
|
|
|
|
POSITION posNext(pos);
|
|
m_Files.GetNext(posNext);
|
|
if (posNext != NULL)
|
|
{
|
|
*ppszName = m_Files.GetKeyAt(posNext);
|
|
*ppFile = m_Files.GetValueAt(posNext);
|
|
}
|
|
|
|
return posNext;
|
|
}
|
|
|
|
POSITION GetFirstCookie(LPCSTR *ppszName, 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;
|
|
}
|
|
|
|
POSITION GetNextCookie(POSITION pos, LPCSTR *ppszName, 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;
|
|
}
|
|
|
|
// Returns a pointer to the IHttpServerContext interface for the current request.
|
|
HRESULT GetServerContext(IHttpServerContext ** ppOut) throw()
|
|
{
|
|
return m_spServerContext.CopyTo(ppOut);
|
|
}
|
|
//=========================================================================================
|
|
// END IHttpRequestLookup interface
|
|
//=========================================================================================
|
|
|
|
void SetServerContext(IHttpServerContext *pServerContext) throw()
|
|
{
|
|
m_spServerContext = pServerContext;
|
|
}
|
|
|
|
BOOL Initialize(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((LPSTR)(LPCSTR)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(
|
|
IHttpServerContext *pServerContext,
|
|
DWORD dwMaxFormSize=DEFAULT_MAX_FORM_SIZE,
|
|
DWORD dwFlags=ATL_FORM_FLAG_NONE) throw()
|
|
{
|
|
_ATLTRY
|
|
{
|
|
ATLASSERT(pServerContext != NULL);
|
|
if (!pServerContext)
|
|
return FALSE;
|
|
|
|
m_spServerContext = pServerContext;
|
|
|
|
HTTP_METHOD httpMethod = GetMethod();
|
|
|
|
// Parse the query string.
|
|
CHAR szQueryString[ATL_URL_MAX_URL_LENGTH];
|
|
strcpy(szQueryString, GetQueryString());
|
|
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;
|
|
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 (memcmp(szContentType, "multipart/form-data", 19) == 0)
|
|
m_bMultiPart = TRUE;
|
|
|
|
m_dwBytesRead = 0;
|
|
|
|
// REVIEW : We have to assume the developer knows what they're doing to
|
|
// some extent here.
|
|
return TRUE;
|
|
}
|
|
|
|
// If POSTed data is urlencoded, call InitFromPost.
|
|
if (memcmp(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 (memcmp(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
|
|
{
|
|
ATLASSERT(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 (!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(LPSTR pDest, LPDWORD pdwLen) throw()
|
|
{
|
|
ATLASSERT(pDest);
|
|
ATLASSERT(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.
|
|
DWORD GetAvailableBytes() throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetAvailableBytes() : 0;
|
|
}
|
|
|
|
// 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.
|
|
DWORD GetTotalBytes() throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetTotalBytes() : 0;
|
|
}
|
|
|
|
// 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
|
|
LPBYTE GetAvailableData() throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetAvailableData() : NULL;
|
|
}
|
|
|
|
|
|
// 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() throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetQueryString() : NULL;
|
|
}
|
|
|
|
// 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 GetMethodString() throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetRequestMethod() : NULL;
|
|
}
|
|
|
|
// 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
|
|
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.
|
|
LPCSTR GetContentType() throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetContentType() : NULL;
|
|
}
|
|
|
|
|
|
// 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).
|
|
BOOL GetAuthUserName(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("AUTH_USER", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// 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).
|
|
BOOL GetPhysicalPath(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("APPL_PHYSICAL_PATH", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// 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).
|
|
BOOL GetAuthUserPassword(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("AUTH_PASSWORD", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// 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).
|
|
BOOL GetUrl(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("URL", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// 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).
|
|
BOOL GetUserHostName(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("REMOTE_HOST", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// 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).
|
|
BOOL GetUserHostAddress(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("REMOTE_ADDR", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// 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".
|
|
LPCSTR GetScriptPathTranslated() throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetScriptPathTranslated() : NULL;
|
|
}
|
|
|
|
// 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.
|
|
LPCSTR GetPathTranslated() throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetPathTranslated() : NULL;
|
|
}
|
|
|
|
// 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() throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetPathInfo() : NULL;
|
|
}
|
|
|
|
// Call this function to determine whether the current request was authenticated.
|
|
// Returns TRUE if the authentication type is one of the following:
|
|
// BASIC
|
|
// NTLM
|
|
// Negotiate
|
|
// Returns FALSE otherwise.
|
|
BOOL GetAuthenticated() throw(...)
|
|
{
|
|
// check for basic or NTLM authentication
|
|
CStringA strAuthType;
|
|
if (GetAuthenticationType(strAuthType) &&
|
|
(strAuthType == "BASIC" ||
|
|
strAuthType == "NTLM" ||
|
|
strAuthType == "Negotiate"))
|
|
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).
|
|
BOOL GetAuthenticationType(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("AUTH_TYPE", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// 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).
|
|
BOOL GetUserName(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("REMOTE_USER", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// 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).
|
|
BOOL GetUserAgent(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("HTTP_USER_AGENT", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// 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).
|
|
BOOL GetUserLanguages(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("HTTP_ACCEPT_LANGUAGE", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// 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).
|
|
BOOL GetAcceptTypes(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("HTTP_ACCEPT", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// 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).
|
|
BOOL GetAcceptEncodings(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("HTTP_ACCEPT_ENCODING", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
|
|
// 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).
|
|
BOOL GetUrlReferer(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("HTTP_REFERER", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// 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).
|
|
BOOL GetScriptName(LPSTR szBuff, DWORD *pdwSize) throw()
|
|
{
|
|
return m_spServerContext ? m_spServerContext->GetServerVariable("SCRIPT_NAME", szBuff, pdwSize) :
|
|
FALSE;
|
|
}
|
|
|
|
// Fills a buffer with the contents of the HTTP_COOKIE headers sent
|
|
// from the browser.
|
|
BOOL GetCookies(LPSTR szBuf, LPDWORD pdwSize) const throw()
|
|
{
|
|
ATLASSERT(pdwSize != NULL);
|
|
ATLASSERT(szBuf != NULL);
|
|
|
|
CStringA strCookie;
|
|
if (GetCookies(strCookie))
|
|
{
|
|
if (pdwSize && *pdwSize > (DWORD)strCookie.GetLength())
|
|
{
|
|
strcpy(szBuf, strCookie);
|
|
*pdwSize = strCookie.GetLength();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Fills a CStringA with the contents of the HTTP_COOKIE headers sent
|
|
// from the browser.
|
|
BOOL GetCookies(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(LPCSTR szName) throw()
|
|
{
|
|
static CCookie m_EmptyCookie;
|
|
if (GetRequestCookies())
|
|
{
|
|
// p->m_value is a const CCookie&
|
|
CookieMap::CPair *p = m_requestCookies.Lookup(szName);
|
|
if (p)
|
|
{
|
|
return p->m_value;
|
|
}
|
|
}
|
|
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(LPCSTR szVariable, CStringA &str) const throw()
|
|
{
|
|
if (!m_spServerContext)
|
|
return FALSE;
|
|
|
|
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.
|
|
BOOL GetPhysicalPath(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.
|
|
BOOL GetUserHostName(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.
|
|
BOOL GetUserHostAddress(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.
|
|
BOOL GetAuthenticationType(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.
|
|
BOOL GetUserName(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.
|
|
BOOL GetUserAgent(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.
|
|
BOOL GetUserLanguages(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.
|
|
BOOL GetAuthUserName(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.
|
|
BOOL GetAuthUserPassword(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.
|
|
BOOL GetUrl(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.
|
|
BOOL GetAcceptTypes(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.
|
|
BOOL GetAcceptEncodings(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.
|
|
BOOL GetUrlReferer(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.
|
|
BOOL GetScriptName(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.
|
|
BOOL GetRequestCookies() throw()
|
|
{
|
|
BOOL bRet = FALSE;
|
|
|
|
if (m_requestCookies.GetCount())
|
|
return TRUE; // we already got the cookies!
|
|
|
|
CStringA strCookies;
|
|
if (GetCookies(strCookies))
|
|
{
|
|
bRet = Parse((LPSTR)(LPCSTR)strCookies);
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
// 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(LPSTR szCookieIn) throw()
|
|
{
|
|
// The browser only sends back the data for the
|
|
// cookie, separated by ';'. Parse out all of the cookies
|
|
// in the cookie string and create CCookie's out of them which
|
|
// we add to our array of CCookies
|
|
// example 1: Param1=Value1; hello=world&right=wrong&the+direction=west;
|
|
// example 2: hello=world
|
|
|
|
if (!szCookieIn)
|
|
return FALSE;
|
|
LPSTR pEnd = szCookieIn;
|
|
LPSTR pStart = szCookieIn;
|
|
CStringA strCookieName;
|
|
_ATLTRY
|
|
{
|
|
while (1)
|
|
{
|
|
if (*pEnd == '\0' || *pEnd == ';')
|
|
{
|
|
CCookie c;
|
|
if (c.Parse(pStart))
|
|
{
|
|
if (c.GetName(strCookieName))
|
|
m_requestCookies.SetAt(strCookieName, c);
|
|
}
|
|
|
|
if (*pEnd)
|
|
pStart = pEnd+1;
|
|
}
|
|
if (*pEnd == '\0')
|
|
break;
|
|
pEnd++;
|
|
}
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) throw()
|
|
{
|
|
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() throw()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE Release() throw()
|
|
{
|
|
return 1;
|
|
}
|
|
}; // class CHttpRequest
|
|
|
|
LPCSTR __declspec(selectany) 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;
|
|
|
|
public:
|
|
// The default constructor.
|
|
CWriteStreamHelper() throw()
|
|
:m_pStream(NULL)
|
|
{
|
|
}
|
|
|
|
// The constructor.
|
|
CWriteStreamHelper(IWriteStream *pStream) throw()
|
|
{
|
|
m_pStream = pStream;
|
|
}
|
|
|
|
// Attach a IWriteStream
|
|
IWriteStream *Attach(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(LPCSTR szOut) throw()
|
|
{
|
|
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(int n) throw()
|
|
{
|
|
CHAR szTmp[21];
|
|
_itoa(n, 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(unsigned int u) throw()
|
|
{
|
|
CHAR szTmp[21];
|
|
_itoa((int)u, 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(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(long int dw) throw()
|
|
{
|
|
CHAR szTmp[21];
|
|
_ltoa(dw, 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(unsigned long int dw) throw()
|
|
{
|
|
CHAR szTmp[21];
|
|
_ultoa(dw, 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(double d, int nDigitCount=ATL_DEFAULT_PRECISION) throw()
|
|
{
|
|
CHAR szTmp[512];
|
|
int nDec = 0;
|
|
int nSign = 0;
|
|
bool fWriteDec=true;
|
|
strcpy(szTmp, _fcvt(d, nDigitCount, &nDec, &nSign));
|
|
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(__int64 i) throw()
|
|
{
|
|
CHAR szTmp[21];
|
|
_i64toa(i, 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(unsigned __int64 i) throw()
|
|
{
|
|
CHAR szTmp[21];
|
|
_ui64toa(i, 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(CURRENCY c) throw()
|
|
{
|
|
CHAR szDest[256];
|
|
CHAR szNumber[32];
|
|
|
|
#if 0
|
|
sprintf(szNumber, "%05I64d", c.int64);
|
|
int nLen = (int) strlen(szNumber);
|
|
#else
|
|
_i64toa(c.int64, szNumber, 10);
|
|
int nLen = (int) strlen(szNumber);
|
|
if (nLen < 5)
|
|
{
|
|
// prepend ascii zeros
|
|
memmove(szNumber+5-nLen, szNumber, nLen+1);
|
|
memset(szNumber, '0', 5-nLen);
|
|
nLen = 5;
|
|
}
|
|
#endif
|
|
memmove(szNumber+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(LPCWSTR wsz) throw()
|
|
{
|
|
BOOL bRet;
|
|
|
|
_ATLTRY
|
|
{
|
|
CW2A sz(wsz);
|
|
|
|
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<<(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<<(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<<(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<<(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<<(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<<(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<<(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<<(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<<(__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<<(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<<(CURRENCY c) throw(...)
|
|
{
|
|
if (!Write(c))
|
|
AtlThrow(E_FAIL);
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
|
|
// 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
|
|
{
|
|
protected:
|
|
// 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
|
|
};
|
|
|
|
// The default constructor.
|
|
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;
|
|
}
|
|
|
|
// The constructor.
|
|
CHttpResponse(IHttpServerContext *pServerContext) throw()
|
|
{
|
|
m_bBufferOutput = TRUE;
|
|
m_dwBufferLimit = ULONG_MAX;
|
|
m_nStatusCode = 200;
|
|
m_pStream = this;
|
|
m_bHeadersSent = FALSE;
|
|
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.
|
|
~CHttpResponse() throw()
|
|
{
|
|
// if (m_strContent.GetLength())
|
|
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.
|
|
BOOL Initialize(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.
|
|
BOOL Initialize(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.
|
|
HRESULT GetServerContext(IHttpServerContext ** ppOut) throw()
|
|
{
|
|
return m_spServerContext.CopyTo(ppOut);
|
|
}
|
|
|
|
// 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(BOOL bBufferOutput, 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.
|
|
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.
|
|
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(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(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(LPCSTR szContentType) throw()
|
|
{
|
|
_ATLTRY
|
|
{
|
|
if (!m_headers.SetAt("Content-Type", szContentType))
|
|
return m_headers.Add("Content-Type", szContentType);
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// 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(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(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(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(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(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.
|
|
HRESULT WriteStream(LPCSTR szOut, int nLen, DWORD *pdwWritten) throw()
|
|
{
|
|
ATLASSERT(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.
|
|
BOOL WriteLen(LPCSTR szOut, DWORD dwLen) throw()
|
|
{
|
|
ATLASSERT(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(LPCSTR szUrl, HTTP_REDIRECT statusCode=HTTP_REDIRECT_MOVED, 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
|
|
{
|
|
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(LPCSTR szUrl, LPCSTR szBody, HTTP_REDIRECT statusCode=HTTP_REDIRECT_MOVED) throw()
|
|
{
|
|
// todo: handle multiple
|
|
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(LPCSTR szName, LPCSTR szValue) throw()
|
|
{
|
|
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(const CCookie *pCookie) throw()
|
|
{
|
|
ATLASSERT(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(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)
|
|
{
|
|
bRet = m_headers.Add("Set-Cookie", szCookie);
|
|
}
|
|
|
|
if (!bRet && dwBuffSize > 0) //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))
|
|
{
|
|
bRet = m_headers.Add("Set-Cookie", (const char *) sz);
|
|
}
|
|
}
|
|
}
|
|
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(LPCSTR szName, LPCSTR szValue) throw()
|
|
{
|
|
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(LPCSTR szName) throw()
|
|
{
|
|
BOOL bRet = FALSE;
|
|
_ATLTRY
|
|
{
|
|
CCookie cookie(szName, "");
|
|
cookie.SetMaxAge(0);
|
|
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(BOOL fKeepConn=FALSE) throw()
|
|
{
|
|
if (m_bHeadersSent)
|
|
return TRUE;
|
|
|
|
ATLASSERT(m_spServerContext != NULL);
|
|
|
|
CStringA strHeaders;
|
|
|
|
RenderHeaders(strHeaders);
|
|
|
|
// REVIEW: should fix this to use the user's custom error provider
|
|
CDefaultErrorProvider prov;
|
|
|
|
BOOL bRet = FALSE;
|
|
_ATLTRY
|
|
{
|
|
CStringA strStatus = GetStatusHeader(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
|
|
{
|
|
for (int i=0; i<m_headers.GetSize(); i++)
|
|
{
|
|
strHeaders += m_headers.GetKeyAt(i);
|
|
strHeaders += ": ";
|
|
strHeaders += m_headers.GetValueAt(i);
|
|
strHeaders += "\r\n";
|
|
}
|
|
strHeaders += "\r\n";
|
|
}
|
|
_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() throw()
|
|
{
|
|
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");
|
|
_itoa(m_strContent.GetLength(), 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(BOOL fKeepConn=FALSE) throw()
|
|
{
|
|
ATLASSERT(m_spServerContext != NULL);
|
|
|
|
return SendHeadersInternal(fKeepConn);
|
|
}
|
|
|
|
BOOL AsyncFlush() throw()
|
|
{
|
|
ATLASSERT(m_spServerContext != NULL);
|
|
|
|
BOOL bRet = SendHeadersInternal();
|
|
|
|
if (bRet && m_bBufferOutput)
|
|
{
|
|
DWORD dwLen = 0;
|
|
|
|
dwLen = m_strContent.GetLength();
|
|
if (dwLen)
|
|
{
|
|
if (m_spServerContext->AsyncWriteClient((void *) (LPCSTR) m_strContent, &dwLen) != TRUE)
|
|
return FALSE;
|
|
}
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
BOOL TransmitFile(HANDLE hFile, LPCSTR szContentType="text/plain") throw()
|
|
{
|
|
ATLASSERT(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)
|
|
{
|
|
bRet = m_spServerContext->TransmitFile(hFile, NULL, NULL, NULL,
|
|
0, 0, NULL, 0, NULL, 0, HSE_IO_ASYNC);
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
}; // class CHttpResponse
|
|
|
|
|
|
|
|
#define ATLS_FLAG_NONE 0
|
|
#define ATLS_FLAG_ASYNC 1 // handler might do some async handling
|
|
|
|
//REVIEW: push_macro/pop_macro don't work in a template definition.
|
|
//these have been moved out of IRequestHandlerImpl temporarily because
|
|
//of placement new usage
|
|
#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;
|
|
HeapFree(pWorker->m_hHeap, HEAP_NO_SERIALIZE, this);
|
|
}
|
|
return l;
|
|
}
|
|
};
|
|
|
|
template <typename THandler>
|
|
inline BOOL CreateRequestHandlerSync(IIsapiExtension *pExtension, IUnknown **ppOut) throw()
|
|
{
|
|
CIsapiWorker *pWorker = pExtension->GetThreadWorker();
|
|
ATLASSERT(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() throw() \
|
|
{ \
|
|
return ATLS_FLAG_ASYNC; \
|
|
} \
|
|
DWORD GetAsyncFlags() throw() \
|
|
{ \
|
|
return ATLSRV_INIT_USEASYNC; \
|
|
}
|
|
|
|
#define DECLARE_ASYNC_HANDLER_EX() \
|
|
static DWORD GetHandlerFlags() throw() \
|
|
{ \
|
|
return ATLS_FLAG_ASYNC; \
|
|
} \
|
|
DWORD GetAsyncFlags() throw() \
|
|
{ \
|
|
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() throw()
|
|
:m_hInstHandler(NULL)
|
|
{
|
|
m_dwAsyncFlags = 0;
|
|
}
|
|
|
|
HTTP_CODE GetFlags(DWORD *pdwStatus) throw(...)
|
|
{
|
|
THandler *pT = static_cast<THandler *>(this);
|
|
if (pdwStatus)
|
|
{
|
|
*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(AtlServerRequest *pRequestInfo, IServiceProvider *pProvider) throw()
|
|
{
|
|
ATLASSERT(pRequestInfo != NULL);
|
|
ATLASSERT(pProvider != NULL);
|
|
ATLASSERT(pRequestInfo->hInstDll != NULL);
|
|
ATLASSERT(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(AtlServerRequest *pRequestInfo, IServiceProvider *pProvider, IHttpRequestLookup * /*pLookup*/) throw()
|
|
{
|
|
return InitializeHandler(pRequestInfo, pProvider);
|
|
}
|
|
|
|
void UninitializeHandler() throw()
|
|
{
|
|
}
|
|
|
|
HTTP_CODE HandleRequest(
|
|
AtlServerRequest* /*pRequestInfo*/,
|
|
IServiceProvider* /*pServiceProvider*/) throw()
|
|
{
|
|
return HTTP_SUCCESS;
|
|
}
|
|
|
|
DWORD GetAsyncFlags() throw()
|
|
{
|
|
return m_dwAsyncFlags;
|
|
}
|
|
|
|
void SetAsyncFlags(DWORD dwAsyncFlags) throw()
|
|
{
|
|
ATLASSERT((dwAsyncFlags & ~(ATLSRV_INIT_USEASYNC|ATLSRV_INIT_USEASYNC_EX)) == 0);
|
|
m_dwAsyncFlags = dwAsyncFlags;
|
|
}
|
|
|
|
BOOL CachePage() throw()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static DWORD GetHandlerFlags() throw()
|
|
{
|
|
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(IIsapiExtension *pExtension, IUnknown **ppOut) throw()
|
|
{
|
|
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) throw()
|
|
{
|
|
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() throw()
|
|
{
|
|
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(AtlServerRequest *pRequestInfo, BOOL bSuccess) throw()
|
|
{
|
|
InterlockedIncrement(&m_lTotalRequests);
|
|
if (!bSuccess)
|
|
InterlockedIncrement(&m_lFailedRequests);
|
|
|
|
long lTicks;
|
|
|
|
#ifndef ATL_NO_MMSYS
|
|
lTicks = (long) (timeGetTime() - pRequestInfo->dwStartTicks);
|
|
#else
|
|
lTicks = GetTickCount();
|
|
#endif
|
|
m_liTotalResponseTime += lTicks;
|
|
long lAv = (long) (m_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()
|
|
{
|
|
InterlockedIncrement(&m_lCurrWaiting);
|
|
if (m_lCurrWaiting > m_lMaxWaiting)
|
|
InterlockedExchange(&m_lMaxWaiting, m_lCurrWaiting);
|
|
}
|
|
|
|
void OnRequestDequeued() throw()
|
|
{
|
|
InterlockedDecrement(&m_lCurrWaiting);
|
|
InterlockedIncrement(&m_lActiveThreads);
|
|
}
|
|
|
|
long GetCurrWaiting() throw()
|
|
{
|
|
return m_lCurrWaiting;
|
|
}
|
|
|
|
long GetMaxWaiting() throw()
|
|
{
|
|
return m_lCurrWaiting;
|
|
}
|
|
|
|
long GetActiveThreads() throw()
|
|
{
|
|
return m_lActiveThreads;
|
|
}
|
|
};
|
|
|
|
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_OBJECT_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_OBJECT(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(PERF_REQUEST_OBJECT, L"_Total", reinterpret_cast<CPerfObject**>(&m_pPerfObjectTotal));
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
hr = m_PerfMon.CreateInstanceByName(PERF_REQUEST_OBJECT, szName, reinterpret_cast<CPerfObject**>(&m_pPerfObjectInstance));
|
|
if (FAILED(hr))
|
|
{
|
|
m_PerfMon.ReleaseInstance(m_pPerfObjectTotal);
|
|
m_pPerfObjectTotal = NULL;
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
void Uninitialize() throw()
|
|
{
|
|
if (m_pPerfObjectInstance)
|
|
m_PerfMon.ReleaseInstance(m_pPerfObjectInstance);
|
|
if (m_pPerfObjectTotal)
|
|
m_PerfMon.ReleaseInstance(m_pPerfObjectTotal);
|
|
|
|
m_pPerfObjectInstance = NULL;
|
|
m_pPerfObjectTotal = NULL;
|
|
|
|
m_PerfMon.UnInitialize();
|
|
}
|
|
|
|
void RequestHandled(AtlServerRequest *pRequestInfo, BOOL bSuccess) throw()
|
|
{
|
|
CPerfLock lock(&m_PerfMon);
|
|
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;
|
|
}
|
|
};
|
|
|
|
|
|
inline LPSTR StripHandlerComment(LPSTR szLine) throw()
|
|
{
|
|
if (!memcmp(szLine, "<!--", 4))
|
|
{
|
|
szLine += 4;
|
|
while (_istspace(*szLine))
|
|
szLine++;
|
|
LPSTR szEndComment = strstr(szLine, "-->");
|
|
if (szEndComment)
|
|
*szEndComment = '\0';
|
|
return szLine;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct ATLServerDllInfo
|
|
{
|
|
GETATLHANDLERBYNAME pfnGetHandler;
|
|
UNINITIALIZEATLHANDLERS pfnUninitHandlers;
|
|
INITIALIZEATLHANDLERS pfnInitHandlers;
|
|
IIsapiExtension *pExtension;
|
|
IHttpServerContext *pContext;
|
|
};
|
|
|
|
class CDllCachePeer
|
|
{
|
|
public:
|
|
struct DllInfo : public ATLServerDllInfo
|
|
{
|
|
operator=(const DllInfo& right) throw()
|
|
{
|
|
pfnGetHandler = right.pfnGetHandler;
|
|
pfnUninitHandlers = right.pfnUninitHandlers;
|
|
pfnInitHandlers = right.pfnInitHandlers;
|
|
pExtension = right.pExtension;
|
|
pContext = right.pContext;
|
|
}
|
|
};
|
|
|
|
BOOL Add(HINSTANCE hInst, DllInfo *pInfo) throw(...)
|
|
{
|
|
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*/, DllInfo *pInfo) throw(...)
|
|
{
|
|
if (pInfo->pfnUninitHandlers)
|
|
(*pInfo->pfnUninitHandlers)();
|
|
}
|
|
|
|
};
|
|
|
|
inline bool operator==(const CDllCachePeer::DllInfo& left, 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(IHttpServerContext *pServerContext) throw()
|
|
{
|
|
// impersonate the calling client on the current thread
|
|
HANDLE hToken;
|
|
if (!pServerContext->GetImpersonationToken(&hToken))
|
|
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:
|
|
BOOL Initialize(AtlServerRequest *pRequestInfo) throw()
|
|
{
|
|
return AtlImpersonateClient(pRequestInfo->pServerContext);
|
|
}
|
|
|
|
~CSetThreadToken() throw()
|
|
{
|
|
RevertToSelf();
|
|
}
|
|
};
|
|
|
|
|
|
//REVIEW: push_macro/pop_macro don't work in a template definition.
|
|
//this has been moved out of temporarily because
|
|
//of placement new usage
|
|
#pragma push_macro("new")
|
|
#undef new
|
|
template <class Base>
|
|
class _CComObjectHeap : public Base
|
|
{
|
|
public:
|
|
typedef Base _BaseClass;
|
|
HANDLE m_hHeap;
|
|
_CComObjectHeap(HANDLE hHeap) throw()
|
|
{
|
|
m_hHeap = hHeap;
|
|
}
|
|
// Set refcount to 1 to protect destruction
|
|
~_CComObjectHeap() throw()
|
|
{
|
|
m_dwRef = 1L;
|
|
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->~_CComObjectHeap();
|
|
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);}
|
|
};
|
|
|
|
inline CServerContext* CreateServerContext(HANDLE hRequestHeap) throw()
|
|
{
|
|
// Allocate a fixed block size to avoid fragmentation
|
|
void *pv = HeapAlloc(hRequestHeap, HEAP_ZERO_MEMORY,
|
|
max(sizeof(AtlServerRequest), sizeof(_CComObjectHeap<CServerContext>)));
|
|
if (!pv)
|
|
return FALSE;
|
|
|
|
_CComObjectHeap<CServerContext>* pContext = new(pv) _CComObjectHeap<CServerContext>(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(LPCSTR szFileName, LPSTR szHandlerName) throw()
|
|
{
|
|
szHandlerName[0] = '\0';
|
|
CAtlFile cfFile;
|
|
HRESULT hr;
|
|
|
|
_ATLTRY
|
|
{
|
|
hr = cfFile.Create(CA2TEX<MAX_PATH+1>(szFileName), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING);
|
|
if (FAILED(hr))
|
|
return HTTP_ERROR(500, ISE_SUBERR_LOADFILEFAIL);
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return ISE_SUBERR_OUTOFMEM; // CA2TEX threw
|
|
}
|
|
|
|
if (cfFile.m_h == NULL || GetFileType(cfFile.m_h) != FILE_TYPE_DISK)
|
|
{
|
|
if (hr == AtlHresultFromWin32(ERROR_FILE_NOT_FOUND))
|
|
return HTTP_ERROR(404, SUBERR_NONE);
|
|
else
|
|
return HTTP_ERROR(500, IDS_ATLSRV_SERVER_ERROR_STENCILLOADFAIL);
|
|
}
|
|
|
|
HTTP_CODE hcErr = HTTP_SUCCESS;
|
|
DWORD dwRead=0;
|
|
LPSTR szHandler = "handler";
|
|
LPSTR 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, sizeof(szBuf)-1, dwRead);
|
|
if (hr != S_OK)
|
|
{
|
|
return HTTP_ERROR(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(*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(*pszBuf))
|
|
{
|
|
if (*pszBuf == '\"')
|
|
{
|
|
bInQuote = true;
|
|
}
|
|
else
|
|
{
|
|
pszBuf--;
|
|
}
|
|
nState = 5;
|
|
}
|
|
break;
|
|
case 5:
|
|
// 5 = have "handler" -- get name
|
|
if (isspace(*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 = HTTP_ERROR(500, ISE_SUBERR_BAD_HANDLER_TAG);
|
|
nState = 8;
|
|
}
|
|
if (*szHandlerName)
|
|
nState = 8;
|
|
else
|
|
nState = 0;
|
|
break;
|
|
default:
|
|
__assume( 0 ); // the optimizer will not generate code for this branch
|
|
}
|
|
|
|
pszBuf++;
|
|
}
|
|
} while (dwRead != 0 && nState != 8);
|
|
|
|
*pszHandlerName = '\0';
|
|
|
|
return hcErr;
|
|
}
|
|
|
|
// _AtlCrackHandler cracks a request path of the form dll_path/handler_name into its
|
|
// consituent parts
|
|
// szHandlerDllName - the full handler path of the form "dll_path/handler_name"
|
|
// szDllPath - the DLL path (should be of length MAX_PATH+1)
|
|
// szHandlerName - the handler name (should be of length ATL_MAX_HANDLER_NAME_LEN+1)
|
|
//
|
|
inline BOOL _AtlCrackHandler(
|
|
LPCSTR szHandlerDllName,
|
|
LPSTR szDllPath,
|
|
LPDWORD pdwDllPathLen,
|
|
LPSTR szHandlerName,
|
|
LPDWORD pdwHandlerNameLen) throw()
|
|
{
|
|
ATLASSERT( szHandlerDllName != NULL );
|
|
ATLASSERT( szDllPath != NULL );
|
|
ATLASSERT( pdwDllPathLen != NULL );
|
|
ATLASSERT( szHandlerName != NULL );
|
|
ATLASSERT( pdwHandlerNameLen != NULL );
|
|
|
|
BOOL bRet = TRUE;
|
|
|
|
// skip leading spaces
|
|
while (*szHandlerDllName && isspace(*szHandlerDllName))
|
|
++szHandlerDllName;
|
|
|
|
// get the handler name
|
|
LPSTR szSlash = strchr(szHandlerDllName, '/');
|
|
LPSTR szEnd = NULL;
|
|
LPSTR 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(*(szEnd-1)))
|
|
--szEnd;
|
|
|
|
szSlash++;
|
|
// skip leading whitespace
|
|
while (*szSlash && isspace(*szSlash))
|
|
szSlash++;
|
|
|
|
// right trim szSlash;
|
|
szSlashEnd = szSlash;
|
|
while (*szSlashEnd && !isspace(*szSlashEnd))
|
|
szSlashEnd++;
|
|
}
|
|
else // only the <dll_name>
|
|
{
|
|
szSlash = "Default";
|
|
szSlashEnd = szSlash+sizeof("Default")-1;
|
|
|
|
// do it this way to handle paths with spaces
|
|
// (e.g. "some path\subdirectory one\subdirectory two\dll_name.dll")
|
|
szEnd = (LPSTR) (szHandlerDllName+strlen(szHandlerDllName));
|
|
|
|
// skip trailing spaces
|
|
while (szEnd>szHandlerDllName && isspace(*(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))
|
|
{
|
|
memcpy(szDllPath, szHandlerDllName, szEnd-szHandlerDllName);
|
|
szDllPath[szEnd-szHandlerDllName] = '\0';
|
|
*pdwDllPathLen = (DWORD)(szEnd-szHandlerDllName);
|
|
}
|
|
else
|
|
{
|
|
*pdwDllPathLen = (DWORD)(szEnd-szHandlerDllName)+1;
|
|
bRet = FALSE;
|
|
}
|
|
|
|
if (*pdwHandlerNameLen > (DWORD)(szSlashEnd-szSlash))
|
|
{
|
|
memcpy(szHandlerName, szSlash, (szSlashEnd-szSlash));
|
|
szHandlerName[szSlashEnd-szSlash] = '\0';
|
|
*pdwHandlerNameLen = (DWORD)(szSlashEnd-szSlash);
|
|
}
|
|
else
|
|
{
|
|
*pdwHandlerNameLen = (DWORD)(szSlashEnd-szSlash)+1;
|
|
bRet = FALSE;
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
inline HTTP_CODE _AtlLoadRequestHandler(
|
|
LPCSTR szDllPath,
|
|
LPCSTR szHandlerName,
|
|
IHttpServerContext *pServerContext,
|
|
HINSTANCE *phInstance,
|
|
IRequestHandler **ppHandler,
|
|
IIsapiExtension *pExtension,
|
|
IDllCache *pDllCache) throw(...)
|
|
{
|
|
*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 HTTP_ERROR(500, ISE_SUBERR_LOADLIB);
|
|
}
|
|
|
|
CComPtr<IUnknown> spUnk;
|
|
|
|
if (!DllInfo.pfnGetHandler ||
|
|
!DllInfo.pfnGetHandler(szHandlerName, pExtension, &spUnk) ||
|
|
spUnk->QueryInterface(__uuidof(IRequestHandler), (void **)ppHandler))
|
|
{
|
|
pDllCache->Free(*phInstance);
|
|
*phInstance = NULL;
|
|
return HTTP_ERROR(500, ISE_SUBERR_HANDLER_NOT_FOUND);
|
|
}
|
|
|
|
return HTTP_SUCCESS;
|
|
} // _AtlLoadRequestHandler
|
|
|
|
|
|
class CTransferServerContext : public CComObjectRootEx<CComMultiThreadModel>,
|
|
public CWrappedServerContext
|
|
{
|
|
public:
|
|
char m_szFileName[MAX_PATH+1];
|
|
char m_szQueryString[ATL_URL_MAX_PATH_LENGTH+1];
|
|
IWriteStream *m_pStream;
|
|
|
|
BEGIN_COM_MAP(CTransferServerContext)
|
|
COM_INTERFACE_ENTRY(IHttpServerContext)
|
|
END_COM_MAP()
|
|
|
|
CTransferServerContext() throw()
|
|
{
|
|
m_pStream = NULL;
|
|
}
|
|
|
|
BOOL Initialize(LPCSTR szUrl, IWriteStream *pStream, IHttpServerContext *pParent) throw()
|
|
{
|
|
m_pStream = pStream;
|
|
m_spParent = pParent;
|
|
m_szFileName[0] = '\0';
|
|
long nUrlLen = (long)strlen(szUrl);
|
|
|
|
if (!IsFullPathA(szUrl))
|
|
{
|
|
DWORD dwLen = MAX_PATH;
|
|
BOOL bRet = m_spParent->GetServerVariable(
|
|
"APPL_PHYSICAL_PATH",
|
|
m_szFileName,
|
|
&dwLen);
|
|
if (!bRet)
|
|
return FALSE;
|
|
}
|
|
|
|
// check for query params
|
|
LPCSTR szMark = strchr(szUrl, '?');
|
|
if (szMark)
|
|
{
|
|
long nPathLen = (long) (szMark - szUrl);
|
|
if (strlen(m_szFileName) + nPathLen < MAX_PATH)
|
|
{
|
|
if (m_szFileName[0])
|
|
strncat(m_szFileName, szUrl, nPathLen);
|
|
else
|
|
{
|
|
memcpy(m_szFileName, szUrl, nPathLen);
|
|
m_szFileName[nPathLen] = '\0';
|
|
}
|
|
}
|
|
else
|
|
return FALSE; // path would overwrite buffer
|
|
|
|
// save query params
|
|
if (strlen(szMark + 1) < ATL_URL_MAX_PATH_LENGTH)
|
|
strcpy(m_szQueryString, szMark+1);
|
|
else
|
|
return FALSE; // url would overwrite buffer
|
|
}
|
|
else
|
|
{
|
|
// no query string
|
|
if (strlen(m_szFileName) + nUrlLen < MAX_PATH)
|
|
{
|
|
if (m_szFileName[0])
|
|
strcat(m_szFileName, szUrl);
|
|
else
|
|
strcpy(m_szFileName, szUrl);
|
|
}
|
|
else
|
|
return FALSE; // path would be too long
|
|
m_szQueryString[0] = '\0';
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL WriteClient(void *pvBuffer, DWORD *pdwBytes) throw()
|
|
{
|
|
HRESULT hr = m_pStream->WriteStream((LPCSTR) pvBuffer, *pdwBytes, pdwBytes);
|
|
return SUCCEEDED(hr);
|
|
}
|
|
|
|
|
|
LPCSTR GetQueryString() throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_szQueryString;
|
|
}
|
|
|
|
LPCSTR GetScriptPathTranslated() throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_szFileName;
|
|
}
|
|
|
|
LPCSTR GetPathTranslated() throw()
|
|
{
|
|
ATLASSERT(m_spParent);
|
|
return m_szFileName;
|
|
}
|
|
|
|
// Asynchronous writes will not work properly in a child handler
|
|
BOOL AsyncWriteClient(void * /*pvBuffer*/, DWORD * /*pdwBytes*/) throw()
|
|
{
|
|
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() throw()
|
|
{
|
|
ATLASSERT(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*/) throw()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL AsyncReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/) throw()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
DWORD GetTotalBytes() throw()
|
|
{
|
|
ATLASSERT(FALSE);
|
|
return 0;
|
|
}
|
|
|
|
DWORD GetAvailableBytes() throw()
|
|
{
|
|
ATLASSERT(FALSE);
|
|
return 0;
|
|
}
|
|
|
|
BYTE *GetAvailableData() throw()
|
|
{
|
|
ATLASSERT(FALSE);
|
|
return NULL;
|
|
}
|
|
|
|
LPCSTR GetContentType() throw()
|
|
{
|
|
ATLASSERT(FALSE);
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
inline HTTP_CODE _AtlTransferRequest(
|
|
AtlServerRequest *pRequest,
|
|
IServiceProvider *pServiceProvider,
|
|
IWriteStream *pWriteStream,
|
|
IHttpRequestLookup *pLookup,
|
|
LPCSTR szNewUrl,
|
|
WORD nCodePage,
|
|
bool bContinueAfterProcess = false,
|
|
void *pState = NULL) throw(...)
|
|
{
|
|
ATLASSERT(pRequest != NULL);
|
|
AtlServerRequest* pRequestInfo = NULL;
|
|
HTTP_CODE dwErr = HTTP_SUCCESS;
|
|
|
|
CComPtr<IStencilCache> spStencilCache;
|
|
|
|
if (pRequest->pServerContext == NULL)
|
|
return HTTP_ERROR(500, 0);
|
|
|
|
pServiceProvider->QueryService(
|
|
__uuidof(IStencilCache),
|
|
__uuidof(IStencilCache),
|
|
(void**)&spStencilCache
|
|
);
|
|
if (!spStencilCache)
|
|
return HTTP_ERROR(500, 0);
|
|
|
|
CComObjectStackEx<CTransferServerContext> serverContext;
|
|
serverContext.Initialize(szNewUrl, pWriteStream, pRequest->pServerContext);
|
|
|
|
CStencilState* _pState = reinterpret_cast<CStencilState*>(pState);
|
|
if (_pState && _pState->pIncludeInfo)
|
|
{
|
|
pRequestInfo = _pState->pIncludeInfo;
|
|
_pState->pIncludeInfo = NULL;
|
|
}
|
|
else
|
|
{
|
|
ATLASSERT(spStencilCache != NULL);
|
|
ATLASSERT(pRequest->pDllCache != NULL);
|
|
ATLASSERT(pRequest->pExtension != NULL);
|
|
|
|
pRequestInfo = pRequest->pExtension->CreateRequest();
|
|
if (pRequestInfo == NULL)
|
|
return HTTP_ERROR(500, ISE_SUBERR_OUTOFMEM);
|
|
|
|
pRequestInfo->dwRequestState = ATLSRV_STATE_BEGIN;
|
|
pRequestInfo->dwRequestType = ATLSRV_REQUEST_STENCIL;
|
|
pRequestInfo->pDllCache = pRequest->pDllCache;
|
|
pRequestInfo->pExtension = pRequest->pExtension;
|
|
pRequestInfo->pServerContext = &serverContext;
|
|
if (_pState)
|
|
pRequestInfo->pUserData = _pState->pParentInfo->pUserData;
|
|
else
|
|
pRequestInfo->pUserData = pRequest->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
|
|
LPSTR szDot = NULL;
|
|
LPSTR szMark = serverContext.m_szFileName;
|
|
while (*szMark)
|
|
{
|
|
if (*szMark == '.')
|
|
szDot = szMark;
|
|
|
|
szMark = CharNextExA(nCodePage, szMark, 0);
|
|
}
|
|
|
|
if (szDot && _stricmp(szDot, c_AtlSRFExtension) == 0)
|
|
{
|
|
dwErr = pRequest->pExtension->LoadDispatchFile(
|
|
serverContext.m_szFileName,
|
|
pRequestInfo
|
|
);
|
|
if (dwErr)
|
|
return dwErr;
|
|
|
|
CComPtr<IHttpRequestLookup> spLookup;
|
|
DWORD dwStatus;
|
|
if (pLookup)
|
|
|
|
{
|
|
dwErr = pRequestInfo->pHandler->GetFlags(&dwStatus);
|
|
if (dwErr)
|
|
return dwErr;
|
|
|
|
if (dwStatus & (ATLSRV_INIT_USEASYNC | ATLSRV_INIT_USEASYNC_EX))
|
|
{
|
|
CComObjectNoLock<CTransferServerContext>* pServerContext = NULL;
|
|
ATLTRY(pServerContext = new CComObjectNoLock<CTransferServerContext>);
|
|
if (pServerContext == NULL)
|
|
return HTTP_ERROR(500, ISE_SUBERR_OUTOFMEM);
|
|
pServerContext->Initialize(szNewUrl, pWriteStream, pRequest->pServerContext);
|
|
|
|
pServerContext->AddRef();
|
|
pRequestInfo->pServerContext = pServerContext;
|
|
}
|
|
|
|
dwErr = pRequestInfo->pHandler->InitializeChild(
|
|
pRequestInfo,
|
|
pServiceProvider,
|
|
pLookup);
|
|
if (dwErr)
|
|
return dwErr;
|
|
|
|
}
|
|
}
|
|
else if (szDot && _stricmp(szDot, ".dll") == 0)
|
|
{
|
|
// Get the handler name if they used the asdf.dll?Handler=Default notation
|
|
// REVIEW : case sensitivity on the "Handler"?
|
|
char szHandlerName[ATL_MAX_HANDLER_NAME_LEN+1] = { '\0' };
|
|
|
|
LPSTR szStart = strstr(serverContext.m_szQueryString, "Handler");
|
|
if (szStart)
|
|
{
|
|
szStart += 8; // Skip past "Handler" and the "="
|
|
LPSTR szEnd = strchr(szStart, '&');
|
|
if (szEnd)
|
|
{
|
|
memcpy(szHandlerName, szStart, min((szEnd-szStart), ATL_MAX_HANDLER_NAME_LEN));
|
|
szHandlerName[min((szEnd-szStart), ATL_MAX_HANDLER_NAME_LEN)] = '\0';
|
|
}
|
|
else
|
|
{
|
|
strcpy(szHandlerName, szStart);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
memcpy(szHandlerName, "Default", sizeof("Default"));
|
|
}
|
|
|
|
pRequestInfo->dwRequestType = ATLSRV_REQUEST_DLL;
|
|
|
|
dwErr = pRequest->pExtension->LoadRequestHandler(
|
|
serverContext.m_szFileName,
|
|
szHandlerName,
|
|
pRequestInfo->pServerContext,
|
|
&pRequestInfo->hInstDll,
|
|
&pRequestInfo->pHandler
|
|
);
|
|
if (dwErr != HTTP_SUCCESS)
|
|
return dwErr;
|
|
|
|
ATLASSERT(pLookup);
|
|
dwErr = pRequestInfo->pHandler->InitializeChild(
|
|
pRequestInfo,
|
|
pServiceProvider,
|
|
pLookup
|
|
);
|
|
}
|
|
|
|
pRequestInfo->pfnHandleRequest = &IRequestHandler::HandleRequest;
|
|
}
|
|
|
|
if (pRequestInfo)
|
|
{
|
|
if (!dwErr)
|
|
{
|
|
if (pRequestInfo->pServerContext == NULL)
|
|
pRequestInfo->pServerContext = &serverContext;
|
|
|
|
ATLASSERT(pRequestInfo->pfnHandleRequest != NULL);
|
|
dwErr = (pRequestInfo->pHandler->*pRequestInfo->pfnHandleRequest)(pRequestInfo, pServiceProvider);
|
|
|
|
if (pRequestInfo->pServerContext == &serverContext)
|
|
pRequestInfo->pServerContext = NULL;
|
|
|
|
if (IsAsyncStatus(dwErr))
|
|
{
|
|
ATLASSERT(pState); // state is required for async
|
|
if (IsAsyncContinueStatus(dwErr))
|
|
{
|
|
_pState->pIncludeInfo = pRequestInfo;
|
|
pRequestInfo->dwRequestState = ATLSRV_STATE_CONTINUE;
|
|
}
|
|
else if (IsAsyncDoneStatus(dwErr))
|
|
pRequest->pExtension->FreeRequest(pRequestInfo);
|
|
}
|
|
else
|
|
pRequest->pExtension->FreeRequest(pRequestInfo);
|
|
}
|
|
}
|
|
else
|
|
dwErr = HTTP_ERROR(500, ISE_SUBERR_UNEXPECTED);
|
|
|
|
if (dwErr == HTTP_SUCCESS && bContinueAfterProcess)
|
|
return dwErr;
|
|
return HTTP_SUCCESS_NO_PROCESS;
|
|
}
|
|
|
|
//
|
|
// Used to terminate process when buffer security check fails
|
|
//
|
|
inline void __cdecl AtlsSecErrHandlerFunc(int nCode, void * /* pv */)
|
|
{
|
|
nCode;
|
|
|
|
#if defined(_M_IX86)
|
|
//
|
|
// only valid code
|
|
//
|
|
ATLASSERT( nCode == _SECERR_BUFFER_OVERRUN );
|
|
#endif
|
|
|
|
//
|
|
// 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.
|
|
// WorkerThreadClass: The worker thread that will be used for this extension.
|
|
// The worker thread will be used to sweep any caches used by the extension
|
|
// and perform other periodic maintanence work while the extension is
|
|
// loaded. You would provide your own worker thread class to customize
|
|
// when the worker thread times out.
|
|
// 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
|
|
{
|
|
#ifndef ATL_NO_CRITICAL_ISAPI_ERROR
|
|
|
|
DWORD m_dwCriticalIsapiError;
|
|
|
|
#endif // ATL_NO_CRITICAL_ISAPI_ERROR
|
|
|
|
protected:
|
|
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(const ServiceNode& t1, const ServiceNode& t2) throw()
|
|
{
|
|
return (InlineIsEqualGUID(t1.guidService, t2.guidService) != 0 &&
|
|
InlineIsEqualGUID(t1.riid, t2.riid) != 0);
|
|
}
|
|
};
|
|
|
|
CSimpleArray<ServiceNode, CServiceEqualHelper> m_serviceMap;
|
|
|
|
public:
|
|
DWORD m_dwTlsIndex;
|
|
CWin32Heap m_heap;
|
|
|
|
CRequestStatClass m_reqStats;
|
|
|
|
AtlServerRequest *CreateRequest() throw()
|
|
{
|
|
// Allocate a fixed block size to avoid fragmentation
|
|
AtlServerRequest *pRequest = (AtlServerRequest *) HeapAlloc(m_hRequestHeap,
|
|
HEAP_ZERO_MEMORY, max(sizeof(AtlServerRequest), sizeof(_CComObjectHeap<CServerContext>)));
|
|
if (!pRequest)
|
|
return NULL;
|
|
pRequest->cbSize = sizeof(AtlServerRequest);
|
|
return pRequest;
|
|
}
|
|
|
|
void FreeRequest(AtlServerRequest *pRequest) throw()
|
|
{
|
|
_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(
|
|
AtlServerRequest *pRequest,
|
|
IServiceProvider *pServiceProvider,
|
|
IWriteStream *pWriteStream,
|
|
IHttpRequestLookup *pLookup,
|
|
LPCSTR szNewUrl,
|
|
WORD nCodePage,
|
|
bool bContinueAfterProcess = false,
|
|
void *pState = NULL
|
|
) throw(...)
|
|
{
|
|
return _AtlTransferRequest(pRequest, pServiceProvider, pWriteStream,
|
|
pLookup, szNewUrl, nCodePage, bContinueAfterProcess, pState);
|
|
}
|
|
|
|
#ifndef ATL_NO_CRITICAL_ISAPI_ERROR
|
|
DWORD ReturnCriticalError(EXTENSION_CONTROL_BLOCK *pECB) throw()
|
|
{
|
|
UINT uResId = 0;
|
|
LPCSTR szHeader = NULL;
|
|
|
|
m_UserErrorProvider.GetErrorText(500,
|
|
ISE_SUBERR_ISAPISTARTUPFAILED,
|
|
&szHeader,
|
|
&uResId);
|
|
_ATLTRY
|
|
{
|
|
CStringA strStatus, strBody;
|
|
strStatus.Format("500 %s", szHeader);
|
|
if (uResId)
|
|
{
|
|
// load the body string from a resource
|
|
if (!strBody.LoadString(uResId))
|
|
{
|
|
strBody = "<html><body>A critical error has occurred initializing this ISAPI extension.</body></html>";
|
|
}
|
|
}
|
|
|
|
|
|
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()
|
|
{
|
|
//REALLY BAD!
|
|
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;
|
|
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;
|
|
|
|
// QueueRequest failed
|
|
FreeRequest(pRequestInfo);
|
|
return HSE_STATUS_ERROR;
|
|
}
|
|
|
|
|
|
BOOL QueueRequest(AtlServerRequest * pRequestInfo) throw()
|
|
{
|
|
return m_ThreadPool.QueueRequest(pRequestInfo);
|
|
}
|
|
|
|
CIsapiWorker *GetThreadWorker() throw()
|
|
{
|
|
return (CIsapiWorker *) TlsGetValue(m_dwTlsIndex);
|
|
}
|
|
|
|
BOOL SetThreadWorker(CIsapiWorker *pWorker) throw()
|
|
{
|
|
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() throw()
|
|
{
|
|
return SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
|
|
}
|
|
|
|
void OnThreadTerminate() throw()
|
|
{
|
|
CoUninitialize();
|
|
}
|
|
|
|
|
|
#ifndef ATL_NO_CRITICAL_ISAPI_ERROR
|
|
BOOL SetCriticalIsapiError(DWORD dwErr = 1) throw()
|
|
{
|
|
m_dwCriticalIsapiError = dwErr;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
DWORD GetCriticalIsapiError() throw()
|
|
{
|
|
return m_dwCriticalIsapiError;
|
|
}
|
|
|
|
#else
|
|
BOOL SetCriticalIsapiError(DWORD dwErr = 1) throw()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
DWORD GetCriticalIsapiError() throw()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#endif // ATL_NO_CRITICAL_ISAPI_ERROR
|
|
|
|
|
|
BOOL GetExtensionVersion(HSE_VERSION_INFO* pVer) throw()
|
|
{
|
|
// 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();
|
|
}
|
|
|
|
}
|
|
|
|
// 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") );
|
|
|
|
return SetCriticalIsapiError();
|
|
}
|
|
|
|
if (S_OK != m_WorkerThread.Initialize())
|
|
{
|
|
return SetCriticalIsapiError();
|
|
}
|
|
|
|
if (m_critSec.Init() != S_OK)
|
|
{
|
|
m_WorkerThread.Shutdown();
|
|
return SetCriticalIsapiError();
|
|
}
|
|
|
|
if (S_OK != m_ThreadPool.Initialize(static_cast<IIsapiExtension*>(this), GetNumPoolThreads(), GetPoolStackSize(), GetIOCompletionHandle()))
|
|
{
|
|
m_WorkerThread.Shutdown();
|
|
m_critSec.Term();
|
|
return SetCriticalIsapiError();
|
|
}
|
|
|
|
|
|
if (FAILED(m_DllCache.Initialize(&m_WorkerThread, GetDllCacheTimeout())))
|
|
{
|
|
m_WorkerThread.Shutdown();
|
|
m_ThreadPool.Shutdown();
|
|
m_critSec.Term();
|
|
return SetCriticalIsapiError();
|
|
}
|
|
|
|
if (FAILED(m_PageCache.Initialize(&m_WorkerThread)))
|
|
{
|
|
m_WorkerThread.Shutdown();
|
|
m_ThreadPool.Shutdown();
|
|
m_DllCache.Uninitialize();
|
|
m_critSec.Term();
|
|
return SetCriticalIsapiError();
|
|
}
|
|
|
|
if (S_OK != m_StencilCache.Initialize(static_cast<IServiceProvider*>(this),
|
|
&m_WorkerThread,
|
|
GetStencilCacheTimeout(),
|
|
GetStencilLifespan()))
|
|
{
|
|
m_WorkerThread.Shutdown();
|
|
m_ThreadPool.Shutdown();
|
|
m_DllCache.Uninitialize();
|
|
m_PageCache.Uninitialize();
|
|
m_critSec.Term();
|
|
return SetCriticalIsapiError();
|
|
}
|
|
|
|
pVer->dwExtensionVersion = HSE_VERSION;
|
|
strcpy(pVer->lpszExtensionDesc, GetExtensionDesc());
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL TerminateExtension(DWORD /*dwFlags*/) throw()
|
|
{
|
|
m_critSec.Lock();
|
|
for (int i=0; i < m_serviceMap.GetSize(); i++)
|
|
{
|
|
ATLASSERT(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();
|
|
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 TRUE;
|
|
}
|
|
|
|
static void WINAPI AsyncCallback(LPEXTENSION_CONTROL_BLOCK /*lpECB*/,
|
|
PVOID pContext,
|
|
DWORD cbIO,
|
|
DWORD dwError) throw(...)
|
|
{
|
|
ATLASSERT(pContext);
|
|
|
|
AtlServerRequest *pRequestInfo = reinterpret_cast<AtlServerRequest*>(pContext);
|
|
ATLASSERT(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(IHttpServerContext *pServerContext, DWORD dwStatus, DWORD dwSubStatus) throw()
|
|
{
|
|
RenderError(pServerContext, dwStatus, dwSubStatus, &m_UserErrorProvider);
|
|
}
|
|
|
|
void RequestComplete(AtlServerRequest *pRequestInfo, DWORD dwStatus, DWORD dwSubStatus) throw(...)
|
|
{
|
|
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(LPCSTR szFileName, LPSTR szHandlerName) throw()
|
|
{
|
|
return _AtlGetHandlerName(szFileName, szHandlerName);
|
|
}
|
|
|
|
HTTP_CODE LoadDispatchFile(LPCSTR szFileName, AtlServerRequest *pRequestInfo) throw()
|
|
{
|
|
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+1];
|
|
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, szHandlerName);
|
|
|
|
CFileTime cftCurr;
|
|
CFileTime cftLastChecked;
|
|
cftCurr = CFileTime::GetCurrentTime();
|
|
|
|
pStencil->GetLastChecked(&cftLastChecked);
|
|
|
|
CFileTimeSpan d(ATL_STENCIL_CHECK_TIMEOUT * CFileTime::Millisecond);
|
|
|
|
if (cftLastChecked + d < cftCurr)
|
|
{
|
|
CComPtr<IStencilCacheControl> spCacheCtrl;
|
|
m_StencilCache.QueryInterface(__uuidof(IStencilCacheControl), (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+2] = { '\0' };
|
|
|
|
// not in the cache, so open the file
|
|
HTTP_CODE hcErr = GetHandlerName(szFileName, szHandlerDllName);
|
|
if (hcErr)
|
|
return hcErr;
|
|
DWORD dwDllPathLen = MAX_PATH+1;
|
|
DWORD dwHandlerNameLen = ATL_MAX_HANDLER_NAME_LEN+1;
|
|
if (!_AtlCrackHandler(szHandlerDllName, szDllPath, &dwDllPathLen, szHandlerName, &dwHandlerNameLen))
|
|
{
|
|
HTTP_ERROR(500, ISE_SUBERR_HANDLER_NOT_FOUND);
|
|
}
|
|
ATLASSERT(*szHandlerName);
|
|
ATLASSERT(*szDllPath);
|
|
if (!*szHandlerName)
|
|
return HTTP_ERROR(500, ISE_SUBERR_HANDLER_NOT_FOUND);
|
|
}
|
|
else
|
|
m_StencilCache.ReleaseStencil(hStencil);
|
|
|
|
|
|
return LoadRequestHandler(szDllPath, szHandlerName, pRequestInfo->pServerContext,
|
|
&pRequestInfo->hInstDll, &pRequestInfo->pHandler);
|
|
}
|
|
|
|
HTTP_CODE LoadDllHandler(LPCSTR szFileName, AtlServerRequest *pRequestInfo) throw()
|
|
{
|
|
_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 = HTTP_ERROR(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];
|
|
strcpy(szFile, szFileName);
|
|
|
|
hcErr = LoadRequestHandler(szFile, szHandler, pRequestInfo->pServerContext, &pRequestInfo->hInstDll, &pRequestInfo->pHandler);
|
|
}
|
|
|
|
return hcErr;
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return HTTP_ERROR(500, ISE_SUBERR_UNEXPECTED);
|
|
}
|
|
}
|
|
|
|
BOOL TransmitFromCache(AtlServerRequest* pRequestInfo) throw()
|
|
{
|
|
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();
|
|
|
|
LPSTR szTo = szUrl;
|
|
while (*szPathInfo)
|
|
{
|
|
*szTo++ = *szPathInfo++;
|
|
}
|
|
*szTo++ = '?';
|
|
while (*szQueryString)
|
|
{
|
|
*szTo++ = *szQueryString++;
|
|
}
|
|
*szTo = '\0';
|
|
|
|
HCACHEITEM hEntry;
|
|
|
|
if (S_OK == m_PageCache.LookupFile(szUrl, &hEntry))
|
|
{
|
|
LPSTR szFileName;
|
|
CPageCachePeer::PeerInfo *pInfo;
|
|
m_PageCache.GetFile(hEntry, &szFileName, (void **)&pInfo);
|
|
CAtlFile file;
|
|
HRESULT hr = E_FAIL;
|
|
|
|
_ATLTRY
|
|
{
|
|
CA2CTEX<MAX_PATH+1> strFile(szFileName);
|
|
hr = file.Create(strFile,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED);
|
|
}
|
|
_ATLCATCHALL()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (FAILED(hr) || GetFileType(file) != FILE_TYPE_DISK)
|
|
return FALSE;
|
|
|
|
pRequestInfo->pServerContext->SendResponseHeader(
|
|
(LPCSTR)pInfo->strHeader, (LPCSTR)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 // Send asynchronously
|
|
);
|
|
if (!bRet)
|
|
{
|
|
m_PageCache.ReleaseFile(hEntry);
|
|
CloseHandle(hFile);
|
|
}
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
BOOL m_bDebug;
|
|
// F5 debugging support for VS7
|
|
BOOL ProcessDebug(AtlServerRequest *pRequestInfo) throw()
|
|
{
|
|
_ATLTRY
|
|
{
|
|
if (!_stricmp(pRequestInfo->pServerContext->GetRequestMethod(), "debug"))
|
|
{
|
|
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 = (LPCSTR)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];
|
|
wcsncpy(szClsid, szPoint, 38);
|
|
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;
|
|
}
|
|
|
|
CComPtr<IDebugAutoAttach> spDebugAutoAttach;
|
|
hr = CoCreateInstance(clsidDebugAutoAttach, NULL, CLSCTX_LOCAL_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))
|
|
{
|
|
// HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_ATTACH);
|
|
char szRetBuf[256];
|
|
DWORD dwLen = wsprintfA(szRetBuf, "204 HRESULT=0x%.08X;ErrorString=Unable to attach to worker process", hr);
|
|
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
|
|
|
|
BOOL DispatchStencilCall(AtlServerRequest *pRequestInfo) throw(...)
|
|
{
|
|
CSetThreadToken sec;
|
|
|
|
m_reqStats.OnRequestDequeued();
|
|
|
|
if (!sec.Initialize(pRequestInfo))
|
|
{
|
|
RequestComplete(pRequestInfo, 500, ISE_SUBERR_IMPERSONATIONFAILED);
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
if (!ProcessDebug(pRequestInfo))
|
|
return TRUE;
|
|
#endif
|
|
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)
|
|
{
|
|
if (TransmitFromCache(pRequestInfo)) // 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 (_stricmp(szDot - ATLS_EXTENSION_LEN, c_AtlSRFExtension) == 0)
|
|
{
|
|
pRequestInfo->dwRequestType = ATLSRV_REQUEST_STENCIL;
|
|
hcErr = LoadDispatchFile(szFileName, pRequestInfo);
|
|
}
|
|
else if (_stricmp(szDot - 3, ".dll") == 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 (dwStatus & ATLSRV_INIT_USECACHE &&
|
|
!strcmp(pRequestInfo->pServerContext->GetRequestMethod(), "GET"))
|
|
{
|
|
CComObjectNoLock<CCacheServerContext> *pCacheServerContext = NULL;
|
|
ATLTRY(pCacheServerContext = new CComObjectNoLock<CCacheServerContext>);
|
|
if (pCacheServerContext == NULL)
|
|
{
|
|
RequestComplete(pRequestInfo, 500, ISE_SUBERR_OUTOFMEM);
|
|
return FALSE;
|
|
}
|
|
|
|
pCacheServerContext->Initialize(pRequestInfo->pServerContext, &m_PageCache);
|
|
pCacheServerContext->AddRef();
|
|
pRequestInfo->pServerContext = pCacheServerContext;
|
|
}
|
|
|
|
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 dwStatus = WaitForSingleObject(pRequestInfo->m_hMutex, 10000);
|
|
if (dwStatus != WAIT_OBJECT_0 && dwStatus != 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
|
|
|
|
ATLASSERT(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), (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(AtlServerRequest *pRequestInfo) throw()
|
|
{
|
|
if (pRequestInfo->pszBuffer == NULL || pRequestInfo->dwBufferLen == 0)
|
|
{
|
|
ATLASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
return pRequestInfo->pServerContext->AsyncWriteClient(
|
|
LPVOID(pRequestInfo->pszBuffer),
|
|
&pRequestInfo->dwBufferLen);
|
|
}
|
|
|
|
long GetTotalRequests() throw()
|
|
{
|
|
return m_reqStats.GetTotalRequests();
|
|
}
|
|
|
|
long GetFailedRequests() throw()
|
|
{
|
|
return m_reqStats.GetFailedRequests();
|
|
}
|
|
|
|
long GetAvgResponseTime() throw()
|
|
{
|
|
return m_reqStats.GetAvgResponseTime();
|
|
}
|
|
|
|
long GetCurrWaiting() throw()
|
|
{
|
|
return m_reqStats.GetCurrWaiting();
|
|
}
|
|
|
|
long GetMaxWaiting() throw()
|
|
{
|
|
return m_reqStats.GetMaxWaiting();
|
|
}
|
|
|
|
long GetActiveThreads() throw()
|
|
{
|
|
return m_reqStats.GetActiveThreads();
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) throw()
|
|
{
|
|
if (!ppv)
|
|
return E_POINTER;
|
|
if (InlineIsEqualGUID(riid, __uuidof(IRequestStats)))
|
|
{
|
|
*ppv = static_cast<IUnknown*>(static_cast<IRequestStats*>(this));
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
if (InlineIsEqualGUID(riid, __uuidof(IUnknown)) ||
|
|
InlineIsEqualGUID(riid, __uuidof(IServiceProvider)))
|
|
{
|
|
*ppv = static_cast<IUnknown*>(static_cast<IServiceProvider*>(this));
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
if (InlineIsEqualGUID(riid, __uuidof(IIsapiExtension)))
|
|
{
|
|
*ppv = static_cast<IUnknown*>(static_cast<IIsapiExtension*>(this));
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE AddRef() throw()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE Release() throw()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
virtual HRESULT STDMETHODCALLTYPE QueryService(
|
|
REFGUID guidService,
|
|
REFIID riid,
|
|
void **ppvObject) throw()
|
|
{
|
|
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();
|
|
ATLASSERT( 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) throw()
|
|
{
|
|
if (!m_DllCache.AddRefModule(hInstance))
|
|
return E_FAIL;
|
|
|
|
if (!punkService)
|
|
return E_INVALIDARG;
|
|
|
|
CCritSecLock Lock(m_critSec.m_sec);
|
|
|
|
ServiceNode srvNode;
|
|
srvNode.hInst = hInstance;
|
|
srvNode.punk = punkService;
|
|
memcpy(&srvNode.guidService, &guidService, sizeof(guidService));
|
|
memcpy(&srvNode.riid, &riid, sizeof(riid));
|
|
|
|
// 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(REFGUID guidService, REFIID riid) throw()
|
|
{
|
|
CCritSecLock Lock(m_critSec.m_sec);
|
|
|
|
ServiceNode srvNode;
|
|
memcpy(&srvNode.guidService, &guidService, sizeof(guidService));
|
|
memcpy(&srvNode.riid, &riid, sizeof(riid));
|
|
int nIndex = m_serviceMap.Find(srvNode);
|
|
if (nIndex < 0)
|
|
return S_FALSE;
|
|
|
|
ATLASSERT(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;
|
|
}
|
|
|
|
HRESULT GetService(REFGUID guidService, REFIID riid, void **ppvObject) throw()
|
|
{
|
|
if (!ppvObject)
|
|
return E_POINTER;
|
|
|
|
*ppvObject = NULL;
|
|
if (!m_serviceMap.GetSize())
|
|
return E_NOINTERFACE;
|
|
|
|
ServiceNode srvNode;
|
|
memcpy(&srvNode.guidService, &guidService, sizeof(guidService));
|
|
memcpy(&srvNode.riid, &riid, sizeof(riid));
|
|
|
|
CCritSecLock Lock(m_critSec.m_sec);
|
|
|
|
int nIndex = m_serviceMap.Find(srvNode);
|
|
if (nIndex < 0)
|
|
return E_NOINTERFACE;
|
|
|
|
ATLASSERT(m_serviceMap[nIndex].punk != NULL);
|
|
return m_serviceMap[nIndex].punk->QueryInterface(riid, ppvObject);
|
|
}
|
|
|
|
HTTP_CODE LoadRequestHandler(LPCSTR szDllPath, LPCSTR szHandlerName, IHttpServerContext *pServerContext,
|
|
HINSTANCE *phInstance, IRequestHandler **ppHandler) throw()
|
|
{
|
|
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 data_seg(push)
|
|
#pragma data_seg("ATLS$A")
|
|
__declspec(selectany) ATL::_HANDLER_ENTRY * __phdlrA = NULL;
|
|
#pragma data_seg("ATLS$Z")
|
|
__declspec(selectany) ATL::_HANDLER_ENTRY * __phdlrZ = NULL;
|
|
#pragma data_seg("ATLS$C")
|
|
#pragma data_seg(pop)
|
|
|
|
#ifndef HANDLER_ENTRY_PRAGMA
|
|
|
|
#if defined(_M_IX86)
|
|
#define HANDLER_ENTRY_PRAGMA(class, line) __pragma(comment(linker, "/include:___phdlrEntry_" #class "_" #line))
|
|
#elif defined(_M_AMD64) || defined(_M_IA64)
|
|
#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, lineNum) \
|
|
__declspec(selectany) ATL::_HANDLER_ENTRY __hdlrEntry_ ## className ## _ ## lineNum = { handlerName, className::CreateRequestHandler, className::InitRequestHandlerClass, className::UninitRequestHandlerClass }; \
|
|
extern "C" __declspec(allocate("ATLS$C")) __declspec(selectany) \
|
|
ATL::_HANDLER_ENTRY * __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--; \
|
|
(*(*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++; \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
// TODO (jasjitg): When __COUNTER__ becomes available, replace __LINE__ with that
|
|
#define __DECLARE_REQUEST_HANDLER(handlerName, className, lineNum) __DECLARE_REQUEST_HANDLER_INTERNAL(handlerName, className, lineNum)
|
|
#define DECLARE_REQUEST_HANDLER(handlerName, className) __DECLARE_REQUEST_HANDLER(handlerName, className, __COUNTER__)
|
|
|
|
#define BEGIN_HANDLER_MAP()
|
|
#define HANDLER_ENTRY(handlerName, className) DECLARE_REQUEST_HANDLER(handlerName, className)
|
|
#define END_HANDLER_MAP()
|
|
|
|
#define HANDLER_ENTRY_SDL(handlerString, handlerClass, sdlClassName)\
|
|
__declspec(selectany) LPCSTR s_szClassName##handlerClass=handlerString;\
|
|
typedef CSDLGenerator<handlerClass, s_szClassName##handlerClass> sdlClassName; \
|
|
HANDLER_ENTRY(handlerString, handlerClass)\
|
|
HANDLER_ENTRY(#sdlClassName, sdlClassName)
|
|
|
|
//
|
|
// 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(AtlServerRequest *pInfo, const SID* psidAuthGroup) throw()
|
|
{
|
|
HTTP_CODE hcErr = HTTP_UNAUTHORIZED;
|
|
char szAuthType[MAX_AUTH_TYPE];
|
|
DWORD dwSize = MAX_AUTH_TYPE;
|
|
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;
|
|
}
|
|
}
|
|
return hcErr;
|
|
}
|
|
|
|
bool CheckAccount(IHttpServerContext *pContext, const SID *psidAuthGroup) throw()
|
|
{
|
|
pContext; // unused
|
|
psidAuthGroup; // unused
|
|
return true;
|
|
}
|
|
|
|
HTTP_CODE HandleError(AtlServerRequest *pRequestInfo) throw()
|
|
{
|
|
pRequestInfo; // unused
|
|
return HTTP_FAIL;
|
|
}
|
|
|
|
bool CheckAuthAccount(IHttpServerContext *pContext, const SID* psidAuthGroup) throw()
|
|
{
|
|
ATLASSERT(pContext);
|
|
ATLASSERT(psidAuthGroup);
|
|
if (!pContext || !psidAuthGroup)
|
|
return false;
|
|
|
|
HANDLE hToken = INVALID_HANDLE_VALUE;
|
|
if (!pContext->GetImpersonationToken(&hToken) ||
|
|
hToken == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
|
|
CAccessToken tok;
|
|
tok.Attach(hToken, true);
|
|
|
|
bool bIsMember;
|
|
bool bRet = tok.CheckTokenMembership(CSid(psidAuthGroup), &bIsMember);
|
|
|
|
if (!bRet)
|
|
return false;
|
|
|
|
return bIsMember;
|
|
}
|
|
};
|
|
|
|
// Checks that the user that is logging on is in the required group
|
|
class CDefaultAuth :
|
|
public CVerifyAuth<CDefaultAuth>
|
|
{
|
|
public:
|
|
bool CheckAccount(IHttpServerContext *pContext, const SID* psidAuthGroup) throw()
|
|
{
|
|
return CheckAuthAccount(pContext, psidAuthGroup);
|
|
}
|
|
|
|
HTTP_CODE HandleError(AtlServerRequest *pRequestInfo) throw()
|
|
{
|
|
ATLASSERT(pRequestInfo); // should always be valid
|
|
|
|
CHttpResponse response(pRequestInfo->pServerContext);
|
|
response.Write(GetErrorResponse());
|
|
response.Flush();
|
|
return HTTP_SUCCESS_NO_PROCESS;
|
|
}
|
|
|
|
virtual LPCSTR GetErrorResponse() throw()
|
|
{
|
|
static const char *szResponse = "<html><body>"
|
|
"<H1 align=center>NOT AUTHORIZED</H1><p>"
|
|
"</body></html>";
|
|
return szResponse;
|
|
}
|
|
|
|
};
|
|
|
|
} // namespace ATL
|
|
|
|
#pragma warning(pop)
|
|
|
|
#endif // __ATLISAPI_H__
|