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.
5312 lines
141 KiB
5312 lines
141 KiB
/*===================================================================
|
|
Microsoft Denali
|
|
|
|
Microsoft Confidential.
|
|
Copyright 1996 Microsoft Corporation. All Rights Reserved.
|
|
|
|
Component: Response object
|
|
|
|
File: response.cpp
|
|
|
|
Owner: CGrant
|
|
|
|
This file contains the code for the implementation of the Response object.
|
|
===================================================================*/
|
|
#include "denpre.h"
|
|
#pragma hdrstop
|
|
|
|
#include "response.h"
|
|
#include "request.h"
|
|
#include "Cookies.h"
|
|
#include "perfdata.h"
|
|
|
|
#include "winsock2.h"
|
|
|
|
#include "memchk.h"
|
|
|
|
#pragma warning (disable: 4355) // ignore: "'this' used in base member init
|
|
|
|
static const char s_szContentLengthHeader[] = "Content-Length: ";
|
|
static const char s_szContentTypeHeader[] = "Content-Type: ";
|
|
static const char s_szCharSetHTML[] = "; Charset=";
|
|
static const char s_szCacheControl[] = "Cache-control: ";
|
|
static const char s_szCacheControlPrivate[] = "Cache-control: private\r\n";
|
|
static const char s_szTransferEncoding[] = "Transfer-Encoding: chunked\r\n";
|
|
static const char s_szHTML[] = "text/html";
|
|
static const char s_szCDF[] = "application/x-cdf";
|
|
static const char s_szDefaultStatus[] = "200 OK";
|
|
|
|
#if VECTSTATS
|
|
|
|
//
|
|
// a mechanism to collect statistics about response vector length and fragment sizes
|
|
//
|
|
|
|
#if DEBUG
|
|
#define COLLECT_RESP_VECT_STATS TRUE
|
|
#else
|
|
#define COLLECT_RESP_VECT_STATS FALSE
|
|
#endif
|
|
|
|
static BOOL fCollectRespVectStats = COLLECT_RESP_VECT_STATS;
|
|
|
|
static VOID RespVectStat_DataSizeStats(BOOL fHtmlData, DWORD cch)
|
|
{
|
|
if (!fCollectRespVectStats)
|
|
return;
|
|
|
|
if (cch == 0)
|
|
InterlockedIncrement(&sRespVecStats.ZeroSizeBlocks);
|
|
|
|
if (fHtmlData)
|
|
{
|
|
if (cch <= 0x10)
|
|
InterlockedIncrement(&sRespVecStats.HTML16);
|
|
else if (cch <= 0x20)
|
|
InterlockedIncrement(&sRespVecStats.HTML32);
|
|
else if (cch <= 0x30)
|
|
InterlockedIncrement(&sRespVecStats.HTML48);
|
|
else if (cch <= 0x40)
|
|
InterlockedIncrement(&sRespVecStats.HTML64);
|
|
else if (cch <= 0x80)
|
|
InterlockedIncrement(&sRespVecStats.HTML128);
|
|
else if (cch <= 0x100)
|
|
InterlockedIncrement(&sRespVecStats.HTML256);
|
|
else if (cch <= 0x200)
|
|
InterlockedIncrement(&sRespVecStats.HTML512);
|
|
else if (cch <= 0x400)
|
|
InterlockedIncrement(&sRespVecStats.HTML1024);
|
|
else if (cch <= 0x800)
|
|
InterlockedIncrement(&sRespVecStats.HTML2048);
|
|
else if (cch <= 0x1000)
|
|
InterlockedIncrement(&sRespVecStats.HTML4096);
|
|
else if (cch <= 0x2000)
|
|
InterlockedIncrement(&sRespVecStats.HTML8192);
|
|
else if (cch <= 0x4000)
|
|
InterlockedIncrement(&sRespVecStats.HTML16384);
|
|
else
|
|
InterlockedIncrement(&sRespVecStats.HTMLbig);
|
|
|
|
if (cch > MAX_HTML_IN_RESPONSE_BUFFER)
|
|
InterlockedExchangeAdd(&sRespVecStats.TotalReferencedHTMLBytes, cch);
|
|
else
|
|
InterlockedExchangeAdd(&sRespVecStats.TotalCopiedHTMLBytes, cch);
|
|
}
|
|
else
|
|
InterlockedIncrement(&sRespVecStats.DynamicBlocks);
|
|
}
|
|
|
|
static VOID RespVectStat_VectorSizeStats(DWORD ca)
|
|
{
|
|
if (!fCollectRespVectStats)
|
|
return;
|
|
|
|
if (ca <= 0x8)
|
|
InterlockedIncrement(&sRespVecStats.Vect8);
|
|
else if (ca <= 0x10)
|
|
InterlockedIncrement(&sRespVecStats.Vect16);
|
|
else if (ca <= 0x20)
|
|
InterlockedIncrement(&sRespVecStats.Vect32);
|
|
else if (ca <= 0x40)
|
|
InterlockedIncrement(&sRespVecStats.Vect64);
|
|
else if (ca <= 0x60)
|
|
InterlockedIncrement(&sRespVecStats.Vect96);
|
|
else if (ca <= 0x80)
|
|
InterlockedIncrement(&sRespVecStats.Vect128);
|
|
else if (ca <= 0xC0)
|
|
InterlockedIncrement(&sRespVecStats.Vect192);
|
|
else if (ca <= 0x100)
|
|
InterlockedIncrement(&sRespVecStats.Vect256);
|
|
else if (ca <= 0x200)
|
|
InterlockedIncrement(&sRespVecStats.Vect512);
|
|
else if (ca <= 0x400)
|
|
InterlockedIncrement(&sRespVecStats.Vect1024);
|
|
else if (ca <= 0x800)
|
|
InterlockedIncrement(&sRespVecStats.Vect2048);
|
|
else if (ca <= 0x1000)
|
|
InterlockedIncrement(&sRespVecStats.Vect4096);
|
|
else
|
|
InterlockedIncrement(&sRespVecStats.VectBig);
|
|
}
|
|
|
|
#else // VECTSTATS
|
|
|
|
#define RespVectStat_DataSizeStats(fHtmlData, ccb)
|
|
#define RespVectStat_VectorSizeStats(ca)
|
|
|
|
#endif // VECTSTATS
|
|
|
|
ResponseVectorStatistics sRespVecStats = {0};
|
|
|
|
inline void AddtoTotalByteOut(int cByteOut)
|
|
{
|
|
#ifndef PERF_DISABLE
|
|
g_PerfData.Add_REQTOTALBYTEOUT(cByteOut);
|
|
#endif
|
|
}
|
|
|
|
inline const char *GetResponseMimeType(CIsapiReqInfo *pIReq)
|
|
{
|
|
TCHAR *szPath = pIReq->QueryPszPathTranslated();
|
|
DWORD cch = pIReq->QueryCchPathTranslated();
|
|
if (cch > 4 && _tcscmp(szPath + cch - 4, _T(".CDX")) == 0)
|
|
{
|
|
return s_szCDF;
|
|
}
|
|
else
|
|
{
|
|
return s_szHTML;
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
* C R e s p o n s e C o o k i e s
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
//===================================================================
|
|
// CResponseCookies::CResponseCookies
|
|
//
|
|
// Constructor.
|
|
//===================================================================
|
|
CResponseCookies::CResponseCookies(CResponse *pResponse, IUnknown *pUnkOuter)
|
|
: m_ISupportErrImp(this, pUnkOuter, IID_IRequestDictionary)
|
|
{
|
|
m_punkOuter = pUnkOuter;
|
|
|
|
if (pResponse)
|
|
pResponse->AddRef();
|
|
m_pResponse = pResponse;
|
|
|
|
m_pRequest = NULL;
|
|
|
|
CDispatch::Init(IID_IRequestDictionary);
|
|
}
|
|
|
|
//===================================================================
|
|
// CResponseCookies::~CResponseCookies
|
|
//
|
|
// Destructor.
|
|
//===================================================================
|
|
CResponseCookies::~CResponseCookies()
|
|
{
|
|
if (m_pRequest)
|
|
m_pRequest->Release();
|
|
|
|
if (m_pResponse)
|
|
m_pResponse->Release();
|
|
}
|
|
|
|
//===================================================================
|
|
// CResponseCookies::ReInit
|
|
//
|
|
// Parameters:
|
|
// pRequest - pointer to the request object. Will need it to
|
|
// read the request for the cookies
|
|
//
|
|
// Returns:
|
|
// always S_OK, unlest pRequest is NULL.
|
|
//===================================================================
|
|
HRESULT CResponseCookies::ReInit(CRequest *pRequest)
|
|
{
|
|
if (pRequest)
|
|
pRequest->AddRef();
|
|
if (m_pRequest)
|
|
m_pRequest->Release();
|
|
|
|
m_pRequest = pRequest; // CRequest is not ref counted, so no need for AddRef/Release
|
|
|
|
if (m_pRequest == NULL)
|
|
return E_POINTER;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseCookies::QueryInterface
|
|
CResponseCookies::AddRef
|
|
CResponseCookies::Release
|
|
|
|
IUnknown members for CResponseCookies object.
|
|
===================================================================*/
|
|
|
|
STDMETHODIMP CResponseCookies::QueryInterface(const IID &idInterface, void **ppvObj)
|
|
{
|
|
*ppvObj = NULL;
|
|
|
|
if (idInterface == IID_IUnknown || idInterface == IID_IRequestDictionary || idInterface == IID_IDispatch)
|
|
*ppvObj = this;
|
|
|
|
else if (idInterface == IID_ISupportErrorInfo)
|
|
*ppvObj = &m_ISupportErrImp;
|
|
|
|
if (*ppvObj != NULL)
|
|
{
|
|
static_cast<IUnknown *>(*ppvObj)->AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
return ResultFromScode(E_NOINTERFACE);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CResponseCookies::AddRef()
|
|
{
|
|
return m_punkOuter->AddRef();
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CResponseCookies::Release()
|
|
{
|
|
return m_punkOuter->Release();
|
|
}
|
|
|
|
|
|
|
|
/*===================================================================
|
|
CResponseCookies::get_Item
|
|
|
|
Function called from DispInvoke to get values from the Response.Cookies
|
|
collection. If the Cookie does not exist, then a new one is created
|
|
and added to the Request dictionary
|
|
|
|
Parameters:
|
|
varKey VARIANT [in], which parameter to get the value of - Empty means whole collection
|
|
pvarReturn VARIANT *, [out] value of the requested parameter
|
|
|
|
Returns:
|
|
S_OK on success, E_FAIL on failure.
|
|
===================================================================*/
|
|
HRESULT CResponseCookies::get_Item(VARIANT varKey, VARIANT *pvarReturn)
|
|
{
|
|
if (FAILED(m_pResponse->CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
char *szKey=NULL; // ascii value of 'varKey'
|
|
CRequestHit *pRequestHit; // pointer to request bucket
|
|
DWORD vt = 0; // Variant type of key
|
|
CWCharToMBCS convKey;
|
|
|
|
if (m_pResponse->FHeadersWritten())
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_HEADERS_WRITTEN);
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Initialize things
|
|
//
|
|
V_VT(pvarReturn) = VT_DISPATCH;
|
|
V_DISPATCH(pvarReturn) = NULL;
|
|
VARIANT *pvarKey = &varKey;
|
|
HRESULT hrReturn = S_OK;
|
|
|
|
// BUG 937: VBScript passes VT_VARIANT|VT_BYREF when passing obect
|
|
// produced by IEnumVariant
|
|
//
|
|
// Use VariantResolveDispatch which will:
|
|
//
|
|
// * Copy BYREF variants for us using VariantCopyInd
|
|
// * handle E_OUTOFMEMORY for us
|
|
// * get the default value from an IDispatch, which seems
|
|
// like an appropriate conversion.
|
|
//
|
|
VARIANT varKeyCopy;
|
|
VariantInit(&varKeyCopy);
|
|
vt = V_VT(pvarKey);
|
|
|
|
if ((vt != VT_BSTR) && (vt != VT_I2) && (vt != VT_I4))
|
|
{
|
|
if (FAILED(VariantResolveDispatch(&varKeyCopy, &varKey, IID_IRequestDictionary, IDE_REQUEST)))
|
|
goto LExit;
|
|
|
|
pvarKey = &varKeyCopy;
|
|
}
|
|
|
|
vt = V_VT(pvarKey);
|
|
|
|
switch(vt)
|
|
{
|
|
// Bug 95201 support all numberic sub-types
|
|
case VT_I1: case VT_I2: case VT_I8:
|
|
case VT_UI1: case VT_UI2: case VT_UI4: case VT_UI8:
|
|
case VT_R4: case VT_R8:
|
|
// Coerce all integral types to VT_I4
|
|
if (FAILED(hrReturn = VariantChangeType(pvarKey, pvarKey, 0, VT_I4)))
|
|
goto LExit;
|
|
|
|
// fallthru to VT_I4
|
|
|
|
case VT_I4:
|
|
case VT_BSTR:
|
|
break;
|
|
default:
|
|
ExceptionId(IID_IRequestDictionary, IDE_COOKIE, IDE_EXPECTING_STR);
|
|
hrReturn = E_FAIL;
|
|
goto LExit;
|
|
}
|
|
|
|
if (FAILED(m_pRequest->CheckForTombstone()))
|
|
{
|
|
hrReturn = E_FAIL;
|
|
goto LExit;
|
|
}
|
|
|
|
if (m_pRequest->m_pData->m_fLoadCookies)
|
|
{
|
|
char *szCookie = m_pRequest->GetIReq()->QueryPszCookie();
|
|
|
|
if (FAILED(hrReturn = m_pRequest->LoadVariables(COOKIE, szCookie, m_pRequest->GetCodePage())))
|
|
goto LExit;
|
|
|
|
m_pRequest->m_pData->m_fLoadCookies = FALSE;
|
|
}
|
|
|
|
if (vt == VT_BSTR)
|
|
{
|
|
if (FAILED(hrReturn = convKey.Init(V_BSTR(pvarKey),m_pRequest->GetCodePage()))) {
|
|
if (hrReturn == E_OUTOFMEMORY) {
|
|
ExceptionId(IID_IResponse, IDE_COOKIE, IDE_OOM);
|
|
goto LExit;
|
|
}
|
|
hrReturn = NO_ERROR;
|
|
szKey = "";
|
|
}
|
|
else {
|
|
szKey = convKey.GetString();
|
|
}
|
|
|
|
// Bug 456: Don't allow assignment to DenaliSessionID
|
|
if (strncmp(szKey, SZ_SESSION_ID_COOKIE_PREFIX, CCH_SESSION_ID_COOKIE_PREFIX) == 0)
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_COOKIE, IDE_RESPONSE_MODIFY_SESS_COOKIE);
|
|
hrReturn = E_FAIL;
|
|
goto LExit;
|
|
}
|
|
|
|
pRequestHit = static_cast<CRequestHit *>(m_pRequest->GetStrings()->FindElem(szKey, strlen(szKey)));
|
|
}
|
|
else
|
|
{
|
|
// Look up item by index
|
|
int iCount = 0;
|
|
if (vt == VT_I2)
|
|
{
|
|
iCount = V_I2(pvarKey);
|
|
}
|
|
else
|
|
{
|
|
iCount = V_I4(pvarKey);
|
|
}
|
|
|
|
// The Request hits for all cookies are stored with the request object
|
|
if ((iCount < 1) || (iCount > (int) m_pRequest->m_pData->m_Cookies.m_dwCount))
|
|
{
|
|
hrReturn = E_FAIL;
|
|
goto LExit;
|
|
}
|
|
|
|
pRequestHit = m_pRequest->m_pData->m_Cookies.m_rgRequestHit[iCount - 1];
|
|
}
|
|
|
|
if (pRequestHit)
|
|
{
|
|
CCookie *pDictionary = pRequestHit->m_pCookieData;
|
|
if (pDictionary == NULL)
|
|
goto LNotFound;
|
|
|
|
if (FAILED(pDictionary->QueryInterface(IID_IWriteCookie, reinterpret_cast<void **>(&V_DISPATCH(pvarReturn)))))
|
|
Assert (FALSE);
|
|
|
|
goto LExit;
|
|
}
|
|
|
|
LNotFound:
|
|
// don't allow empty cookie names
|
|
//
|
|
if (szKey != NULL && *szKey == '\0')
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_COOKIE, IDE_COOKIE_NO_NAME);
|
|
hrReturn = E_FAIL;
|
|
goto LExit;
|
|
}
|
|
|
|
// Create a new RequestHit if there is no key by this name
|
|
if (pRequestHit == NULL)
|
|
{
|
|
pRequestHit = new CRequestHit;
|
|
if (pRequestHit == NULL || FAILED(pRequestHit->Init(szKey, TRUE)))
|
|
{
|
|
if (pRequestHit)
|
|
delete pRequestHit;
|
|
ExceptionId(IID_IResponse, IDE_COOKIE, IDE_OOM);
|
|
hrReturn = E_OUTOFMEMORY;
|
|
goto LExit;
|
|
}
|
|
|
|
m_pRequest->GetStrings()->AddElem(pRequestHit);
|
|
}
|
|
|
|
// Create a new cookie, with an initial unassigned value.
|
|
if (pRequestHit->m_pCookieData == NULL)
|
|
{
|
|
pRequestHit->m_pCookieData = new CCookie(m_pResponse->GetIReq(),m_pRequest->GetCodePage());
|
|
if (pRequestHit->m_pCookieData == NULL || FAILED(pRequestHit->m_pCookieData->Init()))
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_COOKIE, IDE_OOM);
|
|
hrReturn = E_OUTOFMEMORY;
|
|
goto LExit;
|
|
}
|
|
}
|
|
|
|
// Add this Request hit to the ResponseCookies array of hits
|
|
if (!m_pRequest->m_pData->m_Cookies.AddRequestHit(pRequestHit))
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
// Query for IWriteCookie
|
|
if (FAILED(pRequestHit->m_pCookieData->QueryInterface(IID_IWriteCookie, reinterpret_cast<void **>(&V_DISPATCH(pvarReturn)))))
|
|
{
|
|
Assert (FALSE);
|
|
}
|
|
|
|
LExit:
|
|
VariantClear(&varKeyCopy);
|
|
return hrReturn;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseCookies::get_Count
|
|
|
|
Parameters:
|
|
pcValues - count is stored in *pcValues
|
|
===================================================================*/
|
|
|
|
STDMETHODIMP CResponseCookies::get_Count(int *pcValues)
|
|
{
|
|
if (FAILED(m_pRequest->CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
return m_pRequest->m_pData->m_Cookies.get_Count(pcValues);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseCookies::get_Key
|
|
|
|
Function called from DispInvoke to get keys from the response cookie collection.
|
|
|
|
Parameters:
|
|
vKey VARIANT [in], which parameter to get the key of
|
|
pvarReturn VARIANT *, [out] value of the requested parameter
|
|
|
|
Returns:
|
|
S_OK on success, E_FAIL on failure.
|
|
===================================================================*/
|
|
|
|
HRESULT CResponseCookies::get_Key(VARIANT varKey, VARIANT *pVar)
|
|
{
|
|
if (FAILED(m_pRequest->CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
return m_pRequest->m_pData->m_Cookies.get_Key(varKey, pVar);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseCookies::get__NewEnum
|
|
|
|
Return a new enumerator
|
|
===================================================================*/
|
|
HRESULT CResponseCookies::get__NewEnum(IUnknown **ppEnumReturn)
|
|
{
|
|
if (FAILED(m_pResponse->CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
*ppEnumReturn = NULL;
|
|
|
|
CRequestIterator *pIterator = new CRequestIterator(m_pRequest, COOKIE);
|
|
if (pIterator == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
HRESULT hrInit = pIterator->Init();
|
|
if (FAILED(hrInit))
|
|
{
|
|
delete pIterator;
|
|
return hrInit;
|
|
}
|
|
|
|
*ppEnumReturn = pIterator;
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseCookies::QueryHeaderSize
|
|
|
|
Returns:
|
|
returns the number of bytes required for the cookie headers.
|
|
===================================================================*/
|
|
|
|
size_t CResponseCookies::QueryHeaderSize()
|
|
{
|
|
if (FAILED(m_pRequest->CheckForTombstone()))
|
|
return 0;
|
|
|
|
int cbHeaders = 0;
|
|
|
|
for (CRequestHit *pRequestHit = static_cast<CRequestHit *>(m_pRequest->GetStrings()->Head());
|
|
pRequestHit != NULL;
|
|
pRequestHit = static_cast<CRequestHit *>(pRequestHit->m_pNext))
|
|
{
|
|
CCookie *pCookie = pRequestHit->m_pCookieData;
|
|
if (pCookie == NULL || !pCookie->IsDirty())
|
|
continue;
|
|
|
|
// add two bytes for '\r\n'
|
|
//
|
|
// CCookie::GetCookieHeaderSize adds one byte for NUL terminator, so
|
|
// just add one byte here.
|
|
//
|
|
// CResponse::WriteHeaders does not want to know about the NUL yet.
|
|
//
|
|
cbHeaders += pCookie->GetCookieHeaderSize(reinterpret_cast<char *>(pRequestHit->m_pKey)) + 1;
|
|
}
|
|
|
|
return cbHeaders;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseCookies::GetHeaders
|
|
|
|
Parameters:
|
|
szBuffer - contains the destination buffer for the cookie header
|
|
text
|
|
|
|
Returns:
|
|
return a pointer to the NUL character in the destination
|
|
===================================================================*/
|
|
|
|
char *CResponseCookies::GetHeaders(char *szBuffer)
|
|
{
|
|
if (FAILED(m_pRequest->CheckForTombstone()))
|
|
{
|
|
szBuffer[0] = '\0';
|
|
return szBuffer;
|
|
}
|
|
|
|
for (CRequestHit *pRequestHit = static_cast<CRequestHit *>(m_pRequest->GetStrings()->Head());
|
|
pRequestHit != NULL;
|
|
pRequestHit = static_cast<CRequestHit *>(pRequestHit->m_pNext))
|
|
{
|
|
CCookie *pCookie = pRequestHit->m_pCookieData;
|
|
if (pCookie == NULL || !pCookie->IsDirty())
|
|
continue;
|
|
|
|
szBuffer = pCookie->GetCookieHeader(reinterpret_cast<char *>(pRequestHit->m_pKey), szBuffer);
|
|
szBuffer = strcpyExA(szBuffer, "\r\n");
|
|
}
|
|
|
|
return szBuffer;
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
* C R e s p o n s e V e c t o r
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
/*===================================================================
|
|
The CResponseVector object maintains a vector of pointers to data blocks in the
|
|
response buffers and HTML data in the templates for an efficient VectorSend.
|
|
The current entry can be open for accumulating data (extending the size of current
|
|
block). Insert() will open a new vector entry for the data block and close it.
|
|
Append() adds to the current (open) entry or creates a new entry. Close() closes
|
|
the current entry.
|
|
====================================================================*/
|
|
|
|
/*===================================================================
|
|
CResponseVector::CResponseVector
|
|
|
|
Constructor
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
Nothing
|
|
|
|
Side Effects
|
|
None
|
|
|
|
===================================================================*/
|
|
CResponseVector::CResponseVector()
|
|
{
|
|
m_pExtVector = NULL;
|
|
m_cExtVectorSize = 0;
|
|
m_iCurrentEntry = 0;
|
|
m_fEntryIsOpen = FALSE;
|
|
m_cchTotalBuffered = 0;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseVector::~CResponseVector
|
|
|
|
Destructor
|
|
Clear() does all the work
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
Nothing
|
|
|
|
Side Effects
|
|
Frees memory
|
|
|
|
===================================================================*/
|
|
CResponseVector::~CResponseVector()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseVector::Clear
|
|
resets the instance, freeing any dynamically allocated memory
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
Nothing
|
|
|
|
Side Effects
|
|
Frees memory
|
|
|
|
===================================================================*/
|
|
VOID CResponseVector::Clear()
|
|
{
|
|
// update statistics
|
|
RespVectStat_VectorSizeStats( GetEntryCount());
|
|
|
|
// Free auxilary vector
|
|
if (m_pExtVector)
|
|
{
|
|
free(m_pExtVector);
|
|
m_pExtVector = NULL;
|
|
}
|
|
|
|
m_cExtVectorSize = 0;
|
|
m_iCurrentEntry = 0;
|
|
m_fEntryIsOpen = FALSE;
|
|
m_cchTotalBuffered = 0;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseVector::Append
|
|
For an open entry, increases the size of buffer the entry points to.
|
|
If the entry is closed, a new entry is opened.
|
|
|
|
Parameters:
|
|
pData pointer to the new data
|
|
cbSize size of data
|
|
|
|
Returns:
|
|
HRESULT
|
|
|
|
Side Effects
|
|
None
|
|
|
|
===================================================================*/
|
|
HRESULT CResponseVector::Append(char * pData, DWORD cbSize)
|
|
{
|
|
HRESULT hr;
|
|
LPWSABUF pEntry;
|
|
|
|
if (cbSize == 0)
|
|
{ // no point in storing zero size data
|
|
return S_OK;
|
|
}
|
|
|
|
Assert( !IsBadReadPtr( pData, cbSize));
|
|
|
|
if (IsEntryOpen())
|
|
{ // append to an open entry
|
|
pEntry = GetEntry( m_iCurrentEntry);
|
|
Assert(pData == (pEntry->buf + pEntry->len));
|
|
pEntry->len += cbSize;
|
|
m_cchTotalBuffered += cbSize;
|
|
return S_OK;
|
|
}
|
|
|
|
if (m_iCurrentEntry >= (RESPONSE_VECTOR_INTRINSIC_SIZE + m_cExtVectorSize))
|
|
{ // need to grow the vector
|
|
if (FAILED(hr = GrowVector()))
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
m_fEntryIsOpen = TRUE;
|
|
pEntry = GetEntry( m_iCurrentEntry);
|
|
pEntry->len = cbSize;
|
|
pEntry->buf = pData;
|
|
m_cchTotalBuffered += cbSize;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseVector::GrowVector
|
|
|
|
Allocate space in the auxilary vector. The auxilary vector is created with
|
|
RESPONSE_VECTOR_INITIAL_ALLOC elements and grown by a factor of
|
|
RESPONSE_VECTOR_REALLOC_FACTOR.
|
|
Factor the current size is an efficient method to grow hash and index tables.
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
HRESULT Indicating success or type of failure
|
|
|
|
Side Effects
|
|
May cause memory to be allocated
|
|
|
|
===================================================================*/
|
|
HRESULT CResponseVector::GrowVector()
|
|
{
|
|
LPVOID pVectorTmp;
|
|
DWORD cNewEntries;
|
|
|
|
if (m_pExtVector == NULL)
|
|
{
|
|
// first dynamic allocation
|
|
cNewEntries = RESPONSE_VECTOR_INITIAL_ALLOC;
|
|
pVectorTmp = malloc( cNewEntries * sizeof(WSABUF));
|
|
}
|
|
else
|
|
{
|
|
// expand current auxilary vector
|
|
cNewEntries = RESPONSE_VECTOR_REALLOC_FACTOR * m_cExtVectorSize;
|
|
pVectorTmp = realloc(m_pExtVector, cNewEntries * sizeof(WSABUF));
|
|
}
|
|
|
|
if (!pVectorTmp)
|
|
return E_OUTOFMEMORY;
|
|
|
|
m_pExtVector = (LPWSABUF)pVectorTmp;
|
|
m_cExtVectorSize = cNewEntries;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
* C R e s p o n s e B u f f e r
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
/*===================================================================
|
|
The CResponseBuffer object maintains an array of buffers.
|
|
If buffering is turned on, the Response.Write and Response.WriteBlock
|
|
methods will write to the buffers in these arrays rather then directly
|
|
back to the client. Response.Flush writes the content of the buffers to
|
|
the client and then frees the buffers. Response.Clear frees the buffers without
|
|
writing to the client
|
|
====================================================================*/
|
|
|
|
/*===================================================================
|
|
CResponseBuffer::CResponseBuffer
|
|
|
|
Constructor
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
Nothing
|
|
|
|
Side Effects
|
|
None
|
|
|
|
===================================================================*/
|
|
CResponseBuffer::CResponseBuffer()
|
|
{
|
|
m_pBufferSet = NULL;
|
|
|
|
m_rgpchBuffers = &m_pchBuffer0;
|
|
m_cBufferPointers = 1;
|
|
m_pchBuffer0 = NULL;
|
|
|
|
m_cBuffers = 0;
|
|
m_iCurrentBuffer = 0;
|
|
m_cchOffsetInCurrentBuffer = 0;
|
|
m_dwBufferLimit = DEFAULT_BUFFER_LIMIT;
|
|
m_fInited = FALSE;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseBuffer::Init
|
|
|
|
Initializes the CResponseBuffer object
|
|
|
|
Parameters:
|
|
CResponseBufferSet * pointer to bufferset holding this object
|
|
dwBufferLimit maximum to buffer
|
|
|
|
Returns:
|
|
S_OK Success
|
|
E_OUTOFMEMORY Failure
|
|
|
|
Side Effects
|
|
Allocates memory
|
|
|
|
===================================================================*/
|
|
HRESULT CResponseBuffer::Init(CResponseBufferSet * pBufferSet,
|
|
DWORD dwBufferLimit)
|
|
{
|
|
Assert(pBufferSet);
|
|
|
|
// Set the pointer to the enclosing response object
|
|
m_pBufferSet = pBufferSet;
|
|
m_dwBufferLimit = dwBufferLimit;
|
|
|
|
m_fInited = TRUE;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseBuffer::~CResponseBuffer
|
|
|
|
Destructor
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
Nothing
|
|
|
|
Side Effects
|
|
Frees memory
|
|
|
|
===================================================================*/
|
|
CResponseBuffer::~CResponseBuffer()
|
|
{
|
|
Assert(m_rgpchBuffers);
|
|
|
|
// Free all the buffers we've allocated
|
|
for (DWORD i = 0; i < m_cBuffers; i++)
|
|
{
|
|
if (m_rgpchBuffers[i])
|
|
{
|
|
ACACHE_FSA_FREE(ResponseBuffer, m_rgpchBuffers[i]);
|
|
}
|
|
}
|
|
|
|
// Free the array of buffer pointers
|
|
// (only if allocated - doesn't point to the member pointer
|
|
if (m_cBufferPointers > 1)
|
|
free(m_rgpchBuffers);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseBuffer::GrowBuffers
|
|
|
|
Increases available buffer space
|
|
|
|
Parameters:
|
|
cchNewRequest count of bytes to be accomodated
|
|
|
|
Returns:
|
|
HRESULT Indicating success or type of failure
|
|
|
|
Side Effects
|
|
May cause memory to be allocated
|
|
|
|
===================================================================*/
|
|
HRESULT CResponseBuffer::GrowBuffers(DWORD cchNewRequest)
|
|
{
|
|
Assert(m_fInited);
|
|
|
|
// Calculate how many more buffers are needed
|
|
DWORD cAddBuffers = (cchNewRequest+RESPONSE_BUFFER_SIZE-1)/RESPONSE_BUFFER_SIZE;
|
|
|
|
// Always at least one must be there already
|
|
Assert(m_rgpchBuffers);
|
|
Assert(m_cBufferPointers);
|
|
|
|
// Allocate more buffer pointers if needed
|
|
if (cAddBuffers > (m_cBufferPointers - m_cBuffers)) // doesn't fit?
|
|
{
|
|
char **rgpchTmp;
|
|
DWORD cNewBufferPointers = m_cBufferPointers + cAddBuffers + BUFFERS_INCREMENT;
|
|
|
|
if (m_cBufferPointers == 1)
|
|
rgpchTmp = (char **)malloc(cNewBufferPointers*sizeof(char *));
|
|
else
|
|
rgpchTmp = (char **)realloc(m_rgpchBuffers, cNewBufferPointers*sizeof(char *));
|
|
if (!rgpchTmp)
|
|
return E_OUTOFMEMORY;
|
|
|
|
// preserve the first buffer pointer in the special case
|
|
// of m_rgpchBuffers initally pointing to a member buffer pointer
|
|
if (m_cBufferPointers == 1)
|
|
rgpchTmp[0] = m_rgpchBuffers[0];
|
|
|
|
m_rgpchBuffers = rgpchTmp;
|
|
m_cBufferPointers = cNewBufferPointers;
|
|
}
|
|
|
|
// Allocate the new buffers
|
|
for (DWORD i = 0; i < cAddBuffers; i++)
|
|
{
|
|
char *pchTmp = (char *)ACACHE_FSA_ALLOC(ResponseBuffer);
|
|
if (!pchTmp)
|
|
return E_OUTOFMEMORY;
|
|
m_rgpchBuffers[m_cBuffers++] = pchTmp;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/*===================================================================
|
|
CResponseBuffer::Write
|
|
|
|
Writes data to the CResponseBuffer object. We first write
|
|
a data structure that describes this segment of the buffer.
|
|
The data structure identifies which method is doing the
|
|
writing, and contains an index to the starting buffer,
|
|
the starting offset in that buffer, and the length of the
|
|
data. The data itself is then writen to one or more buffers.
|
|
New buffers are allocated as needed.
|
|
Pointers to the data blocks in the response buffers are added to
|
|
the ResponseVector for efficient writeto the client.
|
|
|
|
Parameters:
|
|
szSource pointer to buffer to read into the Response buffer
|
|
cch count of bytes to be read into the Response buffer
|
|
fChunkData indicates that the data should be chunked
|
|
fTemplateData indicates (HTML) data that can be referenced rather than copied
|
|
|
|
Returns:
|
|
HRESULT Indicating success or type of failure
|
|
|
|
Side Effects
|
|
May cause memory to be allocated
|
|
|
|
===================================================================*/
|
|
HRESULT CResponseBuffer::Write(char* szSource, DWORD cch, BOOL fChunkData, BOOL fTemplateData)
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
char* pTmp;
|
|
CHAR szBuf[16]; // to hold a sting with length of data + CRLF, if chunking
|
|
int dwNumLen;
|
|
|
|
Assert(m_fInited);
|
|
|
|
// nothing to do if chunking and we got a zero junk.
|
|
|
|
if ((cch == 0) && fChunkData)
|
|
return S_OK;
|
|
|
|
// check to see if we've blown out the buffer limit...
|
|
|
|
if ((m_ResponseVector.BytesBuffered() + cch) > m_dwBufferLimit) {
|
|
|
|
// dump the current contents so as not to obscure the error with the
|
|
// buffered data
|
|
|
|
Clear();
|
|
|
|
// generate the error
|
|
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_BUFFER_LIMIT_EXCEEDED);
|
|
|
|
// return error so that the script engine stops
|
|
|
|
hr = E_FAIL;
|
|
|
|
goto lRet;
|
|
}
|
|
|
|
// add the chunk info if we're chunking
|
|
|
|
if (fChunkData) {
|
|
|
|
// chunk length
|
|
_itoa(cch, szBuf, 16);
|
|
dwNumLen = strlen(szBuf);
|
|
|
|
// CR LF
|
|
szBuf[dwNumLen] = '\r';
|
|
szBuf[dwNumLen+1] = '\n';
|
|
|
|
// call Write recursively.
|
|
|
|
hr = Write(szBuf, dwNumLen+2, FALSE);
|
|
|
|
if (FAILED(hr))
|
|
goto lRet;
|
|
}
|
|
|
|
// Caclulate how much buffer space we have left
|
|
DWORD cchBufferRemaining;
|
|
if (m_cBuffers)
|
|
cchBufferRemaining = RESPONSE_BUFFER_SIZE - m_cchOffsetInCurrentBuffer;
|
|
else
|
|
cchBufferRemaining = 0;
|
|
|
|
// update statistics
|
|
RespVectStat_DataSizeStats(fTemplateData, cch);
|
|
|
|
if (fTemplateData && (cch > MAX_HTML_IN_RESPONSE_BUFFER))
|
|
{
|
|
// don't copy the data, just add it to the response vector
|
|
hr = m_ResponseVector.Insert(szSource, cch);
|
|
|
|
if (SUCCEEDED(hr))
|
|
hr = m_pBufferSet->AddTemplateToArray();
|
|
}
|
|
else if (cch <= cchBufferRemaining)
|
|
{
|
|
// Enough space available, copy data to buffer
|
|
pTmp = m_rgpchBuffers[m_iCurrentBuffer] + m_cchOffsetInCurrentBuffer;
|
|
memcpy(pTmp, szSource, cch);
|
|
hr = m_ResponseVector.Append(pTmp, cch);
|
|
m_cchOffsetInCurrentBuffer += cch;
|
|
}
|
|
else
|
|
{
|
|
// Not enough space in current buffer, allocate more buffers
|
|
hr = GrowBuffers(cch - cchBufferRemaining);
|
|
if (FAILED(hr))
|
|
{
|
|
goto lRet;
|
|
}
|
|
|
|
// Copy data to the buffers, we loop to handle
|
|
// the case where the data is larger then the buffer size
|
|
while (cch)
|
|
{
|
|
if (RESPONSE_BUFFER_SIZE == m_cchOffsetInCurrentBuffer)
|
|
{
|
|
m_iCurrentBuffer++;
|
|
m_cchOffsetInCurrentBuffer = 0;
|
|
m_ResponseVector.Close();
|
|
}
|
|
DWORD cchToCopy = min(cch, (RESPONSE_BUFFER_SIZE - m_cchOffsetInCurrentBuffer));
|
|
pTmp = m_rgpchBuffers[m_iCurrentBuffer] + m_cchOffsetInCurrentBuffer;
|
|
memcpy(pTmp, szSource, cchToCopy);
|
|
hr = m_ResponseVector.Append(pTmp, cchToCopy);
|
|
m_cchOffsetInCurrentBuffer += cchToCopy;
|
|
if (FAILED(hr))
|
|
{
|
|
goto lRet;
|
|
}
|
|
szSource += cchToCopy;
|
|
cch -= cchToCopy;
|
|
}
|
|
}
|
|
|
|
// add the trailing \r\n
|
|
|
|
if (fChunkData)
|
|
hr = Write(szBuf+dwNumLen, 2, FALSE);
|
|
|
|
lRet:
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseBuffer::Clear
|
|
|
|
Deletes all information currently in the buffers, and restores
|
|
the buffer array to it's starting state.
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
S_OK success
|
|
|
|
Side Effects
|
|
May free memory
|
|
|
|
===================================================================*/
|
|
HRESULT CResponseBuffer::Clear()
|
|
{
|
|
Assert(m_fInited);
|
|
|
|
m_ResponseVector.Clear();
|
|
|
|
if (m_cBuffers == 0)
|
|
return S_OK;
|
|
|
|
// Free all but the first of the allocated buffers
|
|
for (DWORD i = 1; i < m_cBuffers; i++)
|
|
{
|
|
ACACHE_FSA_FREE(ResponseBuffer, m_rgpchBuffers[i]);
|
|
m_rgpchBuffers[i] = NULL;
|
|
}
|
|
|
|
m_cBuffers = 1;
|
|
m_iCurrentBuffer = 0;
|
|
m_cchOffsetInCurrentBuffer = 0;
|
|
return S_OK;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
* C D e b u g R e s p o n s e B u f f e r
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
/*===================================================================
|
|
CDebugResponseBuffer::AppendRecord
|
|
|
|
Create client side debugger metadata record and appends it to
|
|
the buffer
|
|
|
|
Parameters:
|
|
|
|
Returns:
|
|
HRESULT Indicating success or type of failure
|
|
===================================================================*/
|
|
HRESULT CDebugResponseBuffer::AppendRecord
|
|
(
|
|
const int cchBlockOffset,
|
|
const int cchBlockLength,
|
|
const int cchSourceOffset,
|
|
const char *pszSourceFile
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
#define CCH_METADATA_RECORD_MAX 40 // without filename
|
|
|
|
if (pszSourceFile)
|
|
{
|
|
char *pszBuf = new char [strlen(pszSourceFile) +
|
|
CCH_METADATA_RECORD_MAX + 1];
|
|
if (pszBuf)
|
|
{
|
|
sprintf(pszBuf, "%d,%d,%d,%s\r\n",
|
|
cchBlockOffset, cchBlockLength, cchSourceOffset,
|
|
pszSourceFile);
|
|
|
|
hr = Write(pszBuf);
|
|
delete [] pszBuf;
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char szBuf[CCH_METADATA_RECORD_MAX+1];
|
|
sprintf(szBuf, "%d,%d,%d\r\n",
|
|
cchBlockOffset, cchBlockLength, cchSourceOffset);
|
|
|
|
hr = Write(szBuf);
|
|
}
|
|
|
|
#undef CCH_METADATA_RECORD_MAX
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
* C H T T P H e a d e r
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
/*===================================================================
|
|
CHTTPHeader::CHTTPHeader
|
|
|
|
Constructor.
|
|
===================================================================*/
|
|
CHTTPHeader::CHTTPHeader()
|
|
:
|
|
m_fInited(FALSE),
|
|
m_fNameAllocated(FALSE), m_fValueAllocated(FALSE),
|
|
m_szName(NULL), m_szValue(NULL),
|
|
m_cchName(0), m_cchValue(0),
|
|
m_pNext(NULL)
|
|
{
|
|
}
|
|
|
|
/*===================================================================
|
|
CHTTPHeader::~CHTTPHeader
|
|
|
|
Destructor
|
|
===================================================================*/
|
|
CHTTPHeader::~CHTTPHeader()
|
|
{
|
|
if (m_fNameAllocated)
|
|
{
|
|
Assert(m_szName);
|
|
delete [] m_szName;
|
|
}
|
|
if (m_fValueAllocated)
|
|
{
|
|
Assert(m_szValue);
|
|
delete [] m_szValue;
|
|
}
|
|
}
|
|
|
|
/*===================================================================
|
|
HRESULT CHTTPHeader::InitHeader
|
|
|
|
Functions set the header strings. Agrument types combinations:
|
|
BSTR, BSTR
|
|
hardcoded char*, BSTR
|
|
hardcoded char*, hardcoded char*
|
|
hardcoded char*, int
|
|
|
|
Parameters:
|
|
Name, Value
|
|
|
|
Returns:
|
|
S_OK Success
|
|
===================================================================*/
|
|
HRESULT CHTTPHeader::InitHeader(BSTR wszName, BSTR wszValue, UINT lCodePage /* CP_ACP */)
|
|
{
|
|
Assert(!m_fInited);
|
|
Assert(wszName);
|
|
|
|
CWCharToMBCS convStr;
|
|
HRESULT hr = S_OK;
|
|
|
|
// name
|
|
|
|
if (FAILED(hr = convStr.Init(wszName,lCodePage))) {
|
|
if (hr == E_OUTOFMEMORY)
|
|
return hr;
|
|
m_fNameAllocated = FALSE;
|
|
m_szName = "";
|
|
}
|
|
else {
|
|
m_szName = convStr.GetString(TRUE);
|
|
m_fNameAllocated = TRUE;
|
|
}
|
|
m_cchName = strlen(m_szName);
|
|
|
|
// value
|
|
int cch = wszValue ? wcslen(wszValue) : 0;
|
|
if (cch > 0)
|
|
{
|
|
if (FAILED(hr = convStr.Init(wszValue,lCodePage))) {
|
|
return hr;
|
|
}
|
|
m_szValue = convStr.GetString(TRUE);
|
|
m_fValueAllocated = TRUE;
|
|
m_cchValue = strlen(m_szValue);
|
|
}
|
|
else
|
|
{
|
|
m_szValue = NULL;
|
|
m_fValueAllocated = FALSE;
|
|
m_cchValue = 0;
|
|
}
|
|
|
|
m_fInited = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CHTTPHeader::InitHeader(char *szName, BSTR wszValue, UINT lCodePage /* = CP_ACP */)
|
|
{
|
|
Assert(!m_fInited);
|
|
Assert(szName);
|
|
|
|
CWCharToMBCS convStr;
|
|
HRESULT hr = S_OK;
|
|
|
|
m_szName = szName;
|
|
m_cchName = strlen(m_szName);
|
|
m_fNameAllocated = FALSE;
|
|
|
|
int cch = wszValue ? wcslen(wszValue) : 0;
|
|
if (cch > 0)
|
|
{
|
|
if (FAILED(hr = convStr.Init(wszValue,lCodePage))) {
|
|
return hr;
|
|
}
|
|
m_szValue = convStr.GetString(TRUE);
|
|
m_fValueAllocated = TRUE;
|
|
m_cchValue = strlen(m_szValue);
|
|
}
|
|
else
|
|
{
|
|
m_szValue = NULL;
|
|
m_fValueAllocated = FALSE;
|
|
m_cchValue = 0;
|
|
}
|
|
|
|
m_fInited = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CHTTPHeader::InitHeader(char *szName, char *szValue, BOOL fCopyValue)
|
|
{
|
|
Assert(!m_fInited);
|
|
Assert(szName);
|
|
|
|
m_szName = szName;
|
|
m_cchName = strlen(m_szName);
|
|
m_fNameAllocated = FALSE;
|
|
|
|
if (fCopyValue)
|
|
{
|
|
int cch = szValue ? strlen(szValue) : 0;
|
|
if (cch > 0)
|
|
{
|
|
m_szValue = new char[cch+1];
|
|
if (m_szValue == NULL)
|
|
return E_OUTOFMEMORY;
|
|
m_fValueAllocated = TRUE;
|
|
strcpy(m_szValue, szValue);
|
|
m_cchValue = cch;
|
|
}
|
|
else
|
|
{
|
|
m_szValue = NULL;
|
|
m_fValueAllocated = FALSE;
|
|
m_cchValue = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_szValue = szValue;
|
|
m_cchValue = strlen(m_szValue);
|
|
m_fValueAllocated = FALSE;
|
|
}
|
|
|
|
m_fInited = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CHTTPHeader::InitHeader(char *szName, long lValue)
|
|
{
|
|
Assert(!m_fInited);
|
|
Assert(szName);
|
|
|
|
m_szName = szName;
|
|
m_cchName = strlen(m_szName);
|
|
m_fNameAllocated = FALSE;
|
|
|
|
ltoa(lValue, m_rgchLtoaBuffer, 10);
|
|
m_szValue = m_rgchLtoaBuffer;
|
|
m_cchValue = strlen(m_szValue);
|
|
m_fValueAllocated = FALSE;
|
|
|
|
m_fInited = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CHTTPHeader::Print
|
|
|
|
Prints the header into a buffer in "Header: Value\r\n" format.
|
|
|
|
Parameters:
|
|
szBuf buffer to fill
|
|
===================================================================*/
|
|
void CHTTPHeader::Print
|
|
(
|
|
char *szBuf
|
|
)
|
|
{
|
|
Assert(m_fInited);
|
|
|
|
Assert(m_cchName);
|
|
Assert(m_szName);
|
|
memcpy(szBuf, m_szName, m_cchName);
|
|
szBuf += m_cchName;
|
|
|
|
*szBuf++ = ':';
|
|
*szBuf++ = ' ';
|
|
|
|
if (m_cchValue)
|
|
{
|
|
Assert(m_szValue);
|
|
memcpy(szBuf, m_szValue, m_cchValue);
|
|
szBuf += m_cchValue;
|
|
}
|
|
|
|
*szBuf++ = '\r';
|
|
*szBuf++ = '\n';
|
|
*szBuf = '\0';
|
|
}
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
* C R e s p o n s e D a t a
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
/*===================================================================
|
|
CResponseBufferSet::CResponseBufferSet
|
|
|
|
Constructor
|
|
|
|
Parameters:
|
|
|
|
Returns:
|
|
Nothing.
|
|
===================================================================*/
|
|
CResponseBufferSet::CResponseBufferSet()
|
|
{
|
|
m_pResponseBuffer = NULL;
|
|
m_pClientDebugBuffer = NULL;
|
|
m_pTemplate = NULL;
|
|
m_fCurTemplateInArray = FALSE;
|
|
m_fTemplateArrayAllocd = FALSE;
|
|
m_aTemplates[0] = NULL;
|
|
m_dwTemplatesRefd = 0;
|
|
m_ppTemplates = m_aTemplates;
|
|
m_dwArraySize = sizeof(m_aTemplates)/sizeof(CTemplate *);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseBufferSet::~CResponseBufferSet
|
|
|
|
Destructor
|
|
|
|
Parameters:
|
|
|
|
Returns:
|
|
Nothing.
|
|
===================================================================*/
|
|
CResponseBufferSet::~CResponseBufferSet()
|
|
{
|
|
if (m_pResponseBuffer)
|
|
delete m_pResponseBuffer;
|
|
if (m_pClientDebugBuffer)
|
|
delete m_pClientDebugBuffer;
|
|
|
|
// release any templates that were referenced
|
|
|
|
for (DWORD i=0; i < m_dwTemplatesRefd; i++)
|
|
m_aTemplates[i]->Release();
|
|
|
|
// delete the template array if it was allocated
|
|
|
|
if (m_fTemplateArrayAllocd)
|
|
free(m_ppTemplates);
|
|
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseBufferSet::Init
|
|
|
|
Initializes this object with the CResponseBuffer.
|
|
|
|
Parameters:
|
|
dwBufferLimit maximum to buffer
|
|
|
|
Returns:
|
|
Nothing.
|
|
===================================================================*/
|
|
HRESULT CResponseBufferSet::Init(DWORD dwBufferLimit)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
m_pResponseBuffer = new CResponseBuffer;
|
|
|
|
if (m_pResponseBuffer == NULL)
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
if (SUCCEEDED(hr))
|
|
hr = m_pResponseBuffer->Init(this, dwBufferLimit);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseBufferSet::InitDebugBuffer
|
|
|
|
Clears or allocates a new CDebugResponseBuffer.
|
|
|
|
Parameters:
|
|
dwBufferLimit maximum to buffer
|
|
|
|
Returns:
|
|
Nothing.
|
|
===================================================================*/
|
|
HRESULT CResponseBufferSet::InitDebugBuffer(DWORD dwBufferLimit)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pClientDebugBuffer) {
|
|
hr = m_pClientDebugBuffer->ClearAndStart();
|
|
}
|
|
else {
|
|
m_pClientDebugBuffer = new CDebugResponseBuffer;
|
|
if (m_pClientDebugBuffer)
|
|
hr = m_pClientDebugBuffer->InitAndStart(this, dwBufferLimit);
|
|
else
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseBufferSet::AddTemplateToArray
|
|
|
|
Takes the current m_pTemplate member variable and adds it to the
|
|
array of ppTemplates. A call to this routine likely indicates that
|
|
pTemplate memory is being referenced in the response data and so the
|
|
lifetime of the Template needs to include the async completion of
|
|
writing the data.
|
|
|
|
Parameters:
|
|
|
|
Returns:
|
|
Nothing.
|
|
===================================================================*/
|
|
HRESULT CResponseBufferSet::AddTemplateToArray() {
|
|
|
|
DWORD i;
|
|
HRESULT hr = S_OK;
|
|
|
|
// nothing to do if it's already in the array
|
|
|
|
if (m_fCurTemplateInArray)
|
|
return hr;
|
|
|
|
// check the current list to see if it's already
|
|
// in there. Because of template swapping caused
|
|
// by child executes, a template could come and go
|
|
// in the response buffer structure. When the template
|
|
// is set again, it is first assumed to NOT be in the
|
|
// array.
|
|
|
|
for (i=0; i < m_dwTemplatesRefd; i++) {
|
|
|
|
if (m_pTemplate == m_ppTemplates[i]) {
|
|
|
|
// found it. Note that it's in aldready and return immediately
|
|
|
|
m_fCurTemplateInArray = TRUE;
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
// ok, it's not currently in the array. We need to add it.
|
|
|
|
// first check to make sure that there is enough room in the
|
|
// array for the pointer.
|
|
|
|
if (i >= m_dwArraySize) {
|
|
|
|
// need to grow the array. The array grows in 128 entry
|
|
// chunks.
|
|
|
|
if (m_fTemplateArrayAllocd) {
|
|
|
|
// if it's already been dynamically allocated, then a realloc
|
|
// is in order.
|
|
|
|
void *pTemp;
|
|
|
|
// attempt the realloc
|
|
|
|
pTemp = realloc(m_ppTemplates, sizeof(CTemplate *)*(m_dwArraySize+128));
|
|
|
|
// if it's successful, then update the pointer with the new
|
|
// value. If not successful, return OOM
|
|
|
|
if (pTemp) {
|
|
|
|
// up the array size by the 128 chunk
|
|
|
|
m_dwArraySize += 128;
|
|
|
|
m_ppTemplates = (CTemplate **)pTemp;
|
|
}
|
|
else
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else {
|
|
|
|
// blew out the internal array. Need to allocate from
|
|
// the heap.
|
|
|
|
m_ppTemplates = (CTemplate **)malloc(sizeof(CTemplate *) * 128);
|
|
|
|
// if successful, note the new array size, copy the internal array
|
|
// in, and note that we've allocated the array.
|
|
|
|
if (m_ppTemplates) {
|
|
m_dwArraySize = 128;
|
|
memcpy(m_ppTemplates, m_aTemplates, sizeof(m_aTemplates));
|
|
m_fTemplateArrayAllocd = TRUE;
|
|
}
|
|
else {
|
|
m_ppTemplates = m_aTemplates;
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if everything is still OK, then there is sufficient room in the array
|
|
// for the template.
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
m_ppTemplates[m_dwTemplatesRefd++] = m_pTemplate;
|
|
m_pTemplate->AddRef();
|
|
m_fCurTemplateInArray = TRUE;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseBufferSet::SendResponseCompletion
|
|
|
|
Handles Async Write completion. Really not much to do here.
|
|
Just delete the BufferSet object.
|
|
|
|
Parameters:
|
|
CIsapiReqInfo *
|
|
PVOID // actually CResponseBufferSet
|
|
DWORD cbIO
|
|
DWORD dwError
|
|
|
|
Returns:
|
|
Nothing.
|
|
===================================================================*/
|
|
VOID CResponseBufferSet::SendResponseCompletion(CIsapiReqInfo *pIReq,
|
|
PVOID pContext,
|
|
DWORD cbIO,
|
|
DWORD dwError)
|
|
{
|
|
CResponseBufferSet *pBufferSet = (CResponseBufferSet *)pContext;
|
|
|
|
#ifndef PERF_DISABLE
|
|
AddtoTotalByteOut(cbIO);
|
|
#endif
|
|
|
|
delete pBufferSet;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
* C R e s p o n s e D a t a
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
/*===================================================================
|
|
CResponseData::CResponseData
|
|
|
|
Constructor
|
|
|
|
Parameters:
|
|
CResponse *pResponse
|
|
|
|
Returns:
|
|
Nothing.
|
|
===================================================================*/
|
|
CResponseData::CResponseData
|
|
(
|
|
CResponse *pResponse
|
|
)
|
|
:
|
|
m_ISupportErrImp(static_cast<IResponse *>(pResponse), this, IID_IResponse),
|
|
m_WriteCookies(pResponse, this),
|
|
m_cRefs(1)
|
|
{
|
|
m_pIReq = NULL;
|
|
m_pHitObj = NULL;
|
|
m_pFirstHeader = m_pLastHeader = NULL;
|
|
m_fResponseAborted = FALSE;
|
|
m_fWriteClientError = FALSE;
|
|
m_fIgnoreWrites = FALSE;
|
|
m_fBufferingOn = FALSE;
|
|
m_fFlushed = FALSE;
|
|
m_fChunkData = FALSE;
|
|
m_fChunkDataInited = FALSE;
|
|
m_fClientDebugMode = FALSE;
|
|
m_fClientDebugFlushIgnored = FALSE;
|
|
m_szCookieVal = NULL;
|
|
m_pszDefaultContentType = NULL;
|
|
m_pszContentType = NULL;
|
|
m_pszCharSet = NULL;
|
|
m_pszStatus = NULL;
|
|
m_pszCacheControl = NULL;
|
|
m_dwVersionMajor = 0;
|
|
m_dwVersionMinor = 0;
|
|
m_pBufferSet = NULL;
|
|
m_tExpires = -1;
|
|
m_pszDefaultExpires = NULL;
|
|
m_pfnGetScript = NULL;
|
|
m_pvGetScriptContext = NULL;
|
|
m_dwBufferLimit = DEFAULT_BUFFER_LIMIT;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseData::~CResponseData
|
|
|
|
Destructor
|
|
|
|
Parameters:
|
|
|
|
Returns:
|
|
Nothing.
|
|
===================================================================*/
|
|
CResponseData::~CResponseData()
|
|
{
|
|
// points to static string - no need to free
|
|
// m_pszDefaultContentType = NULL;
|
|
|
|
// Free any memory associated with the content-type
|
|
if (m_pszContentType)
|
|
free(m_pszContentType);
|
|
|
|
// Free any memory associated with the CacheControl
|
|
if (m_pszCacheControl)
|
|
free(m_pszCacheControl);
|
|
|
|
// Free any memory associated with the CharSet
|
|
if (m_pszCharSet)
|
|
free(m_pszCharSet);
|
|
|
|
// Free any memory associated with the status
|
|
if (m_pszStatus)
|
|
free(m_pszStatus);
|
|
|
|
// Free all headers
|
|
CHTTPHeader *pHeader = m_pFirstHeader;
|
|
while (pHeader)
|
|
{
|
|
CHTTPHeader *pNextHeader = pHeader->PNext();
|
|
delete pHeader;
|
|
pHeader = pNextHeader;
|
|
}
|
|
m_pFirstHeader = m_pLastHeader = NULL;
|
|
|
|
if (m_pBufferSet)
|
|
delete m_pBufferSet;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseData::Init
|
|
|
|
Init
|
|
|
|
Parameters:
|
|
|
|
Returns:
|
|
Nothing.
|
|
===================================================================*/
|
|
HRESULT CResponseData::Init()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
m_pIReq = NULL;
|
|
|
|
// set the HEAD request flag to 0 un-inited
|
|
m_IsHeadRequest = 0;
|
|
|
|
// Initialize header list
|
|
m_pFirstHeader = m_pLastHeader = NULL;
|
|
|
|
m_pBufferSet = new CResponseBufferSet;
|
|
|
|
if (m_pBufferSet == NULL)
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
if (SUCCEEDED(hr))
|
|
hr = m_pBufferSet->Init(m_dwBufferLimit);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponseData::QueryInterface
|
|
CResponseData::AddRef
|
|
CResponseData::Release
|
|
|
|
IUnknown members for CRequestData object.
|
|
===================================================================*/
|
|
STDMETHODIMP CResponseData::QueryInterface
|
|
(
|
|
REFIID iid,
|
|
void **ppvObj
|
|
)
|
|
{
|
|
if (iid == IID_IUnknown)
|
|
{
|
|
*ppvObj = this;
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
*ppvObj = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CResponseData::AddRef()
|
|
{
|
|
return ++m_cRefs;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CResponseData::Release(void)
|
|
{
|
|
if (--m_cRefs)
|
|
return m_cRefs;
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
* C R e s p o n s e
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
/*===================================================================
|
|
CResponse::CResponse
|
|
|
|
Constructor
|
|
|
|
Parameters:
|
|
punkOuter object to ref count (can be NULL)
|
|
===================================================================*/
|
|
CResponse::CResponse(IUnknown *punkOuter)
|
|
:
|
|
m_fInited(FALSE),
|
|
m_fDiagnostics(FALSE),
|
|
m_pUnkFTM(NULL),
|
|
m_pData(NULL)
|
|
{
|
|
CDispatch::Init(IID_IResponse);
|
|
|
|
if (punkOuter)
|
|
{
|
|
m_punkOuter = punkOuter;
|
|
m_fOuterUnknown = TRUE;
|
|
}
|
|
else
|
|
{
|
|
m_cRefs = 1;
|
|
m_fOuterUnknown = FALSE;
|
|
}
|
|
|
|
#ifdef DBG
|
|
m_fDiagnostics = TRUE;
|
|
#endif // DBG
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::~CResponse
|
|
|
|
Destructor
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
Nothing.
|
|
|
|
===================================================================*/
|
|
CResponse::~CResponse()
|
|
{
|
|
Assert(!m_fInited);
|
|
Assert(m_fOuterUnknown || m_cRefs == 0); // must have 0 ref count
|
|
|
|
if ( m_pUnkFTM != NULL )
|
|
{
|
|
m_pUnkFTM->Release();
|
|
m_pUnkFTM = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::CleanUp
|
|
|
|
Deallocates members and removes m_pData
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
HRESULT (S_OK)
|
|
===================================================================*/
|
|
HRESULT CResponse::CleanUp()
|
|
{
|
|
if (m_pData)
|
|
{
|
|
m_pData->Release();
|
|
m_pData = NULL;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::Init
|
|
|
|
Allocates m_pData
|
|
Performs any intiailization of a CResponse that's prone to failure
|
|
that we also use internally before exposing the object outside.
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
S_OK on success.
|
|
|
|
===================================================================*/
|
|
HRESULT CResponse::Init()
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_fInited)
|
|
return S_OK; // already inited
|
|
|
|
Assert(!m_pData);
|
|
|
|
// Create the FTM
|
|
if (m_pUnkFTM == NULL)
|
|
{
|
|
hr = CoCreateFreeThreadedMarshaler((IUnknown*)((IResponseImpl *)this), &m_pUnkFTM );
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
Assert( m_pUnkFTM == NULL );
|
|
return (hr);
|
|
}
|
|
}
|
|
|
|
Assert( m_pUnkFTM != NULL );
|
|
|
|
m_pData = new CResponseData(this);
|
|
if (!m_pData)
|
|
return E_OUTOFMEMORY;
|
|
|
|
hr = m_pData->Init();
|
|
|
|
if (SUCCEEDED(hr))
|
|
m_fInited = TRUE;
|
|
else
|
|
CleanUp();
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::UnInit
|
|
|
|
Remove m_pData. Back to UnInited state
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
HRESULT
|
|
===================================================================*/
|
|
HRESULT CResponse::UnInit()
|
|
{
|
|
if (!m_fInited)
|
|
return S_OK; // already uninited
|
|
|
|
Assert(m_pData);
|
|
CleanUp();
|
|
Assert(!m_pData);
|
|
|
|
// Disconnect proxies NOW (in case we are in shutdown, or enter shutdown later & a proxy has a ref.)
|
|
CoDisconnectObject(static_cast<IResponseImpl *>(this), 0);
|
|
|
|
m_fInited = FALSE;
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::ReInitTemplate
|
|
|
|
This function is used to set the template member. It should only
|
|
be used for an ordinary script file's template, not for global.asa template.
|
|
|
|
Parameters:
|
|
Pointer to template
|
|
|
|
Returns:
|
|
S_OK on success.
|
|
===================================================================*/
|
|
HRESULT CResponse::ReInitTemplate
|
|
(
|
|
CTemplate* pTemplate,
|
|
const char *szCookieVal
|
|
)
|
|
{
|
|
Assert(m_fInited);
|
|
Assert(m_pData);
|
|
|
|
Assert(pTemplate != NULL);
|
|
Assert(m_pData->m_pBufferSet->PTemplate() == NULL);
|
|
|
|
m_pData->m_pBufferSet->SetTemplate(pTemplate);
|
|
|
|
m_pData->m_szCookieVal = szCookieVal;
|
|
return(S_OK);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::SwapTemplate
|
|
|
|
Temporary substitutes Template in response
|
|
Used in child request execution
|
|
|
|
Parameters:
|
|
Pointer to the new template
|
|
|
|
Returns:
|
|
Pointer to the old template
|
|
===================================================================*/
|
|
CTemplate *CResponse::SwapTemplate
|
|
(
|
|
CTemplate* pNewTemplate
|
|
)
|
|
{
|
|
Assert(m_fInited);
|
|
Assert(m_pData);
|
|
|
|
CTemplate *pOldTemplate = m_pData->m_pBufferSet->PTemplate();
|
|
|
|
m_pData->m_pBufferSet->SetTemplate(pNewTemplate);
|
|
|
|
return pOldTemplate;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::ReInit
|
|
|
|
Each Request we service will have a new CIsapiReqInfo.
|
|
This function is used to set the value of the CIsapiReqInfo.
|
|
|
|
Parameters:
|
|
Pointer to CIsapiReqInfo
|
|
Returns:
|
|
S_OK on success.
|
|
===================================================================*/
|
|
HRESULT CResponse::ReInit
|
|
(
|
|
CIsapiReqInfo *pIReq,
|
|
const char *szCookieVal,
|
|
CRequest *pRequest,
|
|
PFNGETSCRIPT pfnGetScript,
|
|
void *pvGetScriptContext,
|
|
CHitObj *pHitObj
|
|
)
|
|
{
|
|
Assert(m_fInited);
|
|
Assert(m_pData);
|
|
|
|
CHTTPHeader *pCurr;
|
|
CLinkElem *pT;
|
|
CLinkElem *pNext;
|
|
|
|
// set the HEAD request flag to 0 un-inited
|
|
m_pData->m_IsHeadRequest = 0;
|
|
|
|
// ReInitialize the WriteCookie dictionary
|
|
if (FAILED(m_pData->m_WriteCookies.ReInit(pRequest)))
|
|
return E_FAIL;
|
|
|
|
// points to static string - no need to free
|
|
m_pData->m_pszDefaultContentType = NULL;
|
|
|
|
// Free any memory associated with the content type
|
|
if (m_pData->m_pszContentType != NULL)
|
|
{
|
|
free(m_pData->m_pszContentType);
|
|
m_pData->m_pszContentType = NULL;
|
|
}
|
|
|
|
// Free any memory associated with the content type
|
|
if (m_pData->m_pszCharSet != NULL)
|
|
{
|
|
free(m_pData->m_pszCharSet);
|
|
m_pData->m_pszCharSet = NULL;
|
|
}
|
|
|
|
|
|
// Free any memory associated with the status
|
|
if (m_pData->m_pszStatus != NULL)
|
|
{
|
|
free(m_pData->m_pszStatus);
|
|
m_pData->m_pszStatus = NULL;
|
|
}
|
|
|
|
// Free all headers
|
|
CHTTPHeader *pHeader = m_pData->m_pFirstHeader;
|
|
while (pHeader)
|
|
{
|
|
CHTTPHeader *pNextHeader = pHeader->PNext();
|
|
delete pHeader;
|
|
pHeader = pNextHeader;
|
|
}
|
|
m_pData->m_pFirstHeader = m_pData->m_pLastHeader = NULL;
|
|
|
|
m_pData->m_fResponseAborted = FALSE;
|
|
m_pData->m_fWriteClientError = FALSE;
|
|
m_pData->m_fIgnoreWrites = FALSE;
|
|
m_pData->m_pIReq = pIReq;
|
|
m_pData->m_szCookieVal = szCookieVal;
|
|
m_pData->m_pszDefaultContentType = NULL;
|
|
m_pData->m_pszContentType = NULL;
|
|
m_pData->m_pszCharSet = NULL;
|
|
m_pData->m_pszStatus = NULL;
|
|
m_pData->m_pfnGetScript = pfnGetScript;
|
|
m_pData->m_pvGetScriptContext = pvGetScriptContext;
|
|
m_pData->m_pHitObj = pHitObj;
|
|
m_pData->m_tExpires = -1;
|
|
m_pData->m_pszDefaultExpires = NULL;
|
|
|
|
// Ask for the HTTP version of the client
|
|
GetClientVerison();
|
|
|
|
// Set the default content type
|
|
if (m_pData->m_pIReq)
|
|
m_pData->m_pszDefaultContentType = GetResponseMimeType(m_pData->m_pIReq);
|
|
|
|
// Set the buffering flag to the global value
|
|
m_pData->m_fBufferingOn = (pHitObj->QueryAppConfig())->fBufferingOn();
|
|
m_pData->SetBufferLimit(pHitObj->QueryAppConfig()->dwBufferLimit());
|
|
|
|
// Buffering always on for client code debug
|
|
if (pHitObj && pHitObj->FClientCodeDebug())
|
|
{
|
|
m_pData->m_fBufferingOn = TRUE;
|
|
m_pData->m_fClientDebugMode = TRUE;
|
|
m_pData->m_fClientDebugFlushIgnored = FALSE;
|
|
}
|
|
else
|
|
{
|
|
m_pData->m_fClientDebugMode = FALSE;
|
|
m_pData->m_fClientDebugFlushIgnored = FALSE;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pData->m_fClientDebugMode)
|
|
{
|
|
hr = m_pData->m_pBufferSet->InitDebugBuffer(m_pData->m_dwBufferLimit);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::QueryInterface
|
|
CResponse::AddRef
|
|
CResponse::Release
|
|
|
|
IUnknown members for CResponse object.
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::QueryInterface
|
|
(
|
|
REFIID riid,
|
|
PPVOID ppv
|
|
)
|
|
{
|
|
*ppv = NULL;
|
|
|
|
/*
|
|
* The only calls for IUnknown are either in a nonaggregated
|
|
* case or when created in an aggregation, so in either case
|
|
* always return our IUnknown for IID_IUnknown.
|
|
*/
|
|
|
|
// BUG FIX 683 added IID_IDenaliIntrinsic to prevent the user from
|
|
// storing intrinsic objects in the application and session object
|
|
if (IID_IUnknown == riid || IID_IDispatch == riid || IID_IResponse == riid || IID_IDenaliIntrinsic == riid)
|
|
*ppv = static_cast<IResponse *>(this);
|
|
|
|
// Support IStream for ADO/XML
|
|
else if (IID_IStream == riid)
|
|
*ppv = static_cast<IStream *>(this);
|
|
|
|
//Indicate that we support error information
|
|
else if (IID_ISupportErrorInfo == riid)
|
|
{
|
|
if (m_pData)
|
|
*ppv = &(m_pData->m_ISupportErrImp);
|
|
}
|
|
|
|
else if (IID_IMarshal == riid)
|
|
{
|
|
Assert( m_pUnkFTM != NULL );
|
|
|
|
if ( m_pUnkFTM == NULL )
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
return m_pUnkFTM->QueryInterface( riid, ppv );
|
|
}
|
|
|
|
//AddRef any interface we'll return.
|
|
if (NULL != *ppv)
|
|
{
|
|
((LPUNKNOWN)*ppv)->AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
return ResultFromScode(E_NOINTERFACE);
|
|
}
|
|
|
|
|
|
STDMETHODIMP_(ULONG) CResponse::AddRef(void)
|
|
{
|
|
if (m_fOuterUnknown)
|
|
return m_punkOuter->AddRef();
|
|
|
|
return InterlockedIncrement((LPLONG)&m_cRefs);
|
|
}
|
|
|
|
|
|
STDMETHODIMP_(ULONG) CResponse::Release(void)
|
|
{
|
|
if (m_fOuterUnknown)
|
|
return m_punkOuter->Release();
|
|
|
|
DWORD cRefs = InterlockedDecrement((LPLONG)&m_cRefs);
|
|
if (cRefs)
|
|
return cRefs;
|
|
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::GetIDsOfNames
|
|
|
|
Special-case implementation for Response.WriteBlock and
|
|
Response.Write
|
|
|
|
Parameters:
|
|
riid REFIID reserved. Must be IID_NULL.
|
|
rgszNames OLECHAR ** pointing to the array of names to be mapped.
|
|
cNames UINT number of names to be mapped.
|
|
lcid LCID of the locale.
|
|
rgDispID DISPID * caller allocated array containing IDs
|
|
corresponging to those names in rgszNames.
|
|
|
|
Return Value:
|
|
HRESULT S_OK or a general error code.
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::GetIDsOfNames
|
|
(
|
|
REFIID riid,
|
|
OLECHAR **rgszNames,
|
|
UINT cNames,
|
|
LCID lcid,
|
|
DISPID *rgDispID
|
|
)
|
|
{
|
|
const DISPID dispidWrite = 0x60020013;
|
|
const DISPID dispidWriteBlock = 0x60020014;
|
|
|
|
if (cNames == 1)
|
|
{
|
|
// first char 'W'
|
|
if (rgszNames[0][0] == L'w' || rgszNames[0][0] == L'W')
|
|
{
|
|
// swtich on strlen
|
|
switch (wcslen(rgszNames[0]))
|
|
{
|
|
case 5:
|
|
// case insensitive because user can type either way
|
|
if (wcsicmp(rgszNames[0], L"write") == 0)
|
|
{
|
|
*rgDispID = dispidWrite;
|
|
return S_OK;
|
|
}
|
|
break;
|
|
case 10:
|
|
// case sensitive because only we generate WriteBlock
|
|
if (wcscmp(rgszNames[0], L"WriteBlock") == 0)
|
|
{
|
|
*rgDispID = dispidWriteBlock;
|
|
return S_OK;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// default to CDispatch's implementation
|
|
return CDispatch::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispID);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::CheckForTombstone
|
|
|
|
Tombstone stub for IResponse methods. If the object is
|
|
tombstone, does ExceptionId and fails.
|
|
|
|
Parameters:
|
|
|
|
Returns:
|
|
HRESULT E_FAIL if Tombstone
|
|
S_OK if not
|
|
===================================================================*/
|
|
HRESULT CResponse::CheckForTombstone()
|
|
{
|
|
if (m_fInited)
|
|
{
|
|
// inited - good object
|
|
Assert(m_pData); // must be present for inited objects
|
|
return S_OK;
|
|
}
|
|
|
|
ExceptionId
|
|
(
|
|
IID_IResponse,
|
|
IDE_RESPONSE,
|
|
IDE_INTRINSIC_OUT_OF_SCOPE
|
|
);
|
|
return E_FAIL;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::StaticWrite
|
|
|
|
Static method. Sends data until either everything is sent
|
|
or there's an error.
|
|
|
|
Parameters:
|
|
pIReq CIsapiReqInfo to send
|
|
pchBuf pointer to buffer to send
|
|
cchBuf number of bytes to send (0 means do strlen())
|
|
pTemplate pointer to template if pchBuf points into a CTemplate
|
|
|
|
Returns:
|
|
HRESULT
|
|
===================================================================*/
|
|
HRESULT CResponse::StaticWrite
|
|
(
|
|
CIsapiReqInfo *pIReq,
|
|
char *pchBuf,
|
|
DWORD cchBuf,
|
|
CTemplate *pTemplate
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
HSE_SEND_ENTIRE_RESPONSE_INFO HseResponseInfo;
|
|
WSABUF_VECTORS WsabuffVectors;
|
|
LPWSABUF_VECTORS pWsabuffVectors = NULL;
|
|
BOOL fHeadersWritten = pIReq->FHeadersWritten();
|
|
BOOL fHeadRequest = (stricmp(pIReq->QueryPszMethod(), "HEAD") == 0);
|
|
|
|
if (fHeadersWritten && fHeadRequest)
|
|
return S_OK;
|
|
|
|
ZeroMemory( &HseResponseInfo, sizeof(HSE_SEND_ENTIRE_RESPONSE_INFO));
|
|
|
|
CResponseBufferSet *pBufferSet = new CResponseBufferSet;
|
|
|
|
if (pBufferSet == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
hr = pBufferSet->Init(DEFAULT_BUFFER_LIMIT);
|
|
|
|
if (FAILED(hr))
|
|
goto LExit;
|
|
|
|
if (cchBuf == 0)
|
|
cchBuf = strlen(pchBuf);
|
|
|
|
if (!fHeadersWritten) {
|
|
|
|
hr = ConstructSimpleHeaders( &HseResponseInfo.HeaderInfo, cchBuf,
|
|
(char *)GetResponseMimeType(pIReq));
|
|
|
|
if (FAILED(hr))
|
|
goto LExit;
|
|
|
|
// set fKeepConn to TRUE to indicate that we do prefer that to
|
|
// keep the connection. SendClientResponse() may decide that
|
|
// it's not possible, though.
|
|
|
|
HseResponseInfo.HeaderInfo.fKeepConn = TRUE;
|
|
}
|
|
|
|
if (pTemplate)
|
|
pBufferSet->SetTemplate(pTemplate);
|
|
|
|
if (fHeadRequest)
|
|
goto LSendResponse;
|
|
|
|
hr = pBufferSet->PResponseBuffer()->Write(pchBuf,
|
|
cchBuf,
|
|
FALSE, // don't chunk the data
|
|
!!pTemplate); // is data in the template?
|
|
|
|
if (FAILED(hr))
|
|
goto LExit;
|
|
|
|
CResponseVector * pResponseVector = pBufferSet->PResponseBuffer()->GetResponseVector();
|
|
pResponseVector->GetVectors( &WsabuffVectors);
|
|
pWsabuffVectors = &WsabuffVectors;
|
|
|
|
|
|
LSendResponse:
|
|
|
|
if (!pIReq->SendClientResponse(CResponseBufferSet::SendResponseCompletion,
|
|
pBufferSet,
|
|
&HseResponseInfo,
|
|
pWsabuffVectors))
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
|
|
LExit:
|
|
|
|
// if these values are set, then SendClientResponse didn't take
|
|
// ownership of this memory and it needs to be deleted here
|
|
|
|
if (HseResponseInfo.HeaderInfo.pszStatus)
|
|
free((PVOID)HseResponseInfo.HeaderInfo.pszStatus);
|
|
|
|
if (HseResponseInfo.HeaderInfo.pszHeader)
|
|
free((PVOID)HseResponseInfo.HeaderInfo.pszHeader);
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
delete pBufferSet;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::SyncWriteFile
|
|
|
|
Static method.
|
|
Sends entire response as a content of the file
|
|
|
|
Parameters:
|
|
pIReq CIsapiReqInfo to send
|
|
szFile file name
|
|
szMimeType mime type
|
|
szStatus HTTP status
|
|
szExtraHeaders additional HTTP headers to send
|
|
|
|
Returns:
|
|
HRESULT
|
|
===================================================================*/
|
|
HRESULT CResponse::SyncWriteFile
|
|
(
|
|
CIsapiReqInfo *pIReq,
|
|
TCHAR *szFile,
|
|
char *szMimeType,
|
|
char *szStatus,
|
|
char *szExtraHeaders
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
CStaticWriteFileCB *pCB = NULL;
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
DWORD dwSize;
|
|
HSE_SEND_ENTIRE_RESPONSE_INFO hseResponseInfo;
|
|
BOOL fHeadersWritten = pIReq->FHeadersWritten();
|
|
BOOL fHeadRequest = (stricmp(pIReq->QueryPszMethod(), "HEAD") == 0);
|
|
|
|
if (fHeadersWritten && fHeadRequest)
|
|
return S_OK;
|
|
|
|
ZeroMemory( &hseResponseInfo, sizeof(HSE_SEND_ENTIRE_RESPONSE_INFO));
|
|
|
|
pCB = new CStaticWriteFileCB;
|
|
|
|
if (pCB == NULL) {
|
|
hr = E_OUTOFMEMORY;
|
|
goto LExit;
|
|
}
|
|
|
|
if (szMimeType == NULL)
|
|
szMimeType = (char *)GetResponseMimeType(pIReq);
|
|
|
|
// open the file
|
|
hFile = AspCreateFile(szFile,
|
|
GENERIC_READ, // access (read-write) mode
|
|
FILE_SHARE_READ, // share mode
|
|
NULL, // pointer to security descriptor
|
|
OPEN_EXISTING, // how to create
|
|
FILE_ATTRIBUTE_NORMAL, // file attributes
|
|
NULL // handle to file with attributes to copy
|
|
);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
DBGERROR((DBG_CONTEXT, "Could not open \"%S\". Win32 Error = %u\n",
|
|
szFile, GetLastError()));
|
|
hr = E_FAIL;
|
|
goto LExit;
|
|
}
|
|
|
|
// get file size
|
|
dwSize = GetFileSize(hFile, NULL);
|
|
if (dwSize == 0 || dwSize == 0xFFFFFFFF)
|
|
{
|
|
hr = E_FAIL;
|
|
goto LExit;
|
|
}
|
|
|
|
hr = ConstructSimpleHeaders( &hseResponseInfo.HeaderInfo, dwSize, szMimeType, szStatus, szExtraHeaders);
|
|
|
|
if (FAILED(hr))
|
|
goto LExit;
|
|
|
|
if (fHeadRequest)
|
|
goto LSendResponse;
|
|
|
|
// this is our way to transfer a file handle in a WSABUF
|
|
|
|
hseResponseInfo.cWsaBuf = 0xFFFFFFFF;
|
|
hseResponseInfo.rgWsaBuf = &(pCB->m_wsaBuf);
|
|
|
|
pCB->m_wsaBuf.len = dwSize;
|
|
pCB->m_wsaBuf.buf = (char *)hFile;
|
|
|
|
// ownership of the handle has been transferred to the WSABUF
|
|
hFile = INVALID_HANDLE_VALUE;
|
|
|
|
|
|
LSendResponse:
|
|
|
|
if (!pIReq->SendClientResponse(CResponse::StaticWriteFileCompletion,
|
|
pCB,
|
|
&hseResponseInfo)) {
|
|
DWORD dwError = GetLastError();
|
|
CResponse::StaticWriteFileCompletion(pIReq, pCB, 0, dwError);
|
|
hr = HRESULT_FROM_WIN32(dwError);
|
|
|
|
// DO NOT place a goto here!!! It will mess up the handling
|
|
// of the pCB cleanup.
|
|
}
|
|
|
|
// NULL out pCB so that it won't be freed below. The async compl
|
|
// is now responsible for it via either an error above, or by
|
|
// a real completion. Note that a goto above will mess this up
|
|
// and likely cause a double free.
|
|
|
|
pCB = NULL;
|
|
|
|
LExit:
|
|
|
|
// cleanup
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
CloseHandle(hFile);
|
|
|
|
// if pCB is still non-zero, then it needs to be freed
|
|
|
|
if (pCB)
|
|
delete pCB;
|
|
|
|
return hr;
|
|
}
|
|
|
|
VOID CResponse::StaticWriteFileCompletion(CIsapiReqInfo *pIReq,
|
|
PVOID pContext,
|
|
DWORD cbIO,
|
|
DWORD dwError)
|
|
{
|
|
CStaticWriteFileCB *pCB = (CStaticWriteFileCB *)pContext;
|
|
|
|
#ifndef PERF_DISABLE
|
|
AddtoTotalByteOut(cbIO);
|
|
#endif
|
|
|
|
delete pCB;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::WriteScriptlessTemplate
|
|
|
|
Static method.
|
|
Sends entire response as a content of the [scriptless] template.
|
|
|
|
Parameters:
|
|
pIReq CIsapiReqInfo to send
|
|
pTemplate template
|
|
|
|
Returns:
|
|
HRESULT
|
|
===================================================================*/
|
|
HRESULT CResponse::WriteScriptlessTemplate
|
|
(
|
|
CIsapiReqInfo *pIReq,
|
|
CTemplate *pTemplate
|
|
)
|
|
{
|
|
char* pbHTML = NULL;
|
|
ULONG cbHTML = 0;
|
|
ULONG cbSrcOffset = 0;
|
|
char* pbIncSrcFileName = NULL;
|
|
|
|
Assert(pTemplate && pTemplate->FScriptless());
|
|
|
|
HRESULT hr = pTemplate->GetHTMLBlock(0, &pbHTML, &cbHTML, &cbSrcOffset, &pbIncSrcFileName);
|
|
|
|
if (FAILED(hr))
|
|
return hr;
|
|
if (pbHTML == NULL || cbHTML == 0)
|
|
return E_FAIL;
|
|
|
|
hr = StaticWrite(pIReq, pbHTML, cbHTML, pTemplate);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::WriteBlocksResponse
|
|
|
|
Static method.
|
|
Sends entire response as a content of a set of memory blocks.
|
|
|
|
Parameters:
|
|
pIReq CIsapiReqInfo to send
|
|
cBlocks number of blocks
|
|
pWsaBuf array of cBlocks structures with data pointers and length
|
|
cbTotal total amount of data
|
|
szMimeType mime type
|
|
szStatus HTTP status
|
|
szExtraHeaders additional HTTP headers to send
|
|
|
|
Note: we also use a WSABUF for file handles. This is encoded by cBlocks == 0xFFFFFFFF
|
|
|
|
Returns:
|
|
HRESULT
|
|
===================================================================*/
|
|
HRESULT CResponse::WriteBlocksResponse
|
|
(
|
|
CIsapiReqInfo *pIReq,
|
|
DWORD cBlocks,
|
|
LPWSABUF pWsaBuf,
|
|
DWORD cbTotal,
|
|
char *szMimeType,
|
|
char *szStatus,
|
|
char *szExtraHeaders
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
HSE_SEND_ENTIRE_RESPONSE_INFO HseResponseInfo;
|
|
WSABUF_VECTORS WsabuffVectors;
|
|
LPWSABUF_VECTORS pWsabuffVectors = NULL;
|
|
BOOL fHeadersWritten = pIReq->FHeadersWritten();
|
|
BOOL fHeadRequest = (stricmp(pIReq->QueryPszMethod(), "HEAD") == 0);
|
|
|
|
if (fHeadersWritten && fHeadRequest)
|
|
return S_OK;
|
|
|
|
ZeroMemory( &HseResponseInfo, sizeof(HSE_SEND_ENTIRE_RESPONSE_INFO));
|
|
|
|
CResponseBufferSet *pBufferSet = new CResponseBufferSet;
|
|
|
|
if (pBufferSet == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
hr = pBufferSet->Init(DEFAULT_BUFFER_LIMIT);
|
|
|
|
if (FAILED(hr))
|
|
goto LExit;
|
|
|
|
if (!fHeadersWritten) {
|
|
|
|
if (szMimeType == NULL)
|
|
szMimeType = (char *)GetResponseMimeType(pIReq);
|
|
|
|
hr = ConstructSimpleHeaders( &HseResponseInfo.HeaderInfo,
|
|
cbTotal,
|
|
szMimeType,
|
|
szStatus,
|
|
szExtraHeaders);
|
|
|
|
if (FAILED(hr))
|
|
goto LExit;
|
|
|
|
// set fKeepConn to TRUE to indicate that we do prefer that to
|
|
// keep the connection. SendClientResponse() may decide that
|
|
// it's not possible, though.
|
|
|
|
HseResponseInfo.HeaderInfo.fKeepConn = TRUE;
|
|
}
|
|
|
|
if (fHeadRequest)
|
|
goto LSendResponse;
|
|
|
|
for (DWORD i = 0; SUCCEEDED(hr) && (i < cBlocks); i++) {
|
|
|
|
hr = pBufferSet->PResponseBuffer()->Write(pWsaBuf[i].buf,
|
|
pWsaBuf[i].len,
|
|
FALSE); // dont' chunk the data
|
|
|
|
if (FAILED(hr))
|
|
goto LExit;
|
|
}
|
|
|
|
CResponseVector * pResponseVector = pBufferSet->PResponseBuffer()->GetResponseVector();
|
|
pResponseVector->GetVectors( &WsabuffVectors);
|
|
pWsabuffVectors = &WsabuffVectors;
|
|
|
|
|
|
LSendResponse:
|
|
|
|
if (!pIReq->SendClientResponse(CResponseBufferSet::SendResponseCompletion,
|
|
pBufferSet,
|
|
&HseResponseInfo,
|
|
pWsabuffVectors))
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
|
|
LExit:
|
|
|
|
// if these values are set, then SendClientResponse didn't take
|
|
// ownership of this memory and it needs to be deleted here
|
|
|
|
if (HseResponseInfo.HeaderInfo.pszStatus)
|
|
free((PVOID)HseResponseInfo.HeaderInfo.pszStatus);
|
|
|
|
if (HseResponseInfo.HeaderInfo.pszHeader)
|
|
free((PVOID)HseResponseInfo.HeaderInfo.pszHeader);
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
delete pBufferSet;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::ConstructSimpleHeaders
|
|
|
|
Static method.
|
|
|
|
Allocates and sets the Header parameters in pHeaderInfo.
|
|
|
|
NOTE - this routine allocates memory with malloc to populate the
|
|
headerinfo parameters. If this routine fails, it remains the
|
|
callers responsibility to clean up any allocated memory.
|
|
|
|
Parameters:
|
|
pHeaderInfo HSE_SEND_HEADER_EX_INFO struct to fill out
|
|
cbTotal bytes to send for ContentLength header
|
|
szMimeType MimeType
|
|
szStatus HTTP status string
|
|
szExtraHeaders other headers
|
|
|
|
Returns:
|
|
HRESULT
|
|
===================================================================*/
|
|
HRESULT CResponse::ConstructSimpleHeaders
|
|
(
|
|
LPHSE_SEND_HEADER_EX_INFO pHeaderInfo,
|
|
DWORD cbTotal,
|
|
char *szMimeType,
|
|
char *szStatus,
|
|
char *szExtraHeaders
|
|
)
|
|
{
|
|
BOOL fCacheControlPrivate = FALSE;
|
|
|
|
// defaut status
|
|
if (szStatus == NULL)
|
|
{
|
|
szStatus = (char *)s_szDefaultStatus;
|
|
fCacheControlPrivate = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fCacheControlPrivate = (strcmp(szStatus, s_szDefaultStatus) == 0);
|
|
}
|
|
|
|
// extra headers size
|
|
|
|
DWORD cbExtra = (szExtraHeaders != NULL) ? strlen(szExtraHeaders) : 0;
|
|
|
|
// send the header
|
|
|
|
char szLength[20];
|
|
ltoa(cbTotal, szLength, 10);
|
|
|
|
DWORD cchContentHeader = (DWORD)(0
|
|
+ sizeof(s_szContentTypeHeader)-1 // Content-Type:
|
|
+ strlen(szMimeType) // text/html
|
|
+ 2 // \r\n
|
|
+ cbExtra // Extra headers
|
|
+ sizeof(s_szContentLengthHeader)-1 // Content-Length:
|
|
+ strlen(szLength) // <length>
|
|
+ 4 // \r\n\r\n
|
|
+ 1); // '\0'
|
|
|
|
if (fCacheControlPrivate)
|
|
cchContentHeader += sizeof(s_szCacheControlPrivate)-1;
|
|
|
|
pHeaderInfo->pszHeader = (LPCSTR)malloc(cchContentHeader);
|
|
|
|
if (pHeaderInfo->pszHeader == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
char *szBuf = (char *)pHeaderInfo->pszHeader;
|
|
|
|
szBuf = strcpyExA(szBuf, s_szContentTypeHeader);
|
|
szBuf = strcpyExA(szBuf, szMimeType);
|
|
szBuf = strcpyExA(szBuf, "\r\n");
|
|
|
|
if (cbExtra > 0)
|
|
szBuf = strcpyExA(szBuf, szExtraHeaders);
|
|
|
|
if (fCacheControlPrivate)
|
|
szBuf = strcpyExA(szBuf, s_szCacheControlPrivate);
|
|
|
|
szBuf = strcpyExA(szBuf, s_szContentLengthHeader);
|
|
szBuf = strcpyExA(szBuf, szLength);
|
|
szBuf = strcpyExA(szBuf, "\r\n\r\n");
|
|
|
|
pHeaderInfo->pszStatus = StringDupA(szStatus);
|
|
|
|
if (pHeaderInfo->pszStatus == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
pHeaderInfo->cchStatus = strlen(szStatus);
|
|
pHeaderInfo->cchHeader = cchContentHeader;
|
|
pHeaderInfo->fKeepConn = FALSE;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/*===================================================================
|
|
CResponse::ConstructHeaders
|
|
|
|
Fill a buffer with standard HTTP headers and any user created headers.
|
|
|
|
Parameters:
|
|
pHeaderInfo - pointer to header info structure to fill with header
|
|
details and pointers to content.
|
|
pHeaders - points to a resizable buffer object, to be sized and
|
|
filled with the header content.
|
|
|
|
Returns:
|
|
HRESULT S_OK on success
|
|
E_FAIL if unable to build expires headers
|
|
E_OUTOFMEMORY if memory failure
|
|
===================================================================*/
|
|
HRESULT CResponse::ConstructHeaders
|
|
(
|
|
LPHSE_SEND_HEADER_EX_INFO pHeaderInfo
|
|
)
|
|
{
|
|
CHAR *szBuff = NULL;
|
|
DWORD cch = 0;
|
|
BOOL fContentTypeFound = FALSE;
|
|
HRESULT hr = S_OK;
|
|
|
|
// Static cookie buffer should be enough to fit:
|
|
// cookie name 20 chars
|
|
// cookie value 24 chars
|
|
// decorations 28 = strlen("Set-Cookie: C=V; secure; path=/;\r\n")
|
|
#define CCH_STATIC_COOKIE_BUF 88
|
|
char szCookieBuff[CCH_STATIC_COOKIE_BUF];
|
|
DWORD cchCookieBuff = 0;
|
|
|
|
AssertValid();
|
|
|
|
// Loop through any headers counting up the length
|
|
CHTTPHeader *pHeader = m_pData->m_pFirstHeader;
|
|
while (pHeader) {
|
|
cch += pHeader->CchLength();
|
|
pHeader = pHeader->PNext();
|
|
}
|
|
|
|
// Add the content-type tag
|
|
cch += sizeof(s_szContentTypeHeader)-1;
|
|
cch += strlen(PContentType())+2;
|
|
|
|
// Add the Character set tag
|
|
if (m_pData->m_pszCharSet) {
|
|
cch += sizeof(s_szCharSetHTML)-1;
|
|
cch += strlen(m_pData->m_pszCharSet);
|
|
}
|
|
|
|
// Add the Expires tag
|
|
if ((m_pData->m_tExpires != -1) || (m_pData->m_pszDefaultExpires != NULL))
|
|
cch += DATE_STRING_SIZE + 11; // DATE_STRING_SIZE + length("Expires: \r\n")
|
|
|
|
// Add the cookies that we will send
|
|
cch += m_pData->m_WriteCookies.QueryHeaderSize()+2;
|
|
|
|
// Account for space required by headers we always send back.
|
|
|
|
// Prepare cookie if any.
|
|
if (m_pData->m_szCookieVal) {
|
|
char *pchEnd = strcpyExA(szCookieBuff, "Set-Cookie: ");
|
|
pchEnd = strcpyExA(pchEnd, m_pData->m_pHitObj->PAppln()->GetSessionCookieName(m_pData->m_pHitObj->FSecure()));
|
|
pchEnd = strcpyExA(pchEnd, "=");
|
|
pchEnd = strcpyExA(pchEnd, m_pData->m_szCookieVal);
|
|
|
|
// If we keep secure sessions secure, and this connection is secure, add flag to cookie
|
|
if ((m_pData->m_pHitObj->QueryAppConfig()->fKeepSessionIDSecure()) &&
|
|
(m_pData->m_pHitObj->FSecure()))
|
|
{
|
|
pchEnd = strcpyExA(pchEnd,"; secure");
|
|
}
|
|
|
|
pchEnd = strcpyExA(pchEnd, "; path=/\r\n");
|
|
cchCookieBuff = strlen(szCookieBuff);
|
|
cch += cchCookieBuff;
|
|
Assert(cchCookieBuff < CCH_STATIC_COOKIE_BUF);
|
|
}
|
|
else {
|
|
szCookieBuff[0] = '\0';
|
|
cchCookieBuff = 0;
|
|
}
|
|
|
|
// Will len of cache control header
|
|
if (m_pData->m_pszCacheControl) {
|
|
cch += sizeof(s_szCacheControl)-1;
|
|
cch += strlen(m_pData->m_pszCacheControl)+2;
|
|
}
|
|
else {
|
|
cch += sizeof(s_szCacheControlPrivate)-1;
|
|
}
|
|
|
|
if (m_pData->FChunkData())
|
|
cch += sizeof(s_szTransferEncoding)-1;
|
|
|
|
// Will terminate with \r\n. Leave extra space
|
|
cch += 2;
|
|
|
|
/*
|
|
* We know how big; allocate memory and build the string.
|
|
*/
|
|
|
|
if (!(szBuff = (LPSTR)malloc(cch + 1))) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
*szBuff = '\0';
|
|
|
|
char *szTmpBuf = szBuff;
|
|
|
|
pHeader = m_pData->m_pFirstHeader;
|
|
while (pHeader) {
|
|
pHeader->Print(szTmpBuf);
|
|
szTmpBuf += pHeader->CchLength();
|
|
pHeader = pHeader->PNext();
|
|
}
|
|
|
|
// Send the content-type tag
|
|
szTmpBuf = strcpyExA(szTmpBuf, s_szContentTypeHeader);
|
|
szTmpBuf = strcpyExA(szTmpBuf, PContentType());
|
|
|
|
// Send the CharSet tag if exists
|
|
if (m_pData->m_pszCharSet) {
|
|
szTmpBuf = strcpyExA(szTmpBuf, s_szCharSetHTML);
|
|
szTmpBuf = strcpyExA(szTmpBuf, m_pData->m_pszCharSet);
|
|
}
|
|
|
|
szTmpBuf = strcpyExA(szTmpBuf, "\r\n");
|
|
|
|
// Send the Expires tag
|
|
if ((m_pData->m_tExpires != -1) || (m_pData->m_pszDefaultExpires != NULL)) {
|
|
|
|
// if the script set an expires value, than use it
|
|
if (m_pData->m_tExpires != -1) {
|
|
szTmpBuf = strcpyExA(szTmpBuf, "Expires: ");
|
|
if (FAILED(CTimeToStringGMT(&m_pData->m_tExpires, szTmpBuf))) {
|
|
hr = E_FAIL;
|
|
goto LExit;
|
|
}
|
|
szTmpBuf += strlen(szTmpBuf);
|
|
szTmpBuf = strcpyExA(szTmpBuf, "\r\n");
|
|
}
|
|
// else, use the default value in the metabase. Note that it already
|
|
// includes the Expires: prefix and \r\n suffix
|
|
else {
|
|
|
|
szTmpBuf = strcpyExA(szTmpBuf, m_pData->m_pszDefaultExpires);
|
|
}
|
|
}
|
|
|
|
// send the cookies
|
|
m_pData->m_WriteCookies.GetHeaders(szTmpBuf);
|
|
szTmpBuf += strlen(szTmpBuf);
|
|
|
|
// Send the required headers: session id cookie and cache control
|
|
szTmpBuf = strcpyExA(szTmpBuf, szCookieBuff);
|
|
|
|
// Send the cache-control tag
|
|
if (m_pData->m_pszCacheControl) {
|
|
szTmpBuf = strcpyExA(szTmpBuf, s_szCacheControl);
|
|
szTmpBuf = strcpyExA(szTmpBuf, m_pData->m_pszCacheControl);
|
|
szTmpBuf = strcpyExA(szTmpBuf, "\r\n");
|
|
}
|
|
else {
|
|
szTmpBuf = strcpyExA(szTmpBuf, s_szCacheControlPrivate);
|
|
}
|
|
|
|
// Chunked encoding
|
|
if (m_pData->FChunkData())
|
|
szTmpBuf = strcpyExA(szTmpBuf, s_szTransferEncoding);
|
|
|
|
// Add trailing \r\n to terminate headers
|
|
szTmpBuf = strcpyExA(szTmpBuf, "\r\n");
|
|
|
|
Assert(strlen(szBuff) <= cch);
|
|
|
|
// Output the headers
|
|
// Failure is not a fatal error, so we still return success
|
|
// but set the m_fWriteClient flag
|
|
CHAR *szStatus = m_pData->m_pszStatus ? m_pData->m_pszStatus
|
|
: (CHAR *)s_szDefaultStatus;
|
|
|
|
BOOL fKeepConnected =
|
|
(m_pData->m_fBufferingOn && !m_pData->m_fFlushed) || m_pData->FChunkData();
|
|
|
|
DWORD cchStatus = strlen(szStatus);
|
|
DWORD cchHeader = strlen(szBuff);
|
|
|
|
pHeaderInfo->pszStatus = StringDupA(szStatus);
|
|
pHeaderInfo->cchStatus = cchStatus;
|
|
pHeaderInfo->pszHeader = szBuff;
|
|
pHeaderInfo->cchHeader = cchHeader;
|
|
pHeaderInfo->fKeepConn = fKeepConnected;
|
|
|
|
LExit:
|
|
|
|
if (FAILED(hr) && szBuff)
|
|
free(szBuff);
|
|
|
|
return hr;
|
|
}
|
|
|
|
//IResponse interface functions
|
|
|
|
|
|
/*===================================================================
|
|
CResponse::WriteResponse
|
|
|
|
Generic routine to send headers and data to the client.
|
|
|
|
If headers have not been sent out yet, they will be sent the first time
|
|
this routine is called.
|
|
|
|
If transmission of the headers or data to the client fails, we still
|
|
want the calling script to finish execution, so we will return
|
|
S_OK, but set the m_fWriteClientError flag. If we are unable
|
|
to build the needed headers we will return E_FAIL.
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
HRESULT S_OK on success
|
|
E_FAIL if unable to build expires headers
|
|
E_OUTOFMEMORY if memory failure
|
|
===================================================================*/
|
|
HRESULT CResponse::WriteResponse()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
HSE_SEND_ENTIRE_RESPONSE_INFO HseResponseInfo;
|
|
WSABUF_VECTORS WsabuffVectors;
|
|
LPWSABUF_VECTORS pWsabuffVectors = NULL;
|
|
BOOL fClearBuffers = FALSE;
|
|
CTemplate *pTemplate = m_pData->m_pBufferSet->PTemplate();
|
|
|
|
STACK_BUFFER( tempWSABUFs, 128 );
|
|
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
if (FDontWrite())
|
|
return S_OK;
|
|
|
|
ZeroMemory( &HseResponseInfo.HeaderInfo, sizeof(HSE_SEND_HEADER_EX_INFO));
|
|
|
|
if (!FHeadersWritten())
|
|
{
|
|
if (FAILED(hr = ConstructHeaders( &HseResponseInfo.HeaderInfo)))
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
HseResponseInfo.cWsaBuf = 0;
|
|
HseResponseInfo.rgWsaBuf = NULL;
|
|
|
|
if (IsHeadRequest())
|
|
{
|
|
if (FHeadersWritten())
|
|
goto LExit;
|
|
|
|
goto Send_Response;
|
|
}
|
|
|
|
BOOL fSendDebugBuffers = (m_pData->m_fClientDebugMode && m_pData->m_pBufferSet->PClientDebugBuffer());
|
|
|
|
fClearBuffers = TRUE;
|
|
|
|
// populate struct with response body buffers (and, if required, debug buffers)
|
|
|
|
if (fSendDebugBuffers)
|
|
{
|
|
CResponseBuffer * pClientDebugBuffer = m_pData->m_pBufferSet->PClientDebugBuffer();
|
|
DWORD cClientDebugBuffers = pClientDebugBuffer->CountOfBuffers();
|
|
|
|
if (cClientDebugBuffers)
|
|
{
|
|
HseResponseInfo.cWsaBuf = cClientDebugBuffers;
|
|
|
|
if (!tempWSABUFs.Resize(HseResponseInfo.cWsaBuf * sizeof(WSABUF)))
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto LExit;
|
|
}
|
|
|
|
HseResponseInfo.rgWsaBuf = static_cast<WSABUF *>(tempWSABUFs.QueryPtr());
|
|
|
|
// fill debug buffers
|
|
for ( UINT i = 0; i < cClientDebugBuffers; i++ )
|
|
{
|
|
HseResponseInfo.rgWsaBuf[i].len = pClientDebugBuffer->GetBufferSize(i);
|
|
HseResponseInfo.rgWsaBuf[i].buf = pClientDebugBuffer->GetBuffer(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
CResponseVector * pResponseVector = m_pData->m_pBufferSet->PResponseBuffer()->GetResponseVector();
|
|
pResponseVector->GetVectors( &WsabuffVectors);
|
|
pWsabuffVectors = &WsabuffVectors;
|
|
|
|
|
|
Send_Response:
|
|
|
|
BOOL fResponseSent;
|
|
|
|
// send entire response (headers and body) at once
|
|
fResponseSent = GetIReq()->SendClientResponse(CResponseBufferSet::SendResponseCompletion,
|
|
m_pData->m_pBufferSet,
|
|
&HseResponseInfo,
|
|
pWsabuffVectors);
|
|
|
|
if (fResponseSent)
|
|
{
|
|
m_pData->m_pBufferSet = new CResponseBufferSet();
|
|
|
|
if (m_pData->m_pBufferSet == NULL) {
|
|
hr = E_OUTOFMEMORY;
|
|
goto LExit;
|
|
}
|
|
|
|
hr = m_pData->m_pBufferSet->Init(m_pData->m_dwBufferLimit);
|
|
|
|
if (FAILED(hr))
|
|
goto LExit;
|
|
|
|
m_pData->m_pBufferSet->SetTemplate(pTemplate);
|
|
|
|
fClearBuffers = FALSE;
|
|
}
|
|
else
|
|
{
|
|
m_pData->m_fWriteClientError = TRUE;
|
|
}
|
|
|
|
// we have consumed the buffered data, so clear.
|
|
if (fClearBuffers)
|
|
{
|
|
if (m_pData->m_pBufferSet->PClientDebugBuffer())
|
|
{
|
|
m_pData->m_pBufferSet->PClientDebugBuffer()->Clear();
|
|
}
|
|
|
|
m_pData->m_pBufferSet->PResponseBuffer()->Clear();
|
|
}
|
|
|
|
LExit:
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
if (HseResponseInfo.HeaderInfo.pszStatus)
|
|
free((PVOID)HseResponseInfo.HeaderInfo.pszStatus);
|
|
|
|
if (HseResponseInfo.HeaderInfo.pszHeader)
|
|
free((PVOID)HseResponseInfo.HeaderInfo.pszHeader);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::Write
|
|
|
|
Writes a string to the client.
|
|
|
|
It accepts a variant as an argument and attempts to coerce that
|
|
variant into a BSTR. We convert that BSTR into an ANSI string,
|
|
and then hand that ANSI string to Response::WriteSz which sends
|
|
it back to the client.
|
|
|
|
Normally a VT_NULL variant cannot be coerced to a BSTR, but we want
|
|
VT_NULL to be a valid input, therefore we explictly handle the case of
|
|
variants of type VT_NULL. If the type of the input variant is VT_NULL
|
|
we return S_OK, but don't send anything back to the client.
|
|
|
|
If we are handed a VT_DISPATCH variant, we resolve it by repeatedly calling
|
|
Dispatch on the associated pdispVal, until we get back a variant that is not
|
|
a VT_DISPATCH. VariantChangeType would ordinarily handle this for us, but the
|
|
final resulting variant might be a VT_NULL which VariantChangeType would not
|
|
coerce into a BSTR. This is why we have to handle walking down the VT_DISPATC
|
|
variants outselves.
|
|
|
|
Parameters:
|
|
VARIANT varInput, value: the variant to be converted to string
|
|
and written to the client
|
|
|
|
Returns:
|
|
S_OK on success. E_FAIL on failure.
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::Write(VARIANT varInput)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
DWORD cch;
|
|
LPSTR szT;
|
|
BSTR bstr;
|
|
VARIANT varResolved;
|
|
|
|
static char szTrue[MAX_MESSAGE_LENGTH];
|
|
static char szFalse[MAX_MESSAGE_LENGTH];
|
|
|
|
AssertValid();
|
|
|
|
// If we've already had an error writing to the client
|
|
// there is no point in proceding, so we immediately return
|
|
// with no error
|
|
if (FDontWrite())
|
|
goto lRet2;
|
|
|
|
// If already BSTR (directly or as VARIANT by ref)
|
|
bstr = VariantGetBSTR(&varInput);
|
|
if (bstr != NULL)
|
|
{
|
|
hr = WriteBSTR(bstr);
|
|
goto lRet2;
|
|
}
|
|
|
|
// If the variant passed in is a VT_DISPATCH, get its default property
|
|
if (FAILED(hr = VariantResolveDispatch(&varResolved, &varInput, IID_IResponse, IDE_RESPONSE)))
|
|
goto lRet2;
|
|
|
|
// Check if the variant in is VT_NULL
|
|
if (V_VT(&varResolved) == VT_NULL)
|
|
goto lRet; // S_OK, but don't send anything to the client
|
|
|
|
// Check if the variant in is VT_BOOL
|
|
if(V_VT(&varResolved) == VT_BOOL)
|
|
{
|
|
if (V_BOOL(&varResolved) == VARIANT_TRUE)
|
|
{
|
|
if (szTrue[0] == '\0')
|
|
cch = CchLoadStringOfId(IDS_TRUE, szTrue, MAX_MESSAGE_LENGTH);
|
|
szT = szTrue;
|
|
}
|
|
else
|
|
{
|
|
if(szFalse[0] == '\0')
|
|
cch = CchLoadStringOfId(IDS_FALSE, szFalse, MAX_MESSAGE_LENGTH);
|
|
szT = szFalse;
|
|
}
|
|
cch = strlen(szT);
|
|
if (FAILED(hr = WriteSz(szT, cch)))
|
|
{
|
|
if (E_OUTOFMEMORY == hr)
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
else
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_UNEXPECTED);
|
|
}
|
|
goto lRet;
|
|
}
|
|
|
|
// Coerce the variant into a bstr if necessary
|
|
if (V_VT(&varResolved) != VT_BSTR)
|
|
{
|
|
if (FAILED(hr = VariantChangeTypeEx(&varResolved, &varResolved, m_pData->m_pHitObj->GetLCID(), 0, VT_BSTR)))
|
|
{
|
|
switch (GetScode(hr))
|
|
{
|
|
case E_OUTOFMEMORY:
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
break;
|
|
case DISP_E_OVERFLOW:
|
|
hr = E_FAIL;
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_UNABLE_TO_CONVERT);
|
|
break;
|
|
case DISP_E_TYPEMISMATCH:
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_TYPE_MISMATCH);
|
|
break;
|
|
default:
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_UNEXPECTED);
|
|
}
|
|
goto lRet;
|
|
}
|
|
}
|
|
|
|
hr = WriteBSTR(V_BSTR(&varResolved));
|
|
|
|
lRet:
|
|
#ifdef DBG
|
|
hr =
|
|
#endif // DBG
|
|
VariantClear(&varResolved);
|
|
Assert(SUCCEEDED(hr));
|
|
lRet2:
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::BinaryWrite
|
|
|
|
Function called from DispInvoke to invoke the BinaryWrite method.
|
|
|
|
Parameters:
|
|
varInput Variant which must resolve to an array of unsigned bytes
|
|
|
|
Returns:
|
|
S_OK on success. E_FAIL on failure.
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::BinaryWrite(VARIANT varInput)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
DWORD nDim = 0;
|
|
long lLBound = 0;
|
|
long lUBound = 0;
|
|
void *lpData = NULL;
|
|
DWORD cch = 0;
|
|
VARIANT varResolved;
|
|
SAFEARRAY* pvarBuffer;
|
|
|
|
AssertValid();
|
|
|
|
// If we've already had an error writing to the client
|
|
// there is no point in proceding, so we immediately return
|
|
// with no error
|
|
if (FDontWrite())
|
|
goto lRet2;
|
|
|
|
// De-reference and de-dispatch the variant
|
|
if (FAILED(hr = VariantResolveDispatch(&varResolved, &varInput, IID_IResponse, IDE_RESPONSE)))
|
|
goto lRet2;
|
|
|
|
// Coerce the result into an array of VT_UI1 if needed
|
|
if (V_VT(&varResolved) != (VT_ARRAY|VT_UI1))
|
|
{
|
|
if (FAILED(hr = VariantChangeType(&varResolved, &varResolved, 0, VT_ARRAY|VT_UI1)))
|
|
{
|
|
switch (GetScode(hr))
|
|
{
|
|
case E_OUTOFMEMORY:
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
break;
|
|
case DISP_E_OVERFLOW:
|
|
hr = E_FAIL;
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_UNABLE_TO_CONVERT);
|
|
break;
|
|
case DISP_E_TYPEMISMATCH:
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_TYPE_MISMATCH);
|
|
break;
|
|
default:
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_UNEXPECTED);
|
|
}
|
|
goto lRet;
|
|
}
|
|
}
|
|
|
|
// We got here, so we must have a variant containing a safe array of UI1 in varResolved
|
|
pvarBuffer = V_ARRAY(&varResolved);
|
|
|
|
nDim = SafeArrayGetDim(pvarBuffer);
|
|
if (nDim != 1)
|
|
{
|
|
hr = E_INVALIDARG;
|
|
goto lRet;
|
|
}
|
|
|
|
if (FAILED(SafeArrayGetLBound(pvarBuffer, 1, &lLBound)))
|
|
{
|
|
hr = E_INVALIDARG;
|
|
goto lRet;
|
|
}
|
|
|
|
if (FAILED(SafeArrayGetUBound(pvarBuffer, 1, &lUBound)))
|
|
{
|
|
hr = E_INVALIDARG;
|
|
goto lRet;
|
|
}
|
|
|
|
if (FAILED(SafeArrayAccessData(pvarBuffer, &lpData)))
|
|
{
|
|
hr = E_INVALIDARG;
|
|
goto lRet;
|
|
}
|
|
cch = lUBound - lLBound + 1;
|
|
|
|
hr = m_pData->m_pBufferSet->PResponseBuffer()->Write((char *) lpData,
|
|
cch,
|
|
m_pData->FChunkData());
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
// We can't buffer the ouput, so quit
|
|
SafeArrayUnaccessData(pvarBuffer);
|
|
if (E_OUTOFMEMORY == hr)
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
else
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_UNEXPECTED);
|
|
goto lRet;
|
|
}
|
|
|
|
else if (!m_pData->m_fBufferingOn)
|
|
{
|
|
// Buffering is off, Flush the data we just added to the response buffer
|
|
|
|
hr = WriteResponse();
|
|
if (FAILED(hr)) {
|
|
SafeArrayUnaccessData(pvarBuffer);
|
|
goto lRet;
|
|
}
|
|
}
|
|
|
|
hr = SafeArrayUnaccessData(pvarBuffer);
|
|
|
|
lRet:
|
|
VariantClear(&varResolved);
|
|
lRet2:
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::WriteSz
|
|
|
|
Support routine for the Write method to write the string. Unlike
|
|
CResponse::Write(), this routine takes an Ascii string, and is
|
|
not intended to be exposed as a method
|
|
|
|
Parameters:
|
|
sz - String to write as an Ascii string
|
|
cch - Length of string to write
|
|
|
|
Returns:
|
|
S_OK on success.
|
|
|
|
===================================================================*/
|
|
HRESULT CResponse::WriteSz(CHAR *sz, DWORD cch)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
AssertValid();
|
|
|
|
// If we've already had an error writing to the client
|
|
// there is no point in proceding, so we immediately return
|
|
// with no error
|
|
if (FDontWrite())
|
|
goto lRet;
|
|
|
|
// don't bother with zero byte writes...
|
|
|
|
if (cch == 0)
|
|
goto lRet;
|
|
|
|
hr = m_pData->m_pBufferSet->PResponseBuffer()->Write(sz,
|
|
cch,
|
|
m_pData->FChunkData());
|
|
|
|
if (SUCCEEDED(hr) && !m_pData->m_fBufferingOn)
|
|
{
|
|
hr = WriteResponse();
|
|
}
|
|
|
|
lRet:
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::WriteBSTR
|
|
|
|
Support routine for the Write method
|
|
|
|
Parameters:
|
|
BSTR - String to write as an Ascii string
|
|
|
|
Returns:
|
|
S_OK on success.
|
|
===================================================================*/
|
|
HRESULT CResponse::WriteBSTR(BSTR bstr)
|
|
{
|
|
CWCharToMBCS convStr;
|
|
HRESULT hr = NO_ERROR;
|
|
|
|
if (FAILED(hr = convStr.Init(bstr, m_pData->m_pHitObj->GetCodePage())));
|
|
|
|
|
|
else hr = WriteSz(convStr.GetString(), convStr.GetStringLen());
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
if (E_OUTOFMEMORY == hr)
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
else
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_UNEXPECTED);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::WriteBlock
|
|
|
|
Function called from DispInvoke to invoke the WriteBlock method.
|
|
|
|
Parameters:
|
|
Identifier for the HTML block
|
|
|
|
Returns:
|
|
S_OK on success. E_FAIL on failure.
|
|
|
|
===================================================================*/
|
|
HRESULT CResponse::WriteBlock(short iBlockNumber)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
char* pbHTML = NULL;
|
|
ULONG cbHTML = 0;
|
|
ULONG cbSrcOffset = 0;
|
|
char* pbIncSrcFileName = NULL;
|
|
|
|
AssertValid();
|
|
|
|
// If we've already had an error writing to the client
|
|
// there is no point in proceding, so we immediately return
|
|
// with no error
|
|
if (FDontWrite())
|
|
goto lRet;
|
|
|
|
// bail out (and assert) if template is null
|
|
Assert(m_pData->m_pBufferSet->PTemplate() != NULL);
|
|
if (m_pData->m_pBufferSet->PTemplate() == NULL)
|
|
{
|
|
hr = E_FAIL;
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_UNEXPECTED);
|
|
goto lRet;
|
|
}
|
|
|
|
/*
|
|
get ptr and byte count for html block from template
|
|
NOTE: by design, this public template call cannot fail because we give it a block id
|
|
generated during template compilation (instead we assert within and after the call)
|
|
|
|
I added the return HRESULT to catch for invalid user access of this method, if a user
|
|
attempts to access this method and passes an invalid array offset it will return the
|
|
error IDE_BAD_ARRAY_INDEX, this really should not happen except for the case of a user
|
|
attempting to access this hidden method.
|
|
*/
|
|
|
|
|
|
hr = m_pData->m_pBufferSet->PTemplate()->GetHTMLBlock(iBlockNumber, &pbHTML, &cbHTML, &cbSrcOffset, &pbIncSrcFileName);
|
|
if ( hr != S_OK )
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_BAD_ARRAY_INDEX);
|
|
goto lRet;
|
|
}
|
|
|
|
Assert(pbHTML);
|
|
Assert(cbHTML > 0);
|
|
|
|
if (m_pData->m_fBufferingOn)
|
|
{
|
|
// Buffering is on
|
|
hr = S_OK;
|
|
|
|
// Take care of Client Debugger issues
|
|
if (m_pData->m_fClientDebugMode && m_pData->m_pBufferSet->PClientDebugBuffer())
|
|
{
|
|
if (cbSrcOffset) // only if source info is known
|
|
{
|
|
// Write a METADATA line corresponding to this block
|
|
// into ClientDebugBuffer
|
|
ULONG cbPos = m_pData->m_pBufferSet->PResponseBuffer()->BytesBuffered() + 1;
|
|
ULONG cbLen = cbHTML;
|
|
|
|
hr = m_pData->m_pBufferSet->PClientDebugBuffer()->AppendRecord(
|
|
cbPos, cbLen, cbSrcOffset, pbIncSrcFileName);
|
|
}
|
|
}
|
|
}
|
|
// Write the actual data
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_pData->m_pBufferSet->PResponseBuffer()->Write(pbHTML,
|
|
cbHTML,
|
|
m_pData->FChunkData(),
|
|
TRUE); // data's in template
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
if (E_OUTOFMEMORY == hr)
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
else
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_UNEXPECTED);
|
|
}
|
|
|
|
if (SUCCEEDED(hr) && !m_pData->m_fBufferingOn)
|
|
hr = WriteResponse();
|
|
|
|
lRet:
|
|
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::GetClientVersion
|
|
|
|
Uses GetServerVariable to determine the HTTP version of the client.
|
|
Borrowed from simiarl code in w3 server Httpreq.cxx, OnVersion()
|
|
|
|
Parameters:
|
|
|
|
None
|
|
|
|
Returns:
|
|
None
|
|
===================================================================*/
|
|
VOID CResponse::GetClientVerison()
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return;
|
|
|
|
if (m_pData->m_pIReq)
|
|
{
|
|
m_pData->m_dwVersionMajor = (BYTE)m_pData->m_pIReq->QueryHttpVersionMajor();
|
|
m_pData->m_dwVersionMinor = (BYTE)m_pData->m_pIReq->QueryHttpVersionMinor();
|
|
}
|
|
else
|
|
{
|
|
// Assume version 0.9
|
|
m_pData->m_dwVersionMajor = 0;
|
|
m_pData->m_dwVersionMinor = 9;
|
|
}
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::AppendHeader
|
|
|
|
Functions to add headers of different kind
|
|
(hardcoded and user-supplied)
|
|
|
|
Parameters:
|
|
Name, Value
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
HRESULT CResponse::AppendHeader(BSTR wszName, BSTR wszValue)
|
|
{
|
|
CHTTPHeader *pHeader = new CHTTPHeader;
|
|
if (!pHeader)
|
|
return E_OUTOFMEMORY;
|
|
if (FAILED(pHeader->InitHeader(wszName, wszValue,m_pData->m_pHitObj->GetCodePage())))
|
|
{
|
|
delete pHeader;
|
|
return E_FAIL;
|
|
}
|
|
m_pData->AppendHeaderToList(pHeader);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CResponse::AppendHeader(char *szName, BSTR wszValue)
|
|
{
|
|
CHTTPHeader *pHeader = new CHTTPHeader;
|
|
if (!pHeader)
|
|
return E_OUTOFMEMORY;
|
|
if (FAILED(pHeader->InitHeader(szName, wszValue,m_pData->m_pHitObj->GetCodePage())))
|
|
{
|
|
delete pHeader;
|
|
return E_FAIL;
|
|
}
|
|
m_pData->AppendHeaderToList(pHeader);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CResponse::AppendHeader(char *szName, char *szValue, BOOL fCopyValue)
|
|
{
|
|
CHTTPHeader *pHeader = new CHTTPHeader;
|
|
if (!pHeader)
|
|
return E_OUTOFMEMORY;
|
|
if (FAILED(pHeader->InitHeader(szName, szValue, fCopyValue)))
|
|
{
|
|
delete pHeader;
|
|
return E_FAIL;
|
|
}
|
|
m_pData->AppendHeaderToList(pHeader);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CResponse::AppendHeader(char *szName, long lValue)
|
|
{
|
|
CHTTPHeader *pHeader = new CHTTPHeader;
|
|
if (!pHeader)
|
|
return E_OUTOFMEMORY;
|
|
if (FAILED(pHeader->InitHeader(szName, lValue)))
|
|
{
|
|
delete pHeader;
|
|
return E_FAIL;
|
|
}
|
|
m_pData->AppendHeaderToList(pHeader);
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::get_ContentType
|
|
|
|
Functions called from DispInvoke to return the ContentType property.
|
|
|
|
Parameters:
|
|
pbstrContentTypeRet BSTR FAR *, return value: pointer to the ContentType as a string
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::get_ContentType
|
|
(
|
|
BSTR FAR * pbstrContentTypeRet
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
hr = SysAllocStringFromSz((char *)PContentType(), 0, pbstrContentTypeRet);
|
|
if (FAILED(hr))
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
hr = E_FAIL;
|
|
}
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::put_ContentType
|
|
|
|
Functions called from DispInvoke to set the ContentType property.
|
|
|
|
Parameters:
|
|
bstrContentType BSTR, value: the ContentType as a string
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::put_ContentType
|
|
(
|
|
BSTR bstrContentType
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
CWCharToMBCS convStr;
|
|
|
|
if (FHeadersWritten())
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_HEADERS_WRITTEN);
|
|
hr = E_FAIL;
|
|
goto lRet;
|
|
}
|
|
|
|
if (m_pData->m_pszContentType) {
|
|
free(m_pData->m_pszContentType);
|
|
m_pData->m_pszContentType = NULL;
|
|
}
|
|
|
|
if (FAILED(hr = convStr.Init(bstrContentType)));
|
|
|
|
else if ((m_pData->m_pszContentType = convStr.GetString(TRUE)) == NULL) {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
lRet:
|
|
if (hr == E_OUTOFMEMORY) {
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
SetLastError((DWORD)E_OUTOFMEMORY);
|
|
}
|
|
return(hr);
|
|
}
|
|
|
|
|
|
/*===================================================================
|
|
CResponse::get_Status
|
|
|
|
Function called from DispInvoke to return the Status property.
|
|
|
|
Parameters:
|
|
pbstrStatusRet BSTR FAR *, return value: pointer to the Status as a string
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::get_Status
|
|
(
|
|
BSTR FAR * pbstrStatusRet
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
if (m_pData->m_pszStatus)
|
|
hr = SysAllocStringFromSz(m_pData->m_pszStatus, 0, pbstrStatusRet);
|
|
else
|
|
hr = SysAllocStringFromSz((CHAR *)s_szDefaultStatus, 0, pbstrStatusRet);
|
|
if (FAILED(hr))
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
hr = E_FAIL;
|
|
}
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::put_Status
|
|
|
|
Function called from DispInvoke to set the ContentType property.
|
|
|
|
Parameters:
|
|
bstrStatus BSTR, value: the status as a string
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::put_Status
|
|
(
|
|
BSTR bstrStatus
|
|
)
|
|
{
|
|
DWORD dwStatus = 200;
|
|
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
CWCharToMBCS convStr;
|
|
|
|
if (FHeadersWritten())
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_HEADERS_WRITTEN);
|
|
hr = E_FAIL;
|
|
goto lRet;
|
|
}
|
|
|
|
if (m_pData->m_pszStatus) {
|
|
free(m_pData->m_pszStatus);
|
|
m_pData->m_pszStatus = NULL;
|
|
}
|
|
|
|
if (FAILED(hr = convStr.Init(bstrStatus)));
|
|
|
|
else if ((m_pData->m_pszStatus = convStr.GetString(TRUE)) == NULL) {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else {
|
|
dwStatus = atol(m_pData->m_pszStatus);
|
|
GetIReq()->SetDwHttpStatusCode(dwStatus);
|
|
}
|
|
|
|
lRet:
|
|
if (hr == E_OUTOFMEMORY) {
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
SetLastError((DWORD)E_OUTOFMEMORY);
|
|
}
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::get_Expires
|
|
|
|
Function called from DispInvoke to return the Expires property.
|
|
|
|
Parameters:
|
|
plExpiresTimeRet long *, return value: pointer to number of minutes until response expires
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::get_Expires
|
|
(
|
|
VARIANT * pvarExpiresTimeRet
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
// return early if we can
|
|
//
|
|
if (m_pData->m_tExpires == -1)
|
|
{
|
|
V_VT(pvarExpiresTimeRet) = VT_NULL;
|
|
return S_OK;
|
|
}
|
|
|
|
// get the current time
|
|
//
|
|
time_t tNow;
|
|
time(&tNow);
|
|
|
|
// get the time difference and round to the nearest minute
|
|
//
|
|
V_VT(pvarExpiresTimeRet) = VT_I4;
|
|
V_I4(pvarExpiresTimeRet) = long((difftime(m_pData->m_tExpires, tNow) / 60) + 0.5);
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::put_Expires
|
|
|
|
Functions called from DispInvoke to set the expires property.
|
|
|
|
Parameters:
|
|
iValue int, value: the number of minutes until response should expire
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::put_Expires
|
|
(
|
|
long lExpiresMinutes
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
if (FHeadersWritten())
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_HEADERS_WRITTEN);
|
|
return E_FAIL;
|
|
}
|
|
|
|
// get the current time
|
|
//
|
|
time_t tNow;
|
|
time(&tNow);
|
|
time_t tRelativeTime;
|
|
// add the number of minuites. (must convert to seconds first)
|
|
//
|
|
tRelativeTime = lExpiresMinutes * 60;
|
|
if ((lExpiresMinutes < 0 && tRelativeTime > 0)
|
|
|| (lExpiresMinutes > 0 && tRelativeTime < 0))
|
|
{
|
|
// overflow, tRelativeTime could be a small positive integer if lExpiresMinutes is
|
|
// some value like 0x80000010
|
|
// tNow will be overflowed if tRelativeTime is negative
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_COOKIE_BAD_EXPIRATION);
|
|
return E_FAIL;
|
|
}
|
|
|
|
tNow += tRelativeTime;
|
|
|
|
// Store the date if
|
|
// a. No date was stored previously
|
|
// b. This date comes before the previously set date.
|
|
//
|
|
if (m_pData->m_tExpires == -1 || tNow < m_pData->m_tExpires)
|
|
{
|
|
struct tm *ptmGMT = gmtime(&tNow);
|
|
if (ptmGMT == NULL)
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_COOKIE_BAD_EXPIRATION);
|
|
return E_FAIL;
|
|
}
|
|
|
|
m_pData->m_tExpires = tNow;
|
|
}
|
|
// convert time to GMT
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::get_ExpiresAbsolute
|
|
|
|
Function called from DispInvoke to return the ExpiresAbsolute property.
|
|
|
|
Parameters:
|
|
pbstrTimeRet BSTR *, return value: pointer to string that will contain
|
|
the time response should expire
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::get_ExpiresAbsolute
|
|
(
|
|
VARIANT *pvarTimeRet
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
V_VT(pvarTimeRet) = VT_DATE;
|
|
CTimeToVariantDate(&m_pData->m_tExpires, &V_DATE(pvarTimeRet));
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::put_ExpiresAbsolute
|
|
|
|
Function called from DispInvoke to set the ExpiresAbsolute property.
|
|
|
|
Parameters:
|
|
pbstrTime BSTR, value: time response should expire
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::put_ExpiresAbsolute
|
|
(
|
|
DATE dtExpires
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
if (FHeadersWritten())
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_HEADERS_WRITTEN);
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (int(dtExpires) == 0) // time specified but no date (assume today)
|
|
{
|
|
time_t tToday; // get the date and time now
|
|
DATE dtToday;
|
|
|
|
time(&tToday);
|
|
struct tm *tmToday = localtime(&tToday);
|
|
|
|
tmToday->tm_hour = tmToday->tm_min = tmToday->tm_sec = 0; // reset to midnight
|
|
tToday = mktime(tmToday);
|
|
|
|
if (FAILED(CTimeToVariantDate(&tToday, &dtToday)))
|
|
return E_FAIL;
|
|
|
|
dtExpires += dtToday;
|
|
}
|
|
|
|
time_t tExpires;
|
|
if (FAILED(VariantDateToCTime(dtExpires, &tExpires)))
|
|
{
|
|
ExceptionId(IID_IWriteCookie, IDE_RESPONSE, IDE_COOKIE_BAD_EXPIRATION);
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (m_pData->m_tExpires == -1 || tExpires < m_pData->m_tExpires)
|
|
{
|
|
m_pData->m_tExpires = tExpires;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::put_Buffer
|
|
|
|
Function called from DispInvoke to set the Buffer property.
|
|
|
|
Parameters:
|
|
fIsBuffering VARIANT_BOOL, if true turn on buffering of HTML output
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
Side Effects:
|
|
Turning buffering on will cause memory to be allocated.
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::put_Buffer
|
|
(
|
|
VARIANT_BOOL fIsBuffering
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
// Assume TRUE if not 0
|
|
if (fIsBuffering != VARIANT_FALSE)
|
|
fIsBuffering = VARIANT_TRUE;
|
|
|
|
// Ignore no change requests
|
|
if ((fIsBuffering == VARIANT_TRUE) && m_pData->m_fBufferingOn)
|
|
return S_OK;
|
|
if ((fIsBuffering == VARIANT_FALSE) && !m_pData->m_fBufferingOn)
|
|
return S_OK;
|
|
|
|
// Ignore if change is not allowed to change (Client Dedug)
|
|
if (m_pData->m_fClientDebugMode)
|
|
return S_OK;
|
|
|
|
// Set the new value (error if cannot change)
|
|
|
|
if (fIsBuffering == VARIANT_TRUE)
|
|
{
|
|
if (FHeadersWritten())
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_HEADERS_WRITTEN);
|
|
return E_FAIL;
|
|
}
|
|
|
|
m_pData->m_fBufferingOn = TRUE;
|
|
}
|
|
else // if (fIsBuffering == VARIANT_FALSE)
|
|
{
|
|
if ((m_pData->m_pBufferSet->PResponseBuffer()->BytesBuffered() > 0) ||
|
|
FHeadersWritten())
|
|
{
|
|
// If we already buffered some output it is too late to go back
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_CANT_STOP_BUFFER);
|
|
return E_FAIL;
|
|
}
|
|
|
|
m_pData->m_fBufferingOn = FALSE;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::get_Buffer
|
|
|
|
Function called from DispInvoke to get the Buffer property.
|
|
|
|
Parameters:
|
|
fIsBuffering VARIANT_BOOL, value: if true turn buffering of HTML output
|
|
is turned on
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
Side Effects:
|
|
None
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::get_Buffer
|
|
(
|
|
VARIANT_BOOL *fIsBuffering
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pData->m_fBufferingOn)
|
|
*fIsBuffering = VARIANT_TRUE;
|
|
else
|
|
*fIsBuffering = VARIANT_FALSE;
|
|
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::Redirect
|
|
|
|
Function called from DispInvoke to invoke the Redirect method.
|
|
|
|
Parameters:
|
|
bstrURL Unicode BSTR Value: URL to rediect to
|
|
|
|
Returns:
|
|
S_OK on success. E_FAIL on failure.
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::Redirect(BSTR bstrURL)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
AssertValid();
|
|
|
|
HRESULT hr = S_OK;
|
|
DWORD cch = 0;
|
|
DWORD cchURL = 0;
|
|
DWORD cchMessage = 0;
|
|
DWORD cchEncodedURL;
|
|
DWORD cchHtmlEncodedURL;
|
|
PSZ szURL = NULL;
|
|
PSZ szMessage = NULL;
|
|
PSZ pszEncodedURL = NULL;
|
|
PSZ pszURL = NULL;
|
|
CWCharToMBCS convURL;
|
|
|
|
STACK_BUFFER( tempURL, 256 );
|
|
STACK_BUFFER( tempMessage, 256 + 512 );
|
|
|
|
|
|
// Insist that we have a non-zero length URL
|
|
if (bstrURL)
|
|
cchURL = wcslen(bstrURL);
|
|
|
|
if (cchURL == 0)
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_NO_URL);
|
|
hr = E_FAIL;
|
|
goto lRet;
|
|
}
|
|
|
|
// Check that we haven't already passed data back to the client
|
|
if (FHeadersWritten())
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_HEADERS_WRITTEN);
|
|
hr = E_FAIL;
|
|
goto lRet;
|
|
}
|
|
|
|
// If buffering is on, clear any pending output
|
|
if (m_pData->m_fBufferingOn)
|
|
Clear();
|
|
|
|
// turn buffering on for this response.
|
|
m_pData->m_fBufferingOn = TRUE;
|
|
|
|
if (FAILED(hr = convURL.Init(bstrURL, m_pData->m_pHitObj->GetCodePage())))
|
|
{
|
|
if (hr == E_OUTOFMEMORY)
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
goto lRet;
|
|
}
|
|
|
|
pszURL = convURL.GetString();
|
|
|
|
cchEncodedURL = URLPathEncodeLen(pszURL);
|
|
|
|
if (!tempURL.Resize(cchEncodedURL))
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
goto lRet;
|
|
}
|
|
|
|
pszEncodedURL = (PSZ)tempURL.QueryPtr();
|
|
|
|
URLPathEncode(pszEncodedURL, pszURL);
|
|
|
|
// for the HTML body, further encode URL
|
|
cchHtmlEncodedURL = HTMLEncodeLen(pszEncodedURL,
|
|
m_pData->m_pHitObj->GetCodePage(),
|
|
NULL,
|
|
FALSE);
|
|
|
|
// We need to alloccate memory to build the body redirection message.
|
|
// If our memory requirement is small we allocate memory from the stack,
|
|
// otherwise we allocate from the heap.
|
|
cchMessage = cchHtmlEncodedURL;
|
|
cchMessage += 512; // Allow space for sub-strings coming from resource file.
|
|
|
|
if (!tempMessage.Resize(cchMessage))
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
hr = E_OUTOFMEMORY;
|
|
goto lRet;
|
|
}
|
|
|
|
szMessage = (PSZ)tempMessage.QueryPtr();
|
|
|
|
// Build the body redirection message
|
|
// Redirect(URL), URL must be a valid URL, that is, no DBCS string.
|
|
cch = CchLoadStringOfId(IDE_RESPONSE_REDIRECT1, szMessage, cchMessage);
|
|
if (cchHtmlEncodedURL) {
|
|
HTMLEncode(szMessage + cch,
|
|
pszEncodedURL,
|
|
m_pData->m_pHitObj->GetCodePage(),
|
|
NULL,
|
|
FALSE);
|
|
cch += cchHtmlEncodedURL-1; // get rid of the terminating \0
|
|
}
|
|
cch += CchLoadStringOfId(IDE_RESPONSE_REDIRECT2, szMessage + cch, cchMessage - cch);
|
|
|
|
// Set the status to redirect
|
|
put_Status(L"302 Object moved");
|
|
|
|
// Add the location header
|
|
AppendHeader("Location", pszEncodedURL, TRUE);
|
|
|
|
// Transmit redirect text to the client, headers will be
|
|
// sent automatically
|
|
if (FAILED(WriteSz(szMessage, cch)))
|
|
{
|
|
hr = E_FAIL;
|
|
goto lRet;
|
|
}
|
|
|
|
// No further processing of the script
|
|
End();
|
|
|
|
lRet:
|
|
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::Add
|
|
|
|
Functions called from DispInvoke to Add a header.
|
|
|
|
This is a compatibility for the ISBU controls.
|
|
|
|
Parameters:
|
|
bstrHeaderValue Unicode BSTR, value: the value of header
|
|
bstrHeaderName Unicode BSTR, value: the name of the header
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::Add
|
|
(
|
|
BSTR bstrHeaderValue,
|
|
BSTR bstrHeaderName
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
return AddHeader(bstrHeaderName, bstrHeaderValue);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::AddHeader
|
|
|
|
Functions called from DispInvoke to Add a header.
|
|
|
|
Parameters:
|
|
bstrHeaderName Unicode BSTR, value: the name of the header
|
|
bstrHeaderValue Unicode BSTR, value: the value of header
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::AddHeader
|
|
(
|
|
BSTR bstrHeaderName,
|
|
BSTR bstrHeaderValue
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
AssertValid();
|
|
|
|
if (FHeadersWritten())
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_HEADERS_WRITTEN);
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (bstrHeaderName == NULL || wcslen(bstrHeaderName) == 0)
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_EXPECTING_STR);
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (FAILED(AppendHeader(bstrHeaderName, bstrHeaderValue)))
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
SetLastError((DWORD)E_OUTOFMEMORY);
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::Clear
|
|
|
|
Erases all output waiting in buffer.
|
|
|
|
Parameters
|
|
None
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::Clear()
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pData->m_fClientDebugMode && m_pData->m_fClientDebugFlushIgnored)
|
|
{
|
|
// Clear after flush in ClientDebugMode is an error
|
|
hr = E_FAIL;
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE,
|
|
IDE_RESPONSE_CLEAR_AFTER_FLUSH_IN_DEBUG);
|
|
}
|
|
else if (!m_pData->m_fBufferingOn)
|
|
{
|
|
hr = E_FAIL;
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE,
|
|
IDE_RESPONSE_BUFFER_NOT_ON);
|
|
}
|
|
else
|
|
{
|
|
AssertValid();
|
|
hr = m_pData->m_pBufferSet->PResponseBuffer()->Clear();
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (m_pData->m_fClientDebugMode && m_pData->m_pBufferSet->PClientDebugBuffer())
|
|
hr = m_pData->m_pBufferSet->PClientDebugBuffer()->ClearAndStart();
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_UNEXPECTED);
|
|
}
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::Flush
|
|
|
|
Sends out all HTML waiting in the buffer.
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::Flush()
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
AssertValid();
|
|
|
|
if (!m_pData->m_fBufferingOn)
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_BUFFER_NOT_ON);
|
|
hr = E_FAIL;
|
|
goto lRet;
|
|
}
|
|
|
|
// Ignore Response.Flush() in Client Debug Mode
|
|
if (m_pData->m_fClientDebugMode)
|
|
{
|
|
m_pData->m_fClientDebugFlushIgnored = TRUE;
|
|
goto lRet;
|
|
}
|
|
|
|
if (FHeadersWritten() && (m_pData->BytesBuffered() == 0))
|
|
goto lRet;
|
|
|
|
// We mark this response as having had flush called so
|
|
// that we won't try to do keep-alive
|
|
m_pData->m_fFlushed = TRUE;
|
|
|
|
if (FAILED(hr = WriteResponse()))
|
|
{
|
|
if (E_OUTOFMEMORY == hr)
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
else
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_UNEXPECTED);
|
|
}
|
|
lRet:
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::FinalFlush
|
|
|
|
FinalFlush is called if a script terminates without having yet sent
|
|
the response headers. This means we can use the Content-Length headers
|
|
to increase efficiency. We add those headers, then send all headers,
|
|
and all waiting output.
|
|
|
|
Returns:
|
|
VOID
|
|
|
|
===================================================================*/
|
|
VOID CResponse::FinalFlush(HRESULT hr_Status)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return;
|
|
|
|
HRESULT hr = S_OK;
|
|
AssertValid();
|
|
|
|
if (SUCCEEDED(hr_Status) && FHeadersWritten() && (m_pData->BytesBuffered() == 0) && !m_pData->FChunkData())
|
|
goto lRet;
|
|
|
|
if (!FHeadersWritten()
|
|
&& FAILED(hr_Status)
|
|
&& (hr_Status != E_SOURCE_FILE_IS_EMPTY)
|
|
&& (m_pData->BytesBuffered() == 0))
|
|
{
|
|
// If there was an error and and nothing is buffered,
|
|
// send "server error" instead of an empty 200 OK response
|
|
Handle500Error(IDE_500_SERVER_ERROR, GetIReq());
|
|
goto lRet;
|
|
}
|
|
|
|
if (FDontWrite())
|
|
goto lRet;
|
|
|
|
if (m_pData->FChunkData())
|
|
{
|
|
// Add the closing chars when chunking
|
|
|
|
m_pData->m_pBufferSet->PResponseBuffer()->Write("0\r\n\r\n", 5, FALSE);
|
|
}
|
|
|
|
// If the headers have not yet been sent, see what needs to be added
|
|
if (!FHeadersWritten())
|
|
{
|
|
DWORD dwLength = m_pData->m_pBufferSet->PResponseBuffer()->BytesBuffered();
|
|
|
|
// If buffering, add the Content-Length header
|
|
if (m_pData->m_fBufferingOn)
|
|
{
|
|
|
|
if (m_pData->m_fClientDebugMode && m_pData->m_pBufferSet->PClientDebugBuffer())
|
|
{
|
|
// end the buffer with end of METADATA
|
|
m_pData->m_pBufferSet->PClientDebugBuffer()->End();
|
|
dwLength += m_pData->m_pBufferSet->PClientDebugBuffer()->BytesBuffered();
|
|
}
|
|
|
|
AppendHeader("Content-Length", (LONG)dwLength);
|
|
}
|
|
}
|
|
|
|
|
|
if (!m_pData->m_fBufferingOn && !m_pData->FChunkData())
|
|
{
|
|
// We mark this response as having had flush called so
|
|
// that we won't try to do keep-alive
|
|
m_pData->m_fFlushed = TRUE;
|
|
}
|
|
|
|
// Write response will send the buffered data, and the headers
|
|
// if they haven't already been sent.
|
|
// While WriteResponse can return an error, there isn't much point
|
|
// in checking the return value since FinalFlush is a void return.
|
|
|
|
WriteResponse();
|
|
|
|
lRet:
|
|
|
|
if (m_pData->m_pHitObj)
|
|
m_pData->m_pHitObj->SetDoneWithSession();
|
|
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::End
|
|
|
|
Stops all further template processing, and returns the current response
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::End()
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
if (m_pData->m_pfnGetScript != NULL && m_pData->m_pvGetScriptContext != NULL)
|
|
{
|
|
int i = 0;
|
|
|
|
CScriptEngine* pEngine;
|
|
while (NULL != (pEngine = (*m_pData->m_pfnGetScript)(i, m_pData->m_pvGetScriptContext)))
|
|
{
|
|
pEngine->InterruptScript(/*fAbnormal*/ FALSE);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
m_pData->m_fResponseAborted = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::AppendToLog
|
|
|
|
Append a string to the current log entry.
|
|
|
|
Parameters
|
|
bstrLogEntry Unicode BSTR, value: string to add to log entry
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
Side Effects:
|
|
NONE
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::AppendToLog(BSTR bstrLogEntry)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
AssertValid();
|
|
|
|
HRESULT hr = S_OK;
|
|
CWCharToMBCS convEntry;
|
|
|
|
// BUGBUG - should this be 65001?
|
|
|
|
if (FAILED(hr = convEntry.Init(bstrLogEntry, m_pData->m_pHitObj->GetCodePage())));
|
|
|
|
else hr = GetIReq()->AppendLogParameter(convEntry.GetString());
|
|
|
|
if (FAILED(hr)) {
|
|
if (hr == E_OUTOFMEMORY) {
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
}
|
|
else {
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_LOG_FAILURE);
|
|
}
|
|
}
|
|
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::get_Cookies
|
|
|
|
Return the write-only response cookie dictionary
|
|
|
|
Parameters
|
|
bstrLogEntry Unicode BSTR, value: string to add to log entry
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::get_Cookies(IRequestDictionary **ppDictReturn)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
AssertValid();
|
|
return m_pData->m_WriteCookies.QueryInterface(IID_IRequestDictionary, reinterpret_cast<void **>(ppDictReturn));
|
|
}
|
|
|
|
#ifdef DBG
|
|
/*===================================================================
|
|
CResponse::AssertValid
|
|
|
|
Test to make sure that the CResponse object is currently correctly formed
|
|
and assert if it is not.
|
|
|
|
Returns:
|
|
|
|
Side effects:
|
|
None.
|
|
===================================================================*/
|
|
VOID CResponse::AssertValid() const
|
|
{
|
|
Assert(m_fInited);
|
|
Assert(m_pData);
|
|
Assert(m_pData->m_pBufferSet->PResponseBuffer());
|
|
Assert(m_pData->m_pIReq);
|
|
}
|
|
#endif // DBG
|
|
|
|
|
|
/*===================================================================
|
|
IsHeadRequest
|
|
|
|
This function will check the REQUEST_METHOD and set a BOOL flag in
|
|
the class. If the request method is HEAD the flag will be set to
|
|
true.
|
|
|
|
Also the flag must be reset with each init/reinit call
|
|
|
|
m_IsHeadRequest == 0 // HEAD request status not set
|
|
m_IsHeadRequest == 1 // Not a HEAD request
|
|
m_IsHeadRequest == 2 // is a HEAD request
|
|
|
|
Parameters
|
|
|
|
Returns:
|
|
void
|
|
|
|
Side effects:
|
|
sets status flag m_IsHeadRequest
|
|
|
|
===================================================================*/
|
|
BOOL CResponse::IsHeadRequest(void)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return FALSE;
|
|
|
|
AssertValid();
|
|
|
|
if (m_pData->m_IsHeadRequest != 0)
|
|
return ( m_pData->m_IsHeadRequest == 2);
|
|
|
|
if (stricmp(GetIReq()->QueryPszMethod(), "HEAD") == 0 )
|
|
m_pData->m_IsHeadRequest = 2;
|
|
else
|
|
m_pData->m_IsHeadRequest = 1;
|
|
|
|
return ( m_pData->m_IsHeadRequest == 2);
|
|
}
|
|
|
|
/*===================================================================
|
|
IsClientConnected
|
|
|
|
This function will return the status of the last attempt to write to
|
|
the client. If Ok, it check the connection using new CIsapiReqInfo method
|
|
|
|
Parameters
|
|
none
|
|
|
|
Returns:
|
|
VARIANT_BOOL reflecting the status of the client connection
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::IsClientConnected(VARIANT_BOOL* fIsClientConnected)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
if (m_pData->m_fWriteClientError)
|
|
{
|
|
*fIsClientConnected = VARIANT_FALSE;
|
|
}
|
|
else
|
|
{
|
|
// assume connected
|
|
BOOL fConnected = TRUE;
|
|
|
|
// test
|
|
if (m_pData->m_pIReq)
|
|
m_pData->m_pIReq->TestConnection(&fConnected);
|
|
|
|
*fIsClientConnected = fConnected ? VARIANT_TRUE : VARIANT_FALSE;
|
|
}
|
|
|
|
return(S_OK);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::get_CharSet
|
|
|
|
Functions called from DispInvoke to return the CarSet property.
|
|
|
|
Parameters:
|
|
pbstrCharSetRet BSTR FAR *, return value: pointer to the CharSet as a string
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::get_CharSet
|
|
(
|
|
BSTR FAR * pbstrCharSetRet
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pData->m_pszCharSet)
|
|
hr = SysAllocStringFromSz(m_pData->m_pszCharSet, 0, pbstrCharSetRet);
|
|
else
|
|
*pbstrCharSetRet = NULL;
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
hr = E_FAIL;
|
|
}
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::put_CharSet
|
|
|
|
Functions called from DispInvoke to set the CharSet property.
|
|
|
|
This function takses a string, which identifies the name of the
|
|
character set of the current page, and appends the name of the
|
|
character set (for example, " ISO-LATIN-7") specified by the
|
|
charsetname to the content-type header in the response object
|
|
|
|
Notes:
|
|
|
|
* this function inserts any string in the header, whether or not
|
|
it represents a valis charcter set.
|
|
* if a single page contains multiple tags contianing response.charset,
|
|
each response.charset will replace the cahrset by the previous entry.
|
|
As a result, the charset will be set to the value specified by the
|
|
last instance of response.charset on a page.
|
|
* this command must also be invoked before the first response.write
|
|
operation unless buffering is turned on.
|
|
|
|
Parameters:
|
|
bstrContentType BSTR, value: the ContentType as a string
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::put_CharSet
|
|
(
|
|
BSTR bstrCharSet
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
CWCharToMBCS convStr;
|
|
|
|
if (FHeadersWritten())
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_HEADERS_WRITTEN);
|
|
hr = E_FAIL;
|
|
goto lRet;
|
|
}
|
|
|
|
if (m_pData->m_pszCharSet) {
|
|
free(m_pData->m_pszCharSet);
|
|
m_pData->m_pszCharSet = NULL;
|
|
}
|
|
|
|
if (FAILED(hr = convStr.Init(bstrCharSet)));
|
|
|
|
else if ((m_pData->m_pszCharSet = convStr.GetString(TRUE)) == NULL) {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
lRet:
|
|
if (hr == E_OUTOFMEMORY) {
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
SetLastError((DWORD)E_OUTOFMEMORY);
|
|
}
|
|
return(hr);
|
|
}
|
|
|
|
|
|
/*===================================================================
|
|
CResponse::Pics
|
|
|
|
Functions called from DispInvoke to Add a pics header.
|
|
|
|
Parameters:
|
|
bstrHeaderValue Unicode BSTR, value: the value of pics header
|
|
|
|
Takes a string, which is the properly formatted PICS label, and adds
|
|
the value specified by the picslabel to the pics-label field of the response header.
|
|
|
|
Note:
|
|
|
|
* this function inserts any string in the header, whether or not it
|
|
represents a valid PICS lavel.
|
|
|
|
* current implimentation is a wraper on the addheader method.
|
|
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::Pics
|
|
(
|
|
BSTR bstrHeaderValue
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
if (FHeadersWritten())
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_HEADERS_WRITTEN);
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (FAILED(AppendHeader("pics-label", bstrHeaderValue)))
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
SetLastError((DWORD)E_OUTOFMEMORY);
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::get_CacheControl
|
|
|
|
Functions called from DispInvoke to return the CacheControl property.
|
|
|
|
Parameters:
|
|
pbstrCacheControl BSTR FAR *, return value: pointer to the CacheControl as a string
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::get_CacheControl
|
|
(
|
|
BSTR FAR * pbstrCacheControl
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pData->m_pszCacheControl)
|
|
hr = SysAllocStringFromSz(m_pData->m_pszCacheControl, 0, pbstrCacheControl);
|
|
else
|
|
hr = SysAllocStringFromSz( "private", 0, pbstrCacheControl);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
hr = E_FAIL;
|
|
}
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::put_CacheControl
|
|
|
|
Functions called from DispInvoke to set the CacheControl property.
|
|
|
|
Parameters:
|
|
bstrCacheControl BSTR, value: the CacheControl as a string
|
|
|
|
Returns:
|
|
HRESULT S_OK if ok
|
|
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::put_CacheControl
|
|
(
|
|
BSTR bstrCacheControl
|
|
)
|
|
{
|
|
if (FAILED(CheckForTombstone()))
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
CWCharToMBCS convStr;
|
|
|
|
if (FHeadersWritten())
|
|
{
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_RESPONSE_HEADERS_WRITTEN);
|
|
hr = E_FAIL;
|
|
goto lRet;
|
|
}
|
|
|
|
if (m_pData->m_pszCacheControl) {
|
|
free(m_pData->m_pszCacheControl);
|
|
m_pData->m_pszCacheControl = NULL;
|
|
}
|
|
|
|
if (FAILED(hr = convStr.Init(bstrCacheControl)));
|
|
|
|
else if ((m_pData->m_pszCacheControl = convStr.GetString(TRUE)) == NULL) {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
lRet:
|
|
if (hr == E_OUTOFMEMORY) {
|
|
ExceptionId(IID_IResponse, IDE_RESPONSE, IDE_OOM);
|
|
SetLastError((DWORD)E_OUTOFMEMORY);
|
|
}
|
|
return(hr);
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::get_CodePage
|
|
|
|
Returns the current code page value for the response
|
|
|
|
Parameters:
|
|
long *plVar [out] code page value
|
|
|
|
Returns:
|
|
HRESULT S_OK on success
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::get_CodePage
|
|
(
|
|
long *plVar
|
|
)
|
|
{
|
|
Assert(m_pData);
|
|
Assert(m_pData->m_pHitObj);
|
|
|
|
*plVar = m_pData->m_pHitObj->GetCodePage();
|
|
|
|
// If code page is 0, look up default ANSI code page
|
|
if (*plVar == 0) {
|
|
*plVar = (long) GetACP();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::put_CodePage
|
|
|
|
Sets the current code page value for the response
|
|
|
|
Parameters:
|
|
long lVar code page to assign to this response
|
|
|
|
Returns:
|
|
HRESULT S_OK on success
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::put_CodePage
|
|
(
|
|
long lVar
|
|
)
|
|
{
|
|
Assert(m_pData);
|
|
Assert(m_pData->m_pHitObj);
|
|
|
|
// set code page member variable
|
|
HRESULT hr = m_pData->m_pHitObj->SetCodePage(lVar);
|
|
|
|
if (FAILED(hr)) {
|
|
ExceptionId
|
|
(
|
|
IID_IResponse,
|
|
IDE_RESPONSE,
|
|
IDE_SESSION_INVALID_CODEPAGE
|
|
);
|
|
return E_FAIL;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::get_LCID
|
|
|
|
Returns the current LCID value for the response
|
|
|
|
Parameters:
|
|
long *plVar [out] LCID value
|
|
|
|
Returns:
|
|
HRESULT S_OK on success
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::get_LCID
|
|
(
|
|
long *plVar
|
|
)
|
|
{
|
|
Assert(m_pData);
|
|
Assert(m_pData->m_pHitObj);
|
|
|
|
*plVar = m_pData->m_pHitObj->GetLCID();
|
|
|
|
// If code page is 0, look up default ANSI code page
|
|
if (*plVar == LOCALE_SYSTEM_DEFAULT) {
|
|
*plVar = (long) GetSystemDefaultLCID();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CResponse::put_LCID
|
|
|
|
Sets the current LCID value for the response
|
|
|
|
Parameters:
|
|
long lVar LCID to assign to this response
|
|
|
|
Returns:
|
|
HRESULT S_OK on success
|
|
===================================================================*/
|
|
STDMETHODIMP CResponse::put_LCID
|
|
(
|
|
long lVar
|
|
)
|
|
{
|
|
Assert(m_pData);
|
|
Assert(m_pData->m_pHitObj);
|
|
|
|
// set code page member variable
|
|
HRESULT hr = m_pData->m_pHitObj->SetLCID(lVar);
|
|
|
|
if (FAILED(hr)) {
|
|
ExceptionId
|
|
(
|
|
IID_IResponse,
|
|
IDE_RESPONSE,
|
|
IDE_TEMPLATE_BAD_LCID
|
|
);
|
|
return E_FAIL;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/*===================================================================
|
|
IStream implementation for ADO/XML
|
|
===================================================================*/
|
|
|
|
STDMETHODIMP CResponse::Read(
|
|
void *pv,
|
|
ULONG cb,
|
|
ULONG *pcbRead)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CResponse::Write(
|
|
const void *pv,
|
|
ULONG cb,
|
|
ULONG *pcbWritten)
|
|
{
|
|
if (pcbWritten != NULL)
|
|
*pcbWritten = cb;
|
|
return WriteSz((CHAR*) pv, cb);
|
|
}
|
|
|
|
STDMETHODIMP CResponse::Seek(
|
|
LARGE_INTEGER dlibMove,
|
|
DWORD dwOrigin,
|
|
ULARGE_INTEGER *plibNewPosition)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CResponse::SetSize(
|
|
ULARGE_INTEGER libNewSize)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CResponse::CopyTo(
|
|
IStream *pstm,
|
|
ULARGE_INTEGER cb,
|
|
ULARGE_INTEGER *pcbRead,
|
|
ULARGE_INTEGER *pcbWritten)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CResponse::Commit(
|
|
DWORD grfCommitFlags)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CResponse::Revert()
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CResponse::LockRegion(
|
|
ULARGE_INTEGER libOffset,
|
|
ULARGE_INTEGER cb,
|
|
DWORD dwLockType)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CResponse::UnlockRegion(
|
|
ULARGE_INTEGER libOffset,
|
|
ULARGE_INTEGER cb,
|
|
DWORD dwLockType)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CResponse::Stat(
|
|
STATSTG *pstatstg,
|
|
DWORD grfStatFlag)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CResponse::Clone(
|
|
IStream **ppstm)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|