Leaked source code of windows server 2003
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.
 
 
 
 
 
 

4864 lines
111 KiB

/*
* HttpRequest.cpp
*
* WinHttp.WinHttpRequest COM component
*
* Copyright (C) 2000 Microsoft Corporation. All rights reserved. *
*
* Much of this code was stolen from our Xml-Http friends over in
* inetcore\xml\http\xmlhttp.cxx. Thanks very much!
*
*/
#include <wininetp.h>
#include "httprequest.hxx"
#include <olectl.h>
/////////////////////////////////////////////////////////////////////////////
// private function prototypes
static void WideCharToUtf8(WCHAR * buffer, UINT cch, BYTE * bytebuffer, UINT * cb);
static HRESULT BSTRToUTF8(char ** psz, DWORD * pcbUTF8, BSTR bstr);
static HRESULT AsciiToBSTR(BSTR * pbstr, char * sz, int cch);
static HRESULT BSTRToAscii(char ** psz, BSTR bstr);
static BSTR GetBSTRFromVariant(VARIANT varVariant);
static BOOL GetBoolFromVariant(VARIANT varVariant, BOOL fDefault);
static DWORD GetDwordFromVariant(VARIANT varVariant, DWORD dwDefault);
static long GetLongFromVariant(VARIANT varVariant, long lDefault);
static HRESULT CreateVector(VARIANT * pVar, const BYTE * pData, DWORD cElems);
static HRESULT ReadFromStream(char ** ppData, ULONG * pcbData, IStream * pStm);
static void MessageLoop();
static DWORD UpdateTimeout(DWORD dwTimeout, DWORD dwStartTime);
static HRESULT FillExcepInfo(HRESULT hr, EXCEPINFO * pExcepInfo);
static BOOL IsValidVariant(VARIANT v);
static BOOL s_fWndClassRegistered;
static const char * s_szWinHttpEventMarshallerWndClass = "_WinHttpEventMarshaller";
#define SafeRelease(p) \
{ \
if (p) \
(p)->Release();\
(p) = NULL;\
}
#ifndef HWND_MESSAGE
#define HWND_MESSAGE ((HWND)-3)
#endif
inline BOOL IsValidBstr(BSTR bstr)
{
// A BSTR can be NULL, or if non-NULL, it should at least
// point to a 2-byte terminating NULL character.
return (bstr == NULL) || (!IsBadReadPtr(bstr, 2));
}
#ifndef WINHTTP_STATIC_LIBRARY
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void ** ppv)
{
if (rclsid != CLSID_WinHttpRequest)
return CLASS_E_CLASSNOTAVAILABLE;
if (riid != IID_IClassFactory || ppv == NULL)
return E_INVALIDARG;
CClassFactory * pCF = New CClassFactory();
if (pCF)
{
*ppv = static_cast<IClassFactory *>(pCF);
pCF->AddRef();
return NOERROR;
}
else
{
*ppv = NULL;
return E_OUTOFMEMORY;
}
}
CClassFactory::CClassFactory()
{
_cRefs = 0;
}
STDMETHODIMP CClassFactory::QueryInterface(REFIID riid, void ** ppvObject)
{
if (ppvObject == NULL)
return E_INVALIDARG;
if (riid == IID_IClassFactory || riid == IID_IUnknown)
{
*ppvObject = static_cast<IClassFactory *>(this);
AddRef();
return NOERROR;
}
else
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE CClassFactory::AddRef()
{
return ++_cRefs;
}
ULONG STDMETHODCALLTYPE CClassFactory::Release()
{
if (--_cRefs == 0)
{
delete this;
return 0;
}
return _cRefs;
}
STDMETHODIMP
CClassFactory::CreateInstance(IUnknown * pUnkOuter, REFIID riid, void ** ppvObject)
{
if (pUnkOuter != NULL)
return CLASS_E_NOAGGREGATION;
if (ppvObject == NULL)
return E_INVALIDARG;
return CreateHttpRequest(riid, ppvObject);
}
STDMETHODIMP
CClassFactory::LockServer(BOOL fLock)
{
return NOERROR;
}
#else
STDAPI WinHttpCreateHttpRequestComponent(REFIID riid, void ** ppvObject)
{
return CreateHttpRequest(riid, ppvObject);
}
#endif //WINHTTP_STATIC_LIBRARY
STDMETHODIMP
CreateHttpRequest(REFIID riid, void ** ppvObject)
{
CHttpRequest * pHttpRequest = New CHttpRequest();
HRESULT hr;
if (pHttpRequest)
{
hr = pHttpRequest->QueryInterface(riid, ppvObject);
if (FAILED(hr))
{
delete pHttpRequest;
}
}
else
hr = E_OUTOFMEMORY;
return hr;
}
/*
* CHttpRequest::CHttpRequest constructor
*
*/
CHttpRequest::CHttpRequest()
{
Initialize();
}
/*
* CHttpRequest::~CHttpRequest destructor
*
*/
CHttpRequest::~CHttpRequest()
{
ReleaseResources();
}
HRESULT STDMETHODCALLTYPE
CHttpRequest::QueryInterface(REFIID riid, void ** ppv)
{
HRESULT hr = NOERROR;
if (ppv == NULL)
{
hr = E_INVALIDARG;
}
else if (riid == IID_IWinHttpRequest || riid == IID_IDispatch || riid == IID_IUnknown)
{
*ppv = static_cast<IWinHttpRequest *>(this);
AddRef();
}
else if (riid == IID_IConnectionPointContainer)
{
*ppv = static_cast<IConnectionPointContainer *>(this);
AddRef();
}
else if (riid == IID_IProvideClassInfo)
{
*ppv = static_cast<IProvideClassInfo *>(static_cast<IProvideClassInfo2 *>(this));
AddRef();
}
else if (riid == IID_IProvideClassInfo2)
{
*ppv = static_cast<IProvideClassInfo2 *>(this);
AddRef();
}
else
hr = E_NOINTERFACE;
return hr;
}
ULONG STDMETHODCALLTYPE
CHttpRequest::AddRef()
{
if (GetCurrentThreadId() == _dwMainThreadId)
++_cRefsOnMainThread;
return InterlockedIncrement(&_cRefs);
}
ULONG STDMETHODCALLTYPE
CHttpRequest::Release()
{
if (GetCurrentThreadId() == _dwMainThreadId)
{
if ((--_cRefsOnMainThread == 0) && _fAsync)
{
// Clean up the Event Marshaller. This must be done
// on the main thread.
_CP.ShutdownEventSinksMarshaller();
// If the worker thread is still running, abort it
// and wait for it to run down.
Abort();
}
}
DWORD cRefs = InterlockedDecrement(&_cRefs);
if (cRefs == 0)
{
delete this;
return 0;
}
else
return cRefs;
}
HRESULT
CHttpRequest::GetHttpRequestTypeInfo(REFGUID guid, ITypeInfo ** ppTypeInfo)
{
HRESULT hr = NOERROR;
ITypeLib * pTypeLib;
char szPath[MAX_PATH];
OLECHAR wszPath[MAX_PATH];
GetModuleFileName(GlobalDllHandle, szPath, MAX_PATH);
MultiByteToWideChar(CP_ACP, 0, szPath, -1, wszPath, MAX_PATH);
hr = LoadTypeLib(wszPath, &pTypeLib);
if (SUCCEEDED(hr))
{
hr = pTypeLib->GetTypeInfoOfGuid(guid, ppTypeInfo);
pTypeLib->Release();
}
return hr;
}
STDMETHODIMP
CHttpRequest::GetTypeInfoCount(UINT * pctinfo)
{
if (!pctinfo)
return E_INVALIDARG;
*pctinfo = 1;
return NOERROR;
}
STDMETHODIMP
CHttpRequest::GetTypeInfo(UINT iTInfo, LCID, ITypeInfo ** ppTInfo)
{
if (!ppTInfo)
return E_INVALIDARG;
*ppTInfo = NULL;
if (iTInfo != 0)
return DISP_E_BADINDEX;
if (!_pTypeInfo)
{
HRESULT hr = GetHttpRequestTypeInfo(IID_IWinHttpRequest, &_pTypeInfo);
if (FAILED(hr))
return hr;
}
*ppTInfo = _pTypeInfo;
_pTypeInfo->AddRef();
return NOERROR;
}
struct IDMAPPING
{
const OLECHAR * wszMemberName;
DISPID dispId;
};
static const IDMAPPING IdMapping[] =
{
{ L"Open", DISPID_HTTPREQUEST_OPEN },
{ L"SetRequestHeader", DISPID_HTTPREQUEST_SETREQUESTHEADER },
{ L"Send", DISPID_HTTPREQUEST_SEND },
{ L"Status", DISPID_HTTPREQUEST_STATUS },
{ L"WaitForResponse", DISPID_HTTPREQUEST_WAITFORRESPONSE },
{ L"GetResponseHeader", DISPID_HTTPREQUEST_GETRESPONSEHEADER },
{ L"ResponseBody", DISPID_HTTPREQUEST_RESPONSEBODY },
{ L"ResponseText", DISPID_HTTPREQUEST_RESPONSETEXT },
{ L"ResponseStream", DISPID_HTTPREQUEST_RESPONSESTREAM },
{ L"StatusText", DISPID_HTTPREQUEST_STATUSTEXT },
{ L"SetCredentials", DISPID_HTTPREQUEST_SETCREDENTIALS },
{ L"SetProxy", DISPID_HTTPREQUEST_SETPROXY },
{ L"GetAllResponseHeaders", DISPID_HTTPREQUEST_GETALLRESPONSEHEADERS },
{ L"Abort", DISPID_HTTPREQUEST_ABORT },
{ L"SetTimeouts", DISPID_HTTPREQUEST_SETTIMEOUTS },
{ L"Option", DISPID_HTTPREQUEST_OPTION }
};
STDMETHODIMP
CHttpRequest::GetIDsOfNames(REFIID riid, LPOLESTR * rgszNames,
UINT cNames,
LCID ,
DISPID * rgDispId)
{
if (riid != IID_NULL)
return E_INVALIDARG;
HRESULT hr = NOERROR;
if (cNames > 0)
{
hr = DISP_E_UNKNOWNNAME;
for (int i = 0; i < (sizeof(IdMapping)/sizeof(IdMapping[0])); i++)
{
if (StrCmpIW(rgszNames[0], IdMapping[i].wszMemberName) == 0)
{
hr = NOERROR;
rgDispId[0] = IdMapping[i].dispId;
break;
}
}
}
return hr;
}
// _DispGetOptionalParam
//
// Helper routine to fetch optional parameters. If DispGetParam returns
// DISP_E_PARAMNOTFOUND, the error is converted to NOERROR.
//
static inline HRESULT _DispGetOptionalParam
(
DISPPARAMS * pDispParams,
DISPID dispid,
VARTYPE vt,
VARIANT * pvarResult,
unsigned int * puArgErr
)
{
HRESULT hr = DispGetParam(pDispParams, dispid, vt, pvarResult, puArgErr);
return (hr == DISP_E_PARAMNOTFOUND) ? NOERROR : hr;
}
STDMETHODIMP
CHttpRequest::Invoke(DISPID dispIdMember, REFIID riid,
LCID,
WORD wFlags,
DISPPARAMS * pDispParams,
VARIANT * pVarResult,
EXCEPINFO * pExcepInfo,
UINT * puArgErr)
{
HRESULT hr = NOERROR;
unsigned int uArgErr;
if (wFlags & ~(DISPATCH_METHOD | DISPATCH_PROPERTYGET | DISPATCH_PROPERTYPUT))
return E_INVALIDARG;
if (riid != IID_NULL)
return DISP_E_UNKNOWNINTERFACE;
if (IsBadReadPtr(pDispParams, sizeof(DISPPARAMS)))
return E_INVALIDARG;
if (!puArgErr)
{
puArgErr = &uArgErr;
}
else if (IsBadWritePtr(puArgErr, sizeof(UINT)))
{
return E_INVALIDARG;
}
if (pVarResult)
{
if (IsBadWritePtr(pVarResult, sizeof(VARIANT)))
return E_INVALIDARG;
VariantInit(pVarResult);
}
switch (dispIdMember)
{
case DISPID_HTTPREQUEST_ABORT:
{
hr = Abort();
break;
}
case DISPID_HTTPREQUEST_SETPROXY:
{
VARIANT varProxySetting;
VARIANT varProxyServer;
VARIANT varBypassList;
VariantInit(&varProxySetting);
VariantInit(&varProxyServer);
VariantInit(&varBypassList);
hr = DispGetParam(pDispParams, 0, VT_UI4, &varProxySetting, puArgErr);
if (SUCCEEDED(hr))
{
hr = _DispGetOptionalParam(pDispParams, 1, VT_BSTR, &varProxyServer, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = _DispGetOptionalParam(pDispParams, 2, VT_BSTR, &varBypassList, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = SetProxy(V_UI4(&varProxySetting), varProxyServer, varBypassList);
}
VariantClear(&varProxySetting);
VariantClear(&varProxyServer);
VariantClear(&varBypassList);
break;
}
case DISPID_HTTPREQUEST_SETCREDENTIALS:
{
VARIANT varUserName;
VARIANT varPassword;
VARIANT varAuthTarget;
VariantInit(&varUserName);
VariantInit(&varPassword);
VariantInit(&varAuthTarget);
hr = DispGetParam(pDispParams, 0, VT_BSTR, &varUserName, puArgErr);
if (SUCCEEDED(hr))
{
hr = DispGetParam(pDispParams, 1, VT_BSTR, &varPassword, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = DispGetParam(pDispParams, 2, VT_UI4, &varAuthTarget, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = SetCredentials(V_BSTR(&varUserName), V_BSTR(&varPassword),
V_UI4(&varAuthTarget));
}
VariantClear(&varUserName);
VariantClear(&varPassword);
VariantClear(&varAuthTarget);
break;
}
case DISPID_HTTPREQUEST_OPEN:
{
VARIANT varMethod;
VARIANT varUrl;
VARIANT varAsync;
VariantInit(&varMethod);
VariantInit(&varUrl);
VariantInit(&varAsync);
hr = DispGetParam(pDispParams, 0, VT_BSTR, &varMethod, puArgErr);
if (SUCCEEDED(hr))
{
hr = DispGetParam(pDispParams, 1, VT_BSTR, &varUrl, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = _DispGetOptionalParam(pDispParams, 2, VT_BOOL, &varAsync, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = Open(V_BSTR(&varMethod), V_BSTR(&varUrl), varAsync);
}
VariantClear(&varMethod);
VariantClear(&varUrl);
VariantClear(&varAsync);
break;
}
case DISPID_HTTPREQUEST_SETREQUESTHEADER:
{
VARIANT varHeader;
VARIANT varValue;
VariantInit(&varHeader);
VariantInit(&varValue);
hr = DispGetParam(pDispParams, 0, VT_BSTR, &varHeader, puArgErr);
if (SUCCEEDED(hr))
{
hr = DispGetParam(pDispParams, 1, VT_BSTR, &varValue, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = SetRequestHeader(V_BSTR(&varHeader), V_BSTR(&varValue));
}
VariantClear(&varHeader);
VariantClear(&varValue);
break;
}
case DISPID_HTTPREQUEST_GETRESPONSEHEADER:
{
VARIANT varHeader;
VariantInit(&varHeader);
hr = DispGetParam(pDispParams, 0, VT_BSTR, &varHeader, puArgErr);
if (SUCCEEDED(hr))
{
BSTR bstrValue = NULL;
hr = GetResponseHeader(V_BSTR(&varHeader), &bstrValue);
if (SUCCEEDED(hr) && pVarResult)
{
V_VT(pVarResult) = VT_BSTR;
V_BSTR(pVarResult) = bstrValue;
}
else
SysFreeString(bstrValue);
}
VariantClear(&varHeader);
break;
}
case DISPID_HTTPREQUEST_GETALLRESPONSEHEADERS:
{
BSTR bstrResponseHeaders = NULL;
hr = GetAllResponseHeaders(&bstrResponseHeaders);
if (SUCCEEDED(hr) && pVarResult)
{
V_VT(pVarResult) = VT_BSTR;
V_BSTR(pVarResult) = bstrResponseHeaders;
}
else
SysFreeString(bstrResponseHeaders);
break;
}
case DISPID_HTTPREQUEST_SEND:
{
if (pDispParams->cArgs <= 1)
{
VARIANT varEmptyBody;
VariantInit(&varEmptyBody);
hr = Send((pDispParams->cArgs == 0) ? varEmptyBody : pDispParams->rgvarg[0]);
}
else
{
hr = DISP_E_BADPARAMCOUNT;
}
break;
}
case DISPID_HTTPREQUEST_STATUS:
{
long Status;
hr = get_Status(&Status);
if (SUCCEEDED(hr) && pVarResult)
{
V_VT(pVarResult) = VT_I4;
V_I4(pVarResult) = Status;
}
break;
}
case DISPID_HTTPREQUEST_STATUSTEXT:
{
BSTR bstrStatus = NULL;
hr = get_StatusText(&bstrStatus);
if (SUCCEEDED(hr) && pVarResult)
{
V_VT(pVarResult) = VT_BSTR;
V_BSTR(pVarResult) = bstrStatus;
}
else
SysFreeString(bstrStatus);
break;
}
case DISPID_HTTPREQUEST_RESPONSETEXT:
{
BSTR bstrResponse = NULL;
hr = get_ResponseText(&bstrResponse);
if (SUCCEEDED(hr) && pVarResult)
{
V_VT(pVarResult) = VT_BSTR;
V_BSTR(pVarResult) = bstrResponse;
}
else
SysFreeString(bstrResponse);
break;
}
case DISPID_HTTPREQUEST_RESPONSEBODY:
{
if (pVarResult)
{
hr = get_ResponseBody(pVarResult);
}
break;
}
case DISPID_HTTPREQUEST_RESPONSESTREAM:
{
if (pVarResult)
{
hr = get_ResponseStream(pVarResult);
}
break;
}
case DISPID_HTTPREQUEST_OPTION:
{
VARIANT varOption;
WinHttpRequestOption Option;
VariantInit(&varOption);
hr = DispGetParam(pDispParams, 0, VT_I4, &varOption, puArgErr);
if (FAILED(hr))
break;
Option = static_cast<WinHttpRequestOption>(V_I4(&varOption));
if (wFlags & (DISPATCH_METHOD | DISPATCH_PROPERTYGET))
{
if (pVarResult)
{
hr = get_Option(Option, pVarResult);
}
}
else if (wFlags & DISPATCH_PROPERTYPUT)
{
hr = put_Option(Option, pDispParams->rgvarg[0]);
}
VariantClear(&varOption);
break;
}
case DISPID_HTTPREQUEST_WAITFORRESPONSE:
{
VARIANT varTimeout;
VARIANT_BOOL boolSucceeded;
VariantInit(&varTimeout);
hr = _DispGetOptionalParam(pDispParams, 0, VT_I4, &varTimeout, puArgErr);
if (SUCCEEDED(hr))
{
hr = WaitForResponse(varTimeout, &boolSucceeded);
}
if (pVarResult)
{
V_VT(pVarResult) = VT_BOOL;
V_BOOL(pVarResult) = boolSucceeded;
}
VariantClear(&varTimeout);
break;
}
case DISPID_HTTPREQUEST_SETTIMEOUTS:
{
VARIANT varResolveTimeout;
VARIANT varConnectTimeout;
VARIANT varSendTimeout;
VARIANT varReceiveTimeout;
VariantInit(&varResolveTimeout);
VariantInit(&varConnectTimeout);
VariantInit(&varSendTimeout);
VariantInit(&varReceiveTimeout);
hr = DispGetParam(pDispParams, 0, VT_I4, &varResolveTimeout, puArgErr);
if (SUCCEEDED(hr))
{
hr = DispGetParam(pDispParams, 1, VT_I4, &varConnectTimeout, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = DispGetParam(pDispParams, 2, VT_I4, &varSendTimeout, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = DispGetParam(pDispParams, 3, VT_I4, &varReceiveTimeout, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = SetTimeouts(V_I4(&varResolveTimeout), V_I4(&varConnectTimeout),
V_I4(&varSendTimeout),
V_I4(&varReceiveTimeout));
}
VariantClear(&varResolveTimeout);
VariantClear(&varConnectTimeout);
VariantClear(&varSendTimeout);
VariantClear(&varReceiveTimeout);
break;
}
default:
hr = DISP_E_MEMBERNOTFOUND;
break;
}
if (FAILED(hr) && (pExcepInfo != NULL))
{
hr = FillExcepInfo(hr, pExcepInfo);
}
return hr;
}
static
HRESULT
FillExcepInfo(HRESULT hr, EXCEPINFO * pExcepInfo)
{
// Don't create excepinfo for these errors to mimic oleaut behavior.
if( hr == DISP_E_BADPARAMCOUNT ||
hr == DISP_E_NONAMEDARGS ||
hr == DISP_E_MEMBERNOTFOUND ||
hr == E_INVALIDARG)
{
return hr;
}
// clear out exception info
IErrorInfo * pei = NULL;
pExcepInfo->wCode = 0;
pExcepInfo->scode = hr;
// if error info exists, use it
GetErrorInfo(0, &pei);
if (pei)
{
// give back to OLE
SetErrorInfo(0, pei);
pei->GetHelpContext(&pExcepInfo->dwHelpContext);
pei->GetSource(&pExcepInfo->bstrSource);
pei->GetDescription(&pExcepInfo->bstrDescription);
pei->GetHelpFile(&pExcepInfo->bstrHelpFile);
// give complete ownership to OLEAUT
pei->Release();
hr = DISP_E_EXCEPTION;
}
return hr;
}
STDMETHODIMP
CHttpRequest::GetClassInfo(ITypeInfo ** ppTI)
{
if (!ppTI)
return E_POINTER;
*ppTI = NULL;
return GetHttpRequestTypeInfo(CLSID_WinHttpRequest, ppTI);
}
STDMETHODIMP
CHttpRequest::GetGUID(DWORD dwGuidKind, GUID * pGUID)
{
if (!pGUID)
return E_POINTER;
if (dwGuidKind == GUIDKIND_DEFAULT_SOURCE_DISP_IID)
{
*pGUID = IID_IWinHttpRequestEvents;
}
else
return E_INVALIDARG;
return NOERROR;
}
STDMETHODIMP
CHttpRequest::EnumConnectionPoints(IEnumConnectionPoints **)
{
return E_NOTIMPL;
}
STDMETHODIMP
CHttpRequest::FindConnectionPoint(REFIID riid, IConnectionPoint ** ppCP)
{
if (!ppCP)
return E_POINTER;
if (riid == IID_IWinHttpRequestEvents)
{
return _CP.QueryInterface(IID_IConnectionPoint, (void **)ppCP);
}
else
return CONNECT_E_NOCONNECTION;
}
STDMETHODIMP
CHttpRequest::CHttpRequestEventsCP::QueryInterface(REFIID riid, void ** ppvObject)
{
if (!ppvObject)
return E_INVALIDARG;
if (riid == IID_IUnknown || riid == IID_IConnectionPoint)
{
*ppvObject = static_cast<IUnknown *>(static_cast<IConnectionPoint *>(this));
AddRef();
return NOERROR;
}
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE
CHttpRequest::CHttpRequestEventsCP::AddRef()
{
return Px()->AddRef();
}
ULONG STDMETHODCALLTYPE
CHttpRequest::CHttpRequestEventsCP::Release()
{
return Px()->Release();
}
STDMETHODIMP
CHttpRequest::CHttpRequestEventsCP::GetConnectionInterface(IID * pIID)
{
if (!pIID)
return E_POINTER;
*pIID = IID_IWinHttpRequestEvents;
return NOERROR;
}
STDMETHODIMP
CHttpRequest::CHttpRequestEventsCP::GetConnectionPointContainer
(
IConnectionPointContainer ** ppCPC
)
{
if (!ppCPC)
return E_POINTER;
return Px()->QueryInterface(IID_IConnectionPointContainer, (void **)ppCPC);
}
STDMETHODIMP
CHttpRequest::CHttpRequestEventsCP::Advise(IUnknown * pUnk, DWORD * pdwCookie)
{
if (!pUnk || !pdwCookie)
{
return E_POINTER;
}
IWinHttpRequestEvents * pIWinHttpRequestEvents;
HRESULT hr;
hr = pUnk->QueryInterface(IID_IWinHttpRequestEvents, (void **)&pIWinHttpRequestEvents);
if (SUCCEEDED(hr))
{
*pdwCookie = _SinkArray.Add(static_cast<IUnknown *>(pIWinHttpRequestEvents));
if (*pdwCookie)
{
_cConnections++;
hr = NOERROR;
}
else
{
hr = E_OUTOFMEMORY;
}
}
else
hr = CONNECT_E_CANNOTCONNECT;
return hr;
}
STDMETHODIMP
CHttpRequest::CHttpRequestEventsCP::Unadvise(DWORD dwCookie)
{
IUnknown * pSink = _SinkArray.GetUnknown(dwCookie);
if (pSink)
{
_SinkArray.Remove(dwCookie);
pSink->Release();
--_cConnections;
}
return NOERROR;
}
STDMETHODIMP
CHttpRequest::CHttpRequestEventsCP::EnumConnections(IEnumConnections **)
{
return E_NOTIMPL;
}
void
CHttpRequest::CHttpRequestEventsCP::FireOnResponseStart(long Status, BSTR ContentType)
{
if (_cConnections > 0 && !Px()->_bAborted)
{
GetSink()->OnResponseStart(Status, ContentType);
}
}
void
CHttpRequest::CHttpRequestEventsCP::FireOnResponseDataAvailable
(
const BYTE * rgbData,
DWORD cbData
)
{
if (_cConnections > 0 && !Px()->_bAborted)
{
VARIANT varData;
HRESULT hr;
VariantInit(&varData);
hr = CreateVector(&varData, rgbData, cbData);
if (SUCCEEDED(hr))
{
GetSink()->OnResponseDataAvailable(&V_ARRAY(&varData));
}
VariantClear(&varData);
}
}
void
CHttpRequest::CHttpRequestEventsCP::FireOnResponseFinished()
{
if (_cConnections > 0 && !Px()->_bAborted)
{
GetSink()->OnResponseFinished();
}
}
HRESULT
CHttpRequest::CHttpRequestEventsCP::CreateEventSinksMarshaller()
{
HRESULT hr = NOERROR;
if (_cConnections > 0)
{
SafeRelease(_pSinkMarshaller);
hr = CWinHttpRequestEventsMarshaller::Create(&_SinkArray, &_pSinkMarshaller);
}
return hr;
}
void
CHttpRequest::CHttpRequestEventsCP::ShutdownEventSinksMarshaller()
{
if (_pSinkMarshaller)
_pSinkMarshaller->Shutdown();
}
void
CHttpRequest::CHttpRequestEventsCP::ReleaseEventSinksMarshaller()
{
SafeRelease(_pSinkMarshaller);
}
void
CHttpRequest::CHttpRequestEventsCP::FreezeEvents()
{
if (_pSinkMarshaller)
_pSinkMarshaller->FreezeEvents();
}
void
CHttpRequest::CHttpRequestEventsCP::UnfreezeEvents()
{
if (_pSinkMarshaller)
_pSinkMarshaller->UnfreezeEvents();
}
CHttpRequest::CHttpRequestEventsCP::~CHttpRequestEventsCP()
{
// If any connections are still alive, unadvise them.
if (_cConnections > 0)
{
_SinkArray.ReleaseAll();
_cConnections = 0;
}
}
/*
* CHttpRequest::Initialize
*
* Purpose:
* Zero all data members
*
*/
void
CHttpRequest::Initialize()
{
_cRefs = 0;
_pTypeInfo = NULL;
_bstrUserAgent = NULL;
_dwProxySetting = INTERNET_OPEN_TYPE_PRECONFIG;
_bstrProxyServer = NULL;
_bstrBypassList = NULL;
_eState = CHttpRequest::CREATED;
_fAsync = FALSE;
_hWorkerThread = NULL;
_cRefsOnMainThread = 0;
_dwMainThreadId = GetCurrentThreadId();
_hrAsyncResult = NOERROR;
_bAborted = false;
_bSetTimeouts = false;
_bDisableRedirects = false;
_hInet = NULL;
_hConnection = NULL;
_hHTTP = NULL;
_ResolveTimeout = 0;
_ConnectTimeout = 0;
_SendTimeout = 0;
_ReceiveTimeout = 0;
_cbRequestBody = 0;
_szRequestBuffer = NULL;
_dwCodePage = CP_UTF8;
_dwEscapePercentFlag = 0;
_szResponseBuffer = NULL;
_cbResponseBuffer = 0;
_cbResponseBody = 0;
_hAbortedConnectObject = NULL;
_hAbortedRequestObject = NULL;
_bstrCertSubject = NULL;
_dwSslIgnoreFlags = 0;
}
/*
* CHttpRequest::ReleaseResources
*
* Purpose:
* Release all handles, events, and buffers
*
*/
void
CHttpRequest::ReleaseResources()
{
SafeRelease(_pTypeInfo);
if (_hWorkerThread)
{
CloseHandle(_hWorkerThread);
_hWorkerThread = NULL;
}
_CP.ReleaseEventSinksMarshaller();
//
// Derefence aborted handle objects (if any).
//
if (_hAbortedRequestObject != NULL)
{
DereferenceObject(_hAbortedRequestObject);
_hAbortedRequestObject = NULL;
}
if (_hAbortedConnectObject != NULL)
{
DereferenceObject(_hAbortedConnectObject);
_hAbortedConnectObject = NULL;
}
if (_hHTTP)
{
HINTERNET temp = _hHTTP;
_hHTTP = NULL;
WinHttpCloseHandle(temp);
}
if (_hConnection)
{
HINTERNET temp = _hConnection;
_hConnection = NULL;
WinHttpCloseHandle(temp);
}
if (_hInet)
{
HINTERNET temp = _hInet;
_hInet = NULL;
WinHttpCloseHandle(temp);
}
if (_szRequestBuffer)
{
delete [] _szRequestBuffer;
_szRequestBuffer = NULL;
}
if (_szResponseBuffer)
{
delete [] _szResponseBuffer;
_szResponseBuffer = NULL;
}
if (_bstrUserAgent)
{
SysFreeString(_bstrUserAgent);
_bstrUserAgent = NULL;
}
if (_bstrProxyServer)
{
SysFreeString(_bstrProxyServer);
_bstrProxyServer = NULL;
}
if (_bstrBypassList)
{
SysFreeString(_bstrBypassList);
_bstrBypassList = NULL;
}
if (_bstrCertSubject)
{
SysFreeString(_bstrCertSubject);
_bstrCertSubject = NULL;
}
}
/*
* CHttpRequest::Reset
*
* Purpose:
* Release all resources and initialize data members
*
*/
void
CHttpRequest::Reset()
{
ReleaseResources();
Initialize();
}
/*
* CHttpRequest::Recycle
*
* Purpose:
* Recycle object
*
*/
void
CHttpRequest::Recycle()
{
//
// Wait for the worker thread to shut down. This shouldn't take long
// since the Abort will close the Request and Connection handles.
//
if (_hWorkerThread)
{
DWORD dwWaitResult;
for (;;)
{
dwWaitResult = MsgWaitForMultipleObjects(1, &_hWorkerThread,
FALSE,
INFINITE,
QS_ALLINPUT);
if (dwWaitResult == (WAIT_OBJECT_0 + 1))
{
// Message waiting in the message queue.
// Run message pump to clear queue.
MessageLoop();
}
else
{
break;
}
}
CloseHandle(_hWorkerThread);
_hWorkerThread = NULL;
}
_hConnection = NULL;
_hHTTP = NULL;
//
// Derefence aborted handle objects (if any).
//
if (_hAbortedRequestObject != NULL)
{
DereferenceObject(_hAbortedRequestObject);
_hAbortedRequestObject = NULL;
}
if (_hAbortedConnectObject != NULL)
{
DereferenceObject(_hAbortedConnectObject);
_hAbortedConnectObject = NULL;
}
_fAsync = FALSE;
_hrAsyncResult = NOERROR;
_bAborted = false;
// don't reset timeouts, keep any that were set.
_cbRequestBody = 0;
_cbResponseBuffer = 0;
_cbResponseBody = 0;
if (_szRequestBuffer)
{
delete [] _szRequestBuffer;
_szRequestBuffer = NULL;
}
if (_szResponseBuffer)
{
delete [] _szResponseBuffer;
_szResponseBuffer = NULL;
}
_CP.ShutdownEventSinksMarshaller();
_CP.ReleaseEventSinksMarshaller();
// Allow events to fire; Abort() would have frozen them from firing.
_CP.UnfreezeEvents();
SetState(CHttpRequest::CREATED);
}
/*
* CHttpRequest::ReadResponse
*
* Purpose:
* Read the response bits
*
* Parameters:
* None
*
* Errors:
* E_FAIL
* E_OUTOFMEMORY
*/
HRESULT
CHttpRequest::ReadResponse()
{
HRESULT hr = NOERROR;
BOOL fRetCode;
long lStatus;
BSTR bstrContentType = NULL;
DWORD dwContentLength = 0;
// Determine the content length
DWORD cb = sizeof(dwContentLength);
fRetCode = HttpQueryInfoA(
_hHTTP,
HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX,
&dwContentLength,
&cb,
0);
hr = get_Status(&lStatus);
if (FAILED(hr))
goto Error;
hr = _GetResponseHeader(L"Content-Type", &bstrContentType);
if (FAILED(hr))
{
bstrContentType = SysAllocString(L"");
if (bstrContentType == NULL)
goto ErrorOutOfMemory;
hr = NOERROR;
}
_CP.FireOnResponseStart(lStatus, bstrContentType);
if (dwContentLength != 0)
{
_szResponseBuffer = New char[dwContentLength];
if (_szResponseBuffer)
{
_cbResponseBuffer = dwContentLength;
}
else
goto ErrorOutOfMemory;
}
else
{
_szResponseBuffer = NULL;
_cbResponseBuffer = 0;
}
//
// Read data until there is no more - we need to buffer the data
//
while (!_bAborted)
{
DWORD cbAvail = 0;
DWORD cbRead = 0;
fRetCode = WinHttpQueryDataAvailable(_hHTTP, &cbAvail);
if (!fRetCode)
{
goto ErrorFail;
}
// Check for buffer overflow - dynamically resize if neccessary
if (_cbResponseBody + cbAvail > _cbResponseBuffer)
{
ULONG cbNewSize = _cbResponseBody + cbAvail;
char * szNewBuf = New char[cbNewSize];
if (!szNewBuf)
goto ErrorOutOfMemory;
if (_szResponseBuffer)
{
::memcpy(szNewBuf, _szResponseBuffer, _cbResponseBody);
delete [] _szResponseBuffer;
}
_cbResponseBuffer = cbNewSize;
_szResponseBuffer = szNewBuf;
}
fRetCode = WinHttpReadData(
_hHTTP,
&_szResponseBuffer[_cbResponseBody],
cbAvail,
&cbRead);
if (!fRetCode)
{
goto ErrorFail;
}
// No more data
if (cbRead == 0)
break;
_CP.FireOnResponseDataAvailable((const BYTE *)&_szResponseBuffer[_cbResponseBody],
cbRead);
_cbResponseBody += cbRead;
}
SetState(CHttpRequest::RESPONSE);
hr = NOERROR;
Cleanup:
if (bstrContentType)
SysFreeString(bstrContentType);
_CP.FireOnResponseFinished();
return hr;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Cleanup;
Error:
goto Cleanup;
}
STDMETHODIMP
CHttpRequest::SetProxy(HTTPREQUEST_PROXY_SETTING ProxySetting,
VARIANT varProxyServer,
VARIANT varBypassList)
{
HRESULT hr = NOERROR;
if (!IsValidVariant(varProxyServer) || !IsValidVariant(varBypassList))
return E_INVALIDARG;
if (_bstrProxyServer)
{
SysFreeString(_bstrProxyServer);
_bstrProxyServer = NULL;
}
if (_bstrBypassList)
{
SysFreeString(_bstrBypassList);
_bstrBypassList = NULL;
}
switch (ProxySetting)
{
case HTTPREQUEST_PROXYSETTING_PRECONFIG:
_dwProxySetting = INTERNET_OPEN_TYPE_PRECONFIG;
break;
case HTTPREQUEST_PROXYSETTING_DIRECT:
_dwProxySetting = INTERNET_OPEN_TYPE_DIRECT;
break;
case HTTPREQUEST_PROXYSETTING_PROXY:
_dwProxySetting = INTERNET_OPEN_TYPE_PROXY;
_bstrProxyServer = GetBSTRFromVariant(varProxyServer);
_bstrBypassList = GetBSTRFromVariant(varBypassList);
break;
default:
hr = E_INVALIDARG;
break;
}
if (SUCCEEDED(hr))
{
if (_hInet)
{
WINHTTP_PROXY_INFOW ProxyInfo;
memset(&ProxyInfo, 0, sizeof(ProxyInfo));
ProxyInfo.dwAccessType = _dwProxySetting;
ProxyInfo.lpszProxy = _bstrProxyServer;
ProxyInfo.lpszProxyBypass = _bstrBypassList;
if (!WinHttpSetOption(_hInet, WINHTTP_OPTION_PROXY,
&ProxyInfo,
sizeof(ProxyInfo)))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
}
SetErrorInfo(hr);
return hr;
}
STDMETHODIMP
CHttpRequest::SetCredentials(
BSTR bstrUserName,
BSTR bstrPassword,
HTTPREQUEST_SETCREDENTIALS_FLAGS Flags)
{
HRESULT hr;
// Must call Open method before SetCredentials.
if (! _hHTTP)
{
goto ErrorCannotCallBeforeOpen;
}
if (!IsValidBstr(bstrUserName) || !IsValidBstr(bstrPassword))
return E_INVALIDARG;
if (Flags == HTTPREQUEST_SETCREDENTIALS_FOR_SERVER)
{
// Set Username and Password.
WinHttpSetOption(
_hHTTP,
WINHTTP_OPTION_USERNAME,
bstrUserName,
SysStringLen(bstrUserName));
WinHttpSetOption(
_hHTTP,
WINHTTP_OPTION_PASSWORD,
bstrPassword,
SysStringLen(bstrPassword));
}
else if (Flags == HTTPREQUEST_SETCREDENTIALS_FOR_PROXY)
{
// Set Username and Password.
WinHttpSetOption(
_hHTTP,
WINHTTP_OPTION_PROXY_USERNAME,
bstrUserName,
SysStringLen(bstrUserName));
WinHttpSetOption(
_hHTTP,
WINHTTP_OPTION_PROXY_PASSWORD,
bstrPassword,
SysStringLen(bstrPassword));
}
else
return E_INVALIDARG;
hr = NOERROR;
Cleanup:
SetErrorInfo(hr);
return hr;
ErrorCannotCallBeforeOpen:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN);
goto Cleanup;
}
/*
* CHttpRequest::Open
*
* Purpose:
* Open a logical HTTP connection
*
* Parameters:
* bstrMethod IN HTTP method (GET, PUT, ...)
* bstrUrl IN Target URL
*
* Errors:
* E_FAIL
* E_INVALIDARG
* E_OUTOFMEMORY
* E_ACCESSDENIED
* Errors from InternetOpenA and WinHttpCrackUrlA and InternetConnectA
* and HttpOpenRequestA
*/
STDMETHODIMP
CHttpRequest::Open(
BSTR bstrMethod,
BSTR bstrUrl,
VARIANT varAsync)
{
HRESULT hr = NOERROR;
BSTR bstrHostName = NULL;
BSTR bstrUrlPath = NULL;
DWORD dwHttpOpenFlags = 0;
URL_COMPONENTSW url;
// Check for reinitialization
if (_eState != CHttpRequest::CREATED)
{
//
// Abort any request in progress.
// This will also recycle the object.
//
Abort();
}
// Validate parameters
if (!bstrMethod || !bstrUrl ||
!IsValidBstr(bstrMethod) ||
!IsValidBstr(bstrUrl) ||
!lstrlenW(bstrMethod) || // cannot have empty method
!lstrlenW(bstrUrl) || // cannot have empty url
!IsValidVariant(varAsync))
return E_INVALIDARG;
_fAsync = GetBoolFromVariant(varAsync, FALSE);
//
// Open an Internet Session if one does not already exist.
//
if (!_hInet)
{
_hInet = WinHttpOpen(
GetUserAgentString(),
_dwProxySetting,
_bstrProxyServer,
_bstrBypassList,
0);
if (!_hInet)
goto ErrorFail;
// In winhttp5, this should be adjusted through an exposed option
// or flag, rather than going through the back door.
HINTERNET hSessionMapped = NULL;
if (ERROR_SUCCESS == MapHandleToAddress(_hInet,
(LPVOID *)&hSessionMapped,
FALSE) &&
hSessionMapped)
{
((INTERNET_HANDLE_OBJECT *)hSessionMapped)->SetUseSslSessionCache(TRUE);
DereferenceObject(hSessionMapped);
}
}
//
// If any timeouts were set previously, apply them.
//
if (_bSetTimeouts)
{
if (!WinHttpSetTimeouts(_hInet, (int)_ResolveTimeout,
(int)_ConnectTimeout,
(int)_SendTimeout,
(int)_ReceiveTimeout))
goto ErrorFail;
}
//
// Set the code page on the Session handle; the Connect
// handle will also inherit this value.
//
if (!WinHttpSetOption(_hInet, WINHTTP_OPTION_CODEPAGE,
&_dwCodePage,
sizeof(_dwCodePage)))
goto ErrorFail;
// Break the URL into the required components
ZeroMemory(&url, sizeof(URL_COMPONENTSW));
url.dwStructSize = sizeof(URL_COMPONENTSW);
url.dwHostNameLength = 1;
url.dwUrlPathLength = 1;
url.dwExtraInfoLength = 1;
if (!WinHttpCrackUrl(bstrUrl, 0, 0, &url))
goto ErrorFail;
// Check for non-http schemes
if (url.nScheme != INTERNET_SCHEME_HTTP && url.nScheme != INTERNET_SCHEME_HTTPS)
goto ErrorUnsupportedScheme;
// IE6/Reno Bug #6236: if the client does not specify a resource path,
// then add the "/".
if (url.dwUrlPathLength == 0)
{
INET_ASSERT(url.dwExtraInfoLength == 1);
url.lpszUrlPath = L"/";
url.dwUrlPathLength = 1;
}
bstrHostName = SysAllocStringLen(url.lpszHostName, url.dwHostNameLength);
bstrUrlPath = SysAllocStringLen(url.lpszUrlPath, lstrlenW(url.lpszUrlPath));
if (!bstrHostName || !bstrUrlPath)
goto ErrorOutOfMemory;
INET_ASSERT(_hConnection == NULL);
INET_ASSERT(_hHTTP == NULL);
_hConnection = WinHttpConnect(
_hInet,
bstrHostName,
url.nPort,
0);
if (!_hConnection)
goto ErrorFail;
if (url.nScheme == INTERNET_SCHEME_HTTPS)
{
dwHttpOpenFlags |= WINHTTP_FLAG_SECURE;
}
//
// Apply EscapePercentInURL option.
//
dwHttpOpenFlags |= _dwEscapePercentFlag;
_hHTTP = WinHttpOpenRequest(
_hConnection,
bstrMethod,
bstrUrlPath,
NULL,
NULL,
NULL,
dwHttpOpenFlags);
if (!_hHTTP)
goto ErrorFail;
// Set the SSL ignore flags through an undocumented front door
if (_dwSslIgnoreFlags)
{
WinHttpSetOption(_hHTTP,
WINHTTP_OPTION_SECURITY_FLAGS,
(LPVOID)&_dwSslIgnoreFlags,
sizeof(_dwSslIgnoreFlags));
}
SetState(CHttpRequest::OPENED);
hr = NOERROR;
Cleanup:
if (bstrHostName)
SysFreeString(bstrHostName);;
if (bstrUrlPath)
SysFreeString(bstrUrlPath);
SetErrorInfo(hr);
return hr;
ErrorUnsupportedScheme:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_UNRECOGNIZED_SCHEME);
goto Cleanup;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Cleanup;
Error:
goto Cleanup;
}
/*
* CHttpRequest::SetRequestHeader
*
* Purpose:
* Set a request header
*
* Parameters:
* bstrHeader IN HTTP request header
* bstrValue IN Header value
*
* Errors:
* E_FAIL
* E_INVALIDARG
* E_UNEXPECTED
*/
STDMETHODIMP
CHttpRequest::SetRequestHeader(BSTR bstrHeader, BSTR bstrValue)
{
WCHAR * wszHeaderValue = NULL;
DWORD cchHeaderValue;
DWORD dwModifiers = HTTP_ADDREQ_FLAG_ADD;
HRESULT hr = NOERROR;
// Validate header parameter (null or zero-length value is allowed)
if (!bstrHeader || !IsValidBstr(bstrHeader) ||
lstrlenW(bstrHeader)==0 ||
!IsValidBstr(bstrValue))
return E_INVALIDARG;
// Validate state
if (_eState < CHttpRequest::OPENED)
goto ErrorCannotCallBeforeOpen;
else if (_eState >= CHttpRequest::SENDING)
goto ErrorCannotCallAfterSend;
// Ignore attempts to set the Content-Length header; the
// content length is computed and sent automatically.
if (StrCmpIW(bstrHeader, L"Content-Length") == 0)
goto Cleanup;
cchHeaderValue = SysStringLen(bstrHeader) + SysStringLen(bstrValue)
+ 2 /* wcslen(L": ") */
+ 2 /* wcslen(L"\r\n") */;
wszHeaderValue = New WCHAR [cchHeaderValue + 1];
if (!wszHeaderValue)
goto ErrorOutOfMemory;
wcscpy(wszHeaderValue, bstrHeader);
wcscat(wszHeaderValue, L": ");
if (bstrValue)
wcscat(wszHeaderValue, bstrValue);
wcscat(wszHeaderValue, L"\r\n");
// For blank header values, erase the header by setting the
// REPLACE flag.
if (SysStringLen(bstrValue) == 0)
{
dwModifiers |= HTTP_ADDREQ_FLAG_REPLACE;
}
if (! WinHttpAddRequestHeaders(_hHTTP, wszHeaderValue,
-1L,
dwModifiers))
goto ErrorFail;
hr = NOERROR;
Cleanup:
if (wszHeaderValue)
delete [] wszHeaderValue;
SetErrorInfo(hr);
return hr;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
ErrorCannotCallBeforeOpen:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN);
goto Error;
ErrorCannotCallAfterSend:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_AFTER_SEND);
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Error;
Error:
goto Cleanup;
}
/*
* CHttpRequest::SetRequiredRequestHeaders
*
* Purpose:
* Set implicit request headers
*
* Parameters:
* None
*
* Errors:
* E_FAIL
* E_UNEXPECTED
* E_OUTOFMEMORY
* Errors from WinHttpAddRequestHeaders and WinHttpSendRequest
*/
HRESULT
CHttpRequest::SetRequiredRequestHeaders()
{
HRESULT hr = NOERROR;
char szContentLengthHeader[sizeof("Content-Length") +
sizeof(": ") +
15 + // content-length value
sizeof("\r\n") + 1];
lstrcpy(szContentLengthHeader, "Content-Length: ");
_ltoa(_cbRequestBody,
szContentLengthHeader + 16, /* "Content-Length: " */
10);
lstrcat(szContentLengthHeader, "\r\n");
if (! HttpAddRequestHeadersA(_hHTTP, szContentLengthHeader,
-1L,
HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE))
{
hr = E_FAIL;
}
// Add an Accept: */* header if no other Accept header
// has been set. Ignore any return code, since it is
// not fatal if this fails.
HttpAddRequestHeadersA(_hHTTP, "Accept: */*",
-1L,
HTTP_ADDREQ_FLAG_ADD_IF_NEW);
return hr;
}
DWORD WINAPI WinHttpRequestSendAsync(LPVOID lpParameter)
{
#ifdef WINHTTP_FOR_MSXML
//
// MSXML needs to initialize its thread local storage data.
// It does not do this during DLL_THREAD_ATTACH, so our
// worker thread must explicitly call into MSXML to initialize
// its TLS for this thread.
//
InitializeMsxmlTLS();
#endif
HRESULT hr;
DWORD dwExitCode;
CHttpRequest * pWinHttpRequest = reinterpret_cast<CHttpRequest *>(lpParameter);
INET_ASSERT(pWinHttpRequest != NULL);
dwExitCode = pWinHttpRequest->SendAsync();
pWinHttpRequest->Release();
// If this worker thread was impersonating, revert to the default
// process identity.
RevertToSelf();
return dwExitCode;
}
/*
* CHttpRequest::CreateAsyncWorkerThread
*
*/
HRESULT
CHttpRequest::CreateAsyncWorkerThread()
{
DWORD dwWorkerThreadId;
HANDLE hThreadToken = NULL;
HRESULT hr;
hr = _CP.CreateEventSinksMarshaller();
if (FAILED(hr))
return hr;
hr = NOERROR;
//
// If the current thread is impersonating, then grab its access token
// and revert the current thread (so it is nolonger impersonating).
// After creating the worker thread, we will make the main thread
// impersonate again. Apparently you should not call CreateThread
// while impersonating.
//
if (OpenThreadToken(GetCurrentThread(), (TOKEN_IMPERSONATE | TOKEN_READ),
FALSE,
&hThreadToken))
{
INET_ASSERT(hThreadToken != 0);
RevertToSelf();
}
// Create the worker thread suspended.
_hWorkerThread = CreateThread(NULL, 0, WinHttpRequestSendAsync,
(void *)static_cast<CHttpRequest *>(this),
CREATE_SUSPENDED,
&dwWorkerThreadId);
// If CreateThread fails, grab the error code now.
if (!_hWorkerThread)
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
//
// If the main thread was impersonating, then:
// (1) have the worker thread impersonate the same user, and
// (2) have the main thread resume impersonating the
// client too (since we called RevertToSelf above).
//
if (hThreadToken)
{
if (_hWorkerThread)
{
SetThreadToken(&_hWorkerThread, hThreadToken);
}
SetThreadToken(NULL, hThreadToken);
CloseHandle(hThreadToken);
}
// If the worker thread was created, start it running.
if (_hWorkerThread)
{
// The worker thread owns a ref count on the component.
// Don't call AddRef() as it will attribute the ref count
// to the main thread.
_cRefs++;
ResumeThread(_hWorkerThread);
}
else
{
_CP.ShutdownEventSinksMarshaller();
_CP.ReleaseEventSinksMarshaller();
}
return hr;
}
/*
* CHttpRequest::Send
*
* Purpose:
* Send the HTTP request
*
* Parameters:
* varBody IN Request body
*
* Errors:
* E_FAIL
* E_UNEXPECTED
* E_OUTOFMEMORY
* Errors from WinHttpAddRequestHeaders and WinHttpSendRequest
*/
STDMETHODIMP
CHttpRequest::Send(VARIANT varBody)
{
HRESULT hr = NOERROR;
BOOL fRetCode = FALSE;
BOOL fRetryWithClientAuth = TRUE;
// Validate parameter
if (!IsValidVariant(varBody))
return E_INVALIDARG;
// Validate state
if (_eState < CHttpRequest::OPENED)
goto ErrorCannotCallBeforeOpen;
// Get the request body
hr = SetRequestBody(varBody);
if (FAILED(hr))
goto Error;
hr = SetRequiredRequestHeaders();
if (FAILED(hr))
goto Error;
if (_bDisableRedirects)
{
DWORD dwDisable = WINHTTP_DISABLE_REDIRECTS;
WinHttpSetOption(_hHTTP,
WINHTTP_OPTION_DISABLE_FEATURE,
(void *) &dwDisable,
sizeof(DWORD));
}
try_again:
SetState(CHttpRequest::SENDING);
if (_fAsync)
{
hr = CreateAsyncWorkerThread();
if (FAILED(hr))
goto Error;
}
else
{
// Send the HTTP request
fRetCode = WinHttpSendRequest(
_hHTTP,
NULL, 0, // No header info here
_szRequestBuffer,
_cbRequestBody,
_cbRequestBody,
0);
if (!fRetCode)
{
goto ErrorFail;
}
fRetCode = WinHttpReceiveResponse(_hHTTP, NULL);
if (!fRetCode)
{
goto ErrorFail;
}
SetState(CHttpRequest::SENT);
// Read the response data
hr = ReadResponse();
if (FAILED(hr))
goto Error;
}
hr = NOERROR;
Cleanup:
SetErrorInfo(hr);
return hr;
ErrorCannotCallBeforeOpen:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN);
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
if (!_fAsync &&
hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED) &&
fRetryWithClientAuth)
{
fRetryWithClientAuth = FALSE;
// Try to enumerate the first cert in the reverted user context,
// select the cert (per object sesssion, not global), and send
// the request again.
if (SelectCertificate())
goto try_again;
}
goto Cleanup;
Error:
goto Cleanup;
}
/*
* CHttpRequest::SendAsync
*
* Purpose:
* Send the HTTP request
*
* Parameters:
* varBody IN Request body
*
* Errors:
* E_FAIL
* E_UNEXPECTED
* E_OUTOFMEMORY
* Errors from WinHttpAddRequestHeaders and WinHttpSendRequest
*/
DWORD
CHttpRequest::SendAsync()
{
DWORD dwLastError = 0;
DWORD fRetCode;
HRESULT hr;
BOOL fRetryWithClientAuth = TRUE;
try_again:
if (_bAborted || !_hHTTP)
goto ErrorUnexpected;
// Send the HTTP request
fRetCode = WinHttpSendRequest(
_hHTTP,
NULL, 0, // No header info here
_szRequestBuffer,
_cbRequestBody,
_cbRequestBody,
0);
if (!fRetCode)
goto ErrorFail;
fRetCode = WinHttpReceiveResponse(_hHTTP, NULL);
if (!fRetCode)
goto ErrorFail;
if (!_bAborted)
{
SetState(CHttpRequest::SENT);
hr = ReadResponse();
if (FAILED(hr))
{
if (hr == E_OUTOFMEMORY)
goto ErrorOutOfMemory;
goto ErrorFail;
}
}
hr = NOERROR;
Cleanup:
_hrAsyncResult = hr;
return dwLastError;
ErrorUnexpected:
dwLastError = ERROR_WINHTTP_INTERNAL_ERROR;
hr = HRESULT_FROM_WIN32(dwLastError);
goto Cleanup;
ErrorFail:
dwLastError = GetLastError();
if (dwLastError == ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED &&
fRetryWithClientAuth)
{
fRetryWithClientAuth = FALSE;
// Try to enumerate the first cert in the reverted user context,
// select the cert (per object sesssion, not global), and send
// the request again.
if (SelectCertificate())
{
SetState(CHttpRequest::SENDING);
goto try_again;
}
}
hr = HRESULT_FROM_WIN32(dwLastError);
goto Cleanup;
ErrorOutOfMemory:
dwLastError = ERROR_NOT_ENOUGH_MEMORY;
hr = E_OUTOFMEMORY;
goto Cleanup;
}
STDMETHODIMP
CHttpRequest::WaitForResponse(VARIANT varTimeout, VARIANT_BOOL * pboolSucceeded)
{
HRESULT hr = NOERROR;
bool bSucceeded= true;
DWORD dwTimeout;
// Validate parameters; null pboolSucceeded pointer is Ok.
if (!IsValidVariant(varTimeout) ||
(pboolSucceeded &&
IsBadWritePtr(pboolSucceeded, sizeof(VARIANT_BOOL))))
return E_INVALIDARG;
// Get the timeout value. Disallow numbers
// less than -1 (which means INFINITE).
if (GetLongFromVariant(varTimeout, INFINITE) < -1L)
return E_INVALIDARG;
dwTimeout = GetDwordFromVariant(varTimeout, INFINITE);
// Validate state.
if (_eState < CHttpRequest::SENDING)
goto ErrorCannotCallBeforeSend;
//
// WaitForResponse is a no-op if we're not in async mode.
//
if (_fAsync && _hWorkerThread)
{
//
// Has the worker thread has already finished?
//
if (WaitForSingleObject(_hWorkerThread, 0) == WAIT_TIMEOUT)
{
//
// Convert Timeout from seconds to milliseconds. Any timeout
// value over 4 million seconds (~46 days) is "rounded up"
// to INFINITE. :)
//
if (dwTimeout > 4000000) // avoid overflow
{
dwTimeout = INFINITE;
}
else
{
// convert to milliseconds
dwTimeout *= 1000;
}
DWORD dwStartTime;
DWORD dwWaitResult;
bool bWaitAgain;
do
{
dwStartTime = GetTickCount();
dwWaitResult = MsgWaitForMultipleObjects(1, &_hWorkerThread,
FALSE,
dwTimeout,
QS_ALLINPUT);
bWaitAgain = false;
switch (dwWaitResult)
{
case WAIT_OBJECT_0:
// Thread exited.
MessageLoop();
hr = _hrAsyncResult;
bSucceeded = SUCCEEDED(hr);
break;
case WAIT_OBJECT_0 + 1:
// Message waiting in the message queue.
// Run message pump to clear queue.
MessageLoop();
bWaitAgain = true;
break;
case WAIT_TIMEOUT:
// Timeout.
bSucceeded = false;
break;
case (-1):
default:
// Error.
goto ErrorFail;
break;
}
// If we're going to continue waiting for the worker
// thread, decrease timeout appropriately.
if (bWaitAgain)
{
dwTimeout = UpdateTimeout(dwTimeout, dwStartTime);
}
} while (bWaitAgain);
}
else
{
// If the worker thread is already done, then pump messages
// to clear any events that it may have posted.
MessageLoop();
hr = _hrAsyncResult;
bSucceeded = SUCCEEDED(hr);
}
}
Cleanup:
if (pboolSucceeded)
{
*pboolSucceeded = (bSucceeded ? VARIANT_TRUE : VARIANT_FALSE);
}
SetErrorInfo(hr);
return hr;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Error;
ErrorCannotCallBeforeSend:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND);
goto Error;
Error:
bSucceeded = false;
goto Cleanup;
}
STDMETHODIMP
CHttpRequest::Abort()
{
//
// Abort if not already aborted and not in the CREATED state,
// (meaning at least the Open method has been called).
//
if ((_eState > CHttpRequest::CREATED) && !_bAborted)
{
DWORD error;
_bAborted = true;
// Tell our connection point manager to abort any
// events "in flight"--i.e., abort any events that
// may have already been posted by the worker thread.
_CP.FreezeEvents();
if (_hHTTP)
{
//
// Add a ref count on the HTTP Request handle.
//
INET_ASSERT(_hAbortedRequestObject == NULL);
error = MapHandleToAddress(_hHTTP, (LPVOID *)&_hAbortedRequestObject, FALSE);
INET_ASSERT(error == 0);
WinHttpCloseHandle(_hHTTP);
}
if (_hConnection)
{
//
// Add a ref count on the Connection handle.
//
INET_ASSERT(_hAbortedConnectObject == NULL);
error = MapHandleToAddress(_hConnection, (LPVOID *)&_hAbortedConnectObject, FALSE);
INET_ASSERT(error == 0);
WinHttpCloseHandle(_hConnection);
}
// Recycle the object.
Recycle();
}
return NOERROR;
}
STDMETHODIMP
CHttpRequest::SetTimeouts(long ResolveTimeout, long ConnectTimeout, long SendTimeout, long ReceiveTimeout)
{
if ((ResolveTimeout < -1L) || (ConnectTimeout < -1L) ||
(SendTimeout < -1L) || (ReceiveTimeout < -1L))
{
return E_INVALIDARG;
}
HRESULT hr = NOERROR;
_ResolveTimeout = (DWORD) ResolveTimeout;
_ConnectTimeout = (DWORD) ConnectTimeout;
_SendTimeout = (DWORD) SendTimeout;
_ReceiveTimeout = (DWORD) ReceiveTimeout;
_bSetTimeouts = true;
if (_hHTTP)
{
DWORD fRetCode;
fRetCode = WinHttpSetTimeouts(_hHTTP, (int)_ResolveTimeout,
(int)_ConnectTimeout,
(int)_SendTimeout,
(int)_ReceiveTimeout);
if (!fRetCode)
hr = E_INVALIDARG;
}
return hr;
}
/*
* CHttpRequest::SetRequestBody
*
* Purpose:
* Set the request body
*
* Parameters:
* varBody IN Request body
*
* Errors:
* E_FAIL
* E_OUTOFMEMORY
* E_UNEXPECTED
*/
HRESULT
CHttpRequest::SetRequestBody(VARIANT varBody)
{
HRESULT hr = NOERROR;
VARIANT varTemp;
BSTR bstrBody = NULL;
SAFEARRAY * psa = NULL;
VARIANT * pvarBody = NULL;
IStream * pStm = NULL;
// varBody is validated by CHttpRequest::Send().
VariantInit(&varTemp);
// Free a previously set body and its response
if (_szRequestBuffer)
{
delete [] _szRequestBuffer;
_szRequestBuffer = NULL;
_cbRequestBody = 0;
}
if (_szResponseBuffer)
{
delete [] _szResponseBuffer;
_szResponseBuffer = NULL;
_cbResponseBuffer = 0;
_cbResponseBody = 0;
}
if (V_ISBYREF(&varBody))
{
pvarBody = varBody.pvarVal;
}
else
{
pvarBody = &varBody;
}
// Check for an empty body
if (V_VT(pvarBody) == VT_EMPTY ||
V_VT(pvarBody) == VT_NULL ||
V_VT(pvarBody) == VT_ERROR)
goto Cleanup;
//
// Extract the body: BSTR or array of UI1
//
// We need to explicitly look for the byte array since it will be converted
// to a BSTR by VariantChangeType.
if (V_ISARRAY(pvarBody) && (V_VT(pvarBody) & VT_UI1))
{
BYTE * pb = NULL;
long lUBound = 0;
long lLBound = 0;
psa = V_ARRAY(pvarBody);
// We only handle 1 dimensional arrays
if (SafeArrayGetDim(psa) != 1)
goto ErrorFail;
// Get access to the blob
hr = SafeArrayAccessData(psa, (void **)&pb);
if (FAILED(hr))
goto Error;
// Compute the data size from the upper and lower array bounds
hr = SafeArrayGetLBound(psa, 1, &lLBound);
if (FAILED(hr))
goto Error;
hr = SafeArrayGetUBound(psa, 1, &lUBound);
if (FAILED(hr))
goto Error;
_cbRequestBody = lUBound - lLBound + 1;
if (_cbRequestBody > 0)
{
// Copy the data into the request buffer
_szRequestBuffer = New char [_cbRequestBody];
if (!_szRequestBuffer)
{
_cbRequestBody = 0;
goto ErrorOutOfMemory;
}
::memcpy(_szRequestBuffer, pb, _cbRequestBody);
}
SafeArrayUnaccessData(psa);
psa = NULL;
}
else
{
// Try a BSTR
bstrBody = GetBSTRFromVariant(*pvarBody);
if (bstrBody)
{
hr = BSTRToUTF8(&_szRequestBuffer, &_cbRequestBody, bstrBody);
if (FAILED(hr))
goto Error;
}
else
{
// Try a Stream
if (V_VT(pvarBody) == VT_UNKNOWN || V_VT(pvarBody) == VT_DISPATCH)
{
IStream * pStm;
hr = pvarBody->punkVal->QueryInterface(
IID_IStream,
(void **)&pStm);
if (FAILED(hr))
goto Error;
hr = ReadFromStream(
&_szRequestBuffer,
&_cbRequestBody,
pStm);
pStm->Release();
if (FAILED(hr))
goto Error;
}
}
}
hr = NOERROR;
Cleanup:
VariantClear(&varTemp);
if (psa)
SafeArrayUnaccessData(psa);
if (bstrBody)
SysFreeString(bstrBody);
return hr;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Error;
Error:
goto Cleanup;
}
/*
* CHttpRequest::GetResponseHeader
*
* Purpose:
* Get a response header
*
* Parameters:
* bstrHeader IN HTTP request header
* pbstrValue OUT Header value
*
* Errors:
* E_FAIL
* E_INVALIDARG
* E_OUTOFMEMORY
* E_UNEXPECTED
* Errors from WinHttpQueryHeaders
*/
STDMETHODIMP
CHttpRequest::GetResponseHeader(BSTR bstrHeader, BSTR * pbstrValue)
{
return _GetResponseHeader(bstrHeader, pbstrValue);
}
HRESULT
CHttpRequest::_GetResponseHeader(OLECHAR * wszHeader, BSTR * pbstrValue)
{
HRESULT hr = NOERROR;
WCHAR * wszHeaderValue = NULL;
DWORD cb;
BOOL fRetCode;
// Validate parameters
if (IsBadReadPtr(wszHeader, 2) ||
IsBadWritePtr(pbstrValue, sizeof(BSTR)) ||
!lstrlenW(wszHeader))
return E_INVALIDARG;
// Validate state
if (_eState < CHttpRequest::SENDING)
goto ErrorCannotCallBeforeSend;
*pbstrValue = NULL;
cb = 64; // arbitrary size in which many header values will fit
wszHeaderValue = New WCHAR[cb];
if (!wszHeaderValue)
goto ErrorOutOfMemory;
RetryQuery:
// Determine length of requested header
fRetCode = WinHttpQueryHeaders(
_hHTTP,
HTTP_QUERY_CUSTOM,
wszHeader,
wszHeaderValue,
&cb,
0);
// Check for ERROR_INSUFFICIENT_BUFFER - reallocate the buffer and retry
if (!fRetCode)
{
switch (GetLastError())
{
case ERROR_INSUFFICIENT_BUFFER:
{
delete [] wszHeaderValue;
wszHeaderValue = New WCHAR[cb]; // should this be cb/2?
if (!wszHeaderValue)
goto ErrorOutOfMemory;
goto RetryQuery;
}
case ERROR_HTTP_HEADER_NOT_FOUND:
goto ErrorFail;
default:
goto ErrorFail;
}
}
*pbstrValue = SysAllocString(wszHeaderValue);
if (!*pbstrValue)
goto ErrorOutOfMemory;
hr = NOERROR;
Cleanup:
if (wszHeaderValue)
delete [] wszHeaderValue;
SetErrorInfo(hr);
return hr;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Error;
ErrorCannotCallBeforeSend:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND);
goto Error;
Error:
goto Cleanup;
}
/*
* CHttpRequest::GetAllResponseHeaders
*
* Purpose:
* Return the response headers
*
* Parameters:
* pbstrHeaders IN/OUT CRLF delimited headers
*
* Errors:
* E_FAIL
* E_INVALIDARG
* E_OUTOFMEMORY
* E_UNEXPECTED
*/
STDMETHODIMP
CHttpRequest::GetAllResponseHeaders(BSTR * pbstrHeaders)
{
HRESULT hr = NOERROR;
BOOL fRetCode;
WCHAR * wszAllHeaders = NULL;
WCHAR * wszFirstHeader = NULL;
DWORD cb = 0;
// Validate parameter
if (IsBadWritePtr(pbstrHeaders, sizeof(BSTR)))
return E_INVALIDARG;
// Validate state
if (_eState < CHttpRequest::SENDING)
goto ErrorCannotCallBeforeSend;
*pbstrHeaders = NULL;
RetryQuery:
// Determine the length of all headers and then get all the headers
fRetCode = WinHttpQueryHeaders(
_hHTTP,
HTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX,
wszAllHeaders,
&cb,
0);
if (!fRetCode)
{
// Allocate a buffer large enough to hold all headers
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
wszAllHeaders = New WCHAR[cb];
if (!wszAllHeaders)
goto ErrorOutOfMemory;
goto RetryQuery;
}
else
{
goto ErrorFail;
}
}
// Bypass status line - jump past first CRLF (0x13, 0x10)
wszFirstHeader = wcschr(wszAllHeaders, '\n');
if (*wszFirstHeader == '\n')
{
*pbstrHeaders = SysAllocString(wszFirstHeader + 1);
if (!*pbstrHeaders)
goto ErrorOutOfMemory;
}
hr = NOERROR;
Cleanup:
if (wszAllHeaders)
delete [] wszAllHeaders;
SetErrorInfo(hr);
return hr;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Error;
ErrorCannotCallBeforeSend:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND);
goto Error;
Error:
if (pbstrHeaders)
{
SysFreeString(*pbstrHeaders);
*pbstrHeaders = NULL;
}
goto Cleanup;
}
/*
* CHttpRequest::get_status
*
* Purpose:
* Get the request status code
*
* Parameters:
* plStatus OUT HTTP request status code
*
* Errors:
* E_FAIL
* E_INVALIDARG
* E_UNEXPECTED
*/
STDMETHODIMP
CHttpRequest::get_Status(long * plStatus)
{
HRESULT hr = NOERROR;
DWORD cb = sizeof(DWORD);
BOOL fRetCode;
DWORD dwStatus;
// Validate parameter
if (IsBadWritePtr(plStatus, sizeof(long)))
return E_INVALIDARG;
// Validate state
if (_eState < CHttpRequest::SENDING)
goto ErrorCannotCallBeforeSend;
fRetCode = HttpQueryInfoA(
_hHTTP,
HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX,
&dwStatus,
&cb,
0);
if (!fRetCode)
goto ErrorFail;
*plStatus = dwStatus;
hr = NOERROR;
Cleanup:
SetErrorInfo(hr);
return hr;
ErrorCannotCallBeforeSend:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND);
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Error;
Error:
goto Cleanup;
}
/*
* CHttpRequest::get_StatusText
*
* Purpose:
* Get the request status text
*
* Parameters:
* pbstrStatus OUT HTTP request status text
*
* Errors:
* E_FAIL
* E_INVALIDARG
* E_UNEXPECTED
*/
STDMETHODIMP
CHttpRequest::get_StatusText(BSTR * pbstrStatus)
{
HRESULT hr = NOERROR;
WCHAR wszStatusText[32];
WCHAR * pwszStatusText = wszStatusText;
BOOL fFreeStatusString = FALSE;
DWORD cb;
BOOL fRetCode;
// Validate parameter
if (IsBadWritePtr(pbstrStatus, sizeof(BSTR)))
return E_INVALIDARG;
// Validate state
if (_eState < CHttpRequest::SENDING)
goto ErrorCannotCallBeforeSend;
*pbstrStatus = NULL;
cb = sizeof(wszStatusText) / sizeof(WCHAR);
RetryQuery:
fRetCode = WinHttpQueryHeaders(
_hHTTP,
HTTP_QUERY_STATUS_TEXT,
WINHTTP_HEADER_NAME_BY_INDEX,
pwszStatusText,
&cb,
0);
if (!fRetCode)
{
// Check for ERROR_INSUFFICIENT_BUFFER error
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
// Reallocate the status text buffer
if (fFreeStatusString)
delete pwszStatusText;
pwszStatusText = New WCHAR[cb];
if (!pwszStatusText)
goto ErrorOutOfMemory;
fFreeStatusString = TRUE;
goto RetryQuery;
}
else
{
goto ErrorFail;
}
}
// Convert the status text to a BSTR
*pbstrStatus = SysAllocString(pwszStatusText);
if (!*pbstrStatus)
goto ErrorOutOfMemory;
hr = NOERROR;
Cleanup:
if (fFreeStatusString)
delete [] pwszStatusText;
SetErrorInfo(hr);
return hr;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
ErrorCannotCallBeforeSend:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND);
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Cleanup;
Error:
goto Cleanup;
}
/*
* CHttpRequest::get_ResponseBody
*
* Purpose:
* Retrieve the response body as a SAFEARRAY of UI1
*
* Parameters:
* pvarBody OUT Response blob
*
* Errors:
* E_INVALIDARG
* E_UNEXPECTED
* E_PENDING
*/
STDMETHODIMP
CHttpRequest::get_ResponseBody(VARIANT * pvarBody)
{
HRESULT hr = NOERROR;
// Validate parameter
if (IsBadWritePtr(pvarBody, sizeof(VARIANT)))
return E_INVALIDARG;
// Validate state
if (_eState < CHttpRequest::SENDING)
goto ErrorCannotCallBeforeSend;
else if (_eState < CHttpRequest::RESPONSE)
goto ErrorPending;
VariantInit(pvarBody);
if (_szResponseBuffer)
{
hr = CreateVector(
pvarBody,
(const BYTE *)_szResponseBuffer,
_cbResponseBody);
if (FAILED(hr))
goto Error;
}
else
{
V_VT(pvarBody) = VT_EMPTY;
}
hr = NOERROR;
Cleanup:
SetErrorInfo(hr);
return hr;
ErrorCannotCallBeforeSend:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND);
goto Error;
ErrorPending:
hr = E_PENDING;
goto Error;
Error:
if (pvarBody)
VariantClear(pvarBody);
goto Cleanup;
}
/*
* CHttpRequest::get_ResponseText
*
* Purpose:
* Retrieve the response body as a BSTR
*
* Parameters:
* pbstrBody OUT response as a BSTR
*
* Errors:
* E_INVALIDARG
* E_OUTOFMEMORY
* E_UNEXPECTED
* E_PENDING
*/
STDMETHODIMP
CHttpRequest::get_ResponseText(BSTR * pbstrBody)
{
HRESULT hr;
// Validate parameter
if (IsBadWritePtr(pbstrBody, sizeof(BSTR)))
return E_INVALIDARG;
// Validate state
if (_eState < CHttpRequest::SENDING)
goto ErrorCannotCallBeforeSend;
else if (_eState < CHttpRequest::RESPONSE)
goto ErrorPending;
hr = AsciiToBSTR(pbstrBody, _szResponseBuffer, (int)_cbResponseBody);
if (FAILED(hr))
goto Error;
hr = NOERROR;
Cleanup:
SetErrorInfo(hr);
return hr;
ErrorCannotCallBeforeSend:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND);
goto Error;
ErrorPending:
hr = E_PENDING;
goto Error;
Error:
goto Cleanup;
}
STDMETHODIMP
CHttpRequest::get_ResponseStream(VARIANT * pvarBody)
{
HRESULT hr = NOERROR;
IStream * pStm = NULL;
// Validate parameter
if (IsBadWritePtr(pvarBody, sizeof(VARIANT)))
return E_INVALIDARG;
// Validate state
if (_eState < CHttpRequest::SENDING)
goto ErrorCannotCallBeforeSend;
else if (_eState < CHttpRequest::RESPONSE)
goto ErrorPending;
VariantInit(pvarBody);
hr = CreateStreamOnResponse(&pStm);
if (FAILED(hr))
goto Error;
V_VT(pvarBody) = VT_UNKNOWN;
pvarBody->punkVal = pStm;
hr = NOERROR;
Cleanup:
SetErrorInfo(hr);
return hr;
ErrorCannotCallBeforeSend:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND);
goto Error;
ErrorPending:
hr = E_PENDING;
goto Error;
Error:
if (pvarBody)
VariantClear(pvarBody);
goto Cleanup;
}
void
CHttpRequest::SetState(State state)
{
if (_fAsync)
{
InterlockedExchange((long *)&_eState, state);
}
else
{
_eState = state;
}
}
/*
* CHttpRequest::CreateStreamOnResponse
*
* Purpose:
* Create a Stream containing the Response data
*
* Parameters:
* ppStm IN/OUT Stream
*
* Errors:
* E_INVALIDARG
* E_OUTOFMEMORY
*/
HRESULT
CHttpRequest::CreateStreamOnResponse(IStream ** ppStm)
{
HRESULT hr = NOERROR;
ULONG cbWritten;
LARGE_INTEGER liReset = { 0 };
if (!ppStm)
return E_INVALIDARG;
*ppStm = NULL;
hr = CreateStreamOnHGlobal(NULL, TRUE, ppStm);
if (FAILED(hr))
goto ErrorOutOfMemory;
hr = (*ppStm)->Write(_szResponseBuffer, _cbResponseBody, &cbWritten);
if (FAILED(hr))
goto Error;
hr = (*ppStm)->Seek(liReset, STREAM_SEEK_SET, NULL);
if (FAILED(hr))
goto Error;
hr = NOERROR;
Cleanup:
return hr;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
Error:
if (ppStm)
SafeRelease(*ppStm);
goto Cleanup;
}
STDMETHODIMP
CHttpRequest::get_Option(WinHttpRequestOption Option, VARIANT * Value)
{
HRESULT hr;
if (IsBadWritePtr(Value, sizeof(VARIANT)))
return E_INVALIDARG;
switch (Option)
{
case WinHttpRequestOption_UserAgentString:
{
V_BSTR(Value) = SysAllocString(GetUserAgentString());
if (V_BSTR(Value) == NULL)
goto ErrorOutOfMemory;
V_VT(Value) = VT_BSTR;
break;
}
case WinHttpRequestOption_URL:
{
WCHAR * pwszUrl = NULL;
DWORD dwBufferSize = 0;
if (_eState < CHttpRequest::OPENED)
goto ErrorCannotCallBeforeOpen;
if (!WinHttpQueryOption(_hHTTP, WINHTTP_OPTION_URL, NULL, &dwBufferSize) &&
(GetLastError() == ERROR_INSUFFICIENT_BUFFER))
{
pwszUrl = New WCHAR[dwBufferSize + 1];
if (!pwszUrl)
goto ErrorOutOfMemory;
if (WinHttpQueryOption(_hHTTP, WINHTTP_OPTION_URL, pwszUrl, &dwBufferSize))
{
V_BSTR(Value) = SysAllocString(pwszUrl);
V_VT(Value) = VT_BSTR;
hr = NOERROR;
}
else
{
hr = E_FAIL;
}
delete [] pwszUrl;
if (FAILED(hr))
{
goto ErrorFail;
}
else if (V_BSTR(Value) == NULL)
{
goto ErrorOutOfMemory;
}
}
break;
}
case WinHttpRequestOption_URLCodePage:
V_I4(Value) = (long) _dwCodePage;
V_VT(Value) = VT_I4;
break;
case WinHttpRequestOption_EscapePercentInURL:
V_BOOL(Value) = (_dwEscapePercentFlag==WINHTTP_FLAG_ESCAPE_PERCENT) ? VARIANT_TRUE : VARIANT_FALSE;
V_VT(Value) = VT_BOOL;
break;
case WinHttpRequestOption_EnableRedirects:
V_BOOL(Value) = _bDisableRedirects ? VARIANT_FALSE : VARIANT_TRUE;
V_VT(Value) = VT_BOOL;
break;
default:
VariantInit(Value);
return E_INVALIDARG;
}
hr = NOERROR;
Cleanup:
SetErrorInfo(hr);
return hr;
ErrorCannotCallBeforeOpen:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN);
goto Error;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Error;
Error:
goto Cleanup;
}
STDMETHODIMP
CHttpRequest::put_Option(WinHttpRequestOption Option, VARIANT Value)
{
HRESULT hr;
if (!IsValidVariant(Value))
return E_INVALIDARG;
switch (Option)
{
case WinHttpRequestOption_UserAgentString:
{
BSTR bstrUserAgent = GetBSTRFromVariant(Value);
if (!bstrUserAgent || (*bstrUserAgent == L'\0'))
return E_INVALIDARG;
if (_hInet)
{
if (!WinHttpSetOption(_hInet, WINHTTP_OPTION_USER_AGENT,
bstrUserAgent,
SysStringLen(bstrUserAgent)))
{
goto ErrorFail;
}
}
if (_hHTTP)
{
if (!WinHttpSetOption(_hHTTP, WINHTTP_OPTION_USER_AGENT,
bstrUserAgent,
SysStringLen(bstrUserAgent)))
{
goto ErrorFail;
}
}
if (_bstrUserAgent)
SysFreeString(_bstrUserAgent);
_bstrUserAgent = bstrUserAgent;
break;
}
case WinHttpRequestOption_URL:
// The URL cannot be set using the Option property.
return E_INVALIDARG;
case WinHttpRequestOption_URLCodePage:
{
_dwCodePage = GetDwordFromVariant(Value, CP_UTF8);
if (_hInet)
{
if (!WinHttpSetOption(_hInet, WINHTTP_OPTION_CODEPAGE,
&_dwCodePage,
sizeof(_dwCodePage)))
goto ErrorFail;
}
if (_hConnection)
{
if (!WinHttpSetOption(_hConnection, WINHTTP_OPTION_CODEPAGE,
&_dwCodePage,
sizeof(_dwCodePage)))
goto ErrorFail;
}
break;
}
case WinHttpRequestOption_EscapePercentInURL:
{
BOOL fEscapePercent = GetBoolFromVariant(Value, FALSE);
_dwEscapePercentFlag = (fEscapePercent ? WINHTTP_FLAG_ESCAPE_PERCENT : 0);
break;
}
case WinHttpRequestOption_SslErrorIgnoreFlags:
{
DWORD dwSslIgnoreFlags = GetDwordFromVariant(Value, _dwSslIgnoreFlags);
if (dwSslIgnoreFlags & ~SECURITY_INTERNET_MASK)
{
return E_INVALIDARG;
}
// Break automatically, so we don't need the overhead of a callback.
dwSslIgnoreFlags |= SECURITY_FLAG_BREAK_ON_STATUS_SECURE_FAILURE;
if (_hHTTP)
{
// Set the SSL ignore flags through an undocumented front door
if (!WinHttpSetOption(_hHTTP,
WINHTTP_OPTION_SECURITY_FLAGS,
(LPVOID)&dwSslIgnoreFlags,
sizeof(dwSslIgnoreFlags)))
goto ErrorFail;
}
_dwSslIgnoreFlags = dwSslIgnoreFlags;
break;
}
case WinHttpRequestOption_SelectCertificate:
{
BSTR bstrCertSubject = GetBSTRFromVariant(Value);
if (!bstrCertSubject)
{
// Allow all empty calls to be interpreted as "first enum"
bstrCertSubject = SysAllocString(L"");
if (!bstrCertSubject)
return E_OUTOFMEMORY;
}
if (_bstrCertSubject)
SysFreeString(_bstrCertSubject);
_bstrCertSubject = bstrCertSubject;
break;
}
case WinHttpRequestOption_EnableRedirects:
{
BOOL fEnableRedirects = GetBoolFromVariant(Value, TRUE);
_bDisableRedirects = fEnableRedirects ? false : true;
break;
}
default:
return E_INVALIDARG;
}
hr = NOERROR;
Cleanup:
SetErrorInfo(hr);
return hr;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Error;
Error:
goto Cleanup;
}
void
CHttpRequest::SetErrorInfo(HRESULT hr)
{
if (SUCCEEDED(hr))
return;
ICreateErrorInfo * pCErrInfo = NULL;
IErrorInfo * pErrInfo = NULL;
DWORD error = hr;
DWORD dwFmtMsgFlag = FORMAT_MESSAGE_FROM_SYSTEM;
HMODULE hModule = NULL;
DWORD rc;
LPWSTR pwszMessage = NULL;
const DWORD dwSize = 512;
pwszMessage = New WCHAR[dwSize];
if (pwszMessage == NULL)
return;
if (HRESULT_FACILITY(hr) == FACILITY_WIN32)
{
DWORD errcode = HRESULT_CODE(hr);
if ((errcode > WINHTTP_ERROR_BASE) && (errcode <= WINHTTP_ERROR_LAST))
{
dwFmtMsgFlag = FORMAT_MESSAGE_FROM_HMODULE;
hModule = GetModuleHandle("WINHTTP5.DLL");
error = errcode;
}
}
rc = ::FormatMessageW(dwFmtMsgFlag, hModule,
error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
pwszMessage,
dwSize,
NULL);
if (rc != 0)
{
if (SUCCEEDED(::CreateErrorInfo(&pCErrInfo)))
{
if (SUCCEEDED(pCErrInfo->QueryInterface(IID_IErrorInfo, (void **) &pErrInfo)))
{
pCErrInfo->SetSource(L"WinHttp.WinHttpRequest");
pCErrInfo->SetGUID(IID_IWinHttpRequest);
pCErrInfo->SetDescription(pwszMessage);
::SetErrorInfo(0, pErrInfo);
pErrInfo->Release();
}
pCErrInfo->Release();
}
}
delete [] pwszMessage;
}
BOOL
CHttpRequest::SelectCertificate()
{
HCERTSTORE hMyStore = NULL;
BOOL fRet = FALSE;
HANDLE hThreadToken = NULL;
PCCERT_CONTEXT pCertContext = NULL;
// If impersonating, revert while trying to obtain the cert
if (OpenThreadToken(GetCurrentThread(), (TOKEN_IMPERSONATE | TOKEN_READ),
FALSE,
&hThreadToken))
{
INET_ASSERT(hThreadToken != 0);
RevertToSelf();
}
// For now, just pick the first enum available
// based on a simple CERT_ FIND_SUBJECT_STR criteria
// If the user selected an empty string, then simply
// pick the first enum.
hMyStore = CertOpenSystemStore(NULL, "MY");
if (!hMyStore)
{
goto Cleanup;
}
if (_bstrCertSubject && _bstrCertSubject[0])
{
pCertContext = CertFindCertificateInStore(hMyStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR,
(LPVOID) _bstrCertSubject,
NULL);
}
else
{
pCertContext = CertFindCertificateInStore(hMyStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0,
CERT_FIND_ANY,
NULL,
NULL);
}
// Winhttp5 will add a couple of new options.
// One will be an option to request that client auth
// certs are not cached, so they can be session based.
// Another will be for flushing the SSL cache on demand.
//
// For now, set this via a private method off of the
// request object.
if (pCertContext)
{
fRet = WinHttpSetOption(_hHTTP,
WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
(LPVOID) pCertContext,
sizeof(CERT_CONTEXT));
}
Cleanup:
if (pCertContext)
CertFreeCertificateContext(pCertContext);
if (hMyStore)
CertCloseStore(hMyStore, 0);
// Restore the impersonating state for the current thread.
if (hThreadToken)
{
SetThreadToken(NULL, hThreadToken);
CloseHandle(hThreadToken);
}
return fRet;
}
/*
* BSTRToUTF8
*
* Purpose:
* Convert a BSTR to UTF-8
*
*/
static
HRESULT
BSTRToUTF8(char ** psz, DWORD * pcbUTF8, BSTR bstr)
{
UINT cch = lstrlenW(bstr);
*pcbUTF8 = 0;
*psz = NULL;
if (cch == 0)
return NOERROR;
// Allocate the maximum buffer the UTF-8 string could be
*psz = New char [(cch * 3) + 1];
if (!*psz)
return E_OUTOFMEMORY;
*pcbUTF8 = cch * 3;
WideCharToUtf8(bstr, cch, (BYTE *)*psz, (UINT *)pcbUTF8);
(*psz)[*pcbUTF8] = 0;
return NOERROR;
}
/**
* Scans buffer and translates Unicode characters into UTF8 characters
*/
static
void
WideCharToUtf8(
WCHAR * buffer,
UINT cch,
BYTE * bytebuffer,
UINT * cb)
{
UINT count = 0, m1 = *cb, m2 = m1 - 1, m3 = m2 - 1, m4 = m3 - 1;
DWORD dw1;
bool surrogate = false;
for (UINT i = cch; i > 0; i--)
{
DWORD dw = *buffer;
if (surrogate) // is it the second char of a surrogate pair?
{
if (dw >= 0xdc00 && dw <= 0xdfff)
{
// four bytes 0x11110xxx 0x10xxxxxx 0x10xxxxxx 0x10xxxxxx
if (count < m4)
count += 4;
else
break;
ULONG ucs4 = (dw1 - 0xd800) * 0x400 + (dw - 0xdc00) + 0x10000;
*bytebuffer++ = (byte)(( ucs4 >> 18) | 0xF0);
*bytebuffer++ = (byte)((( ucs4 >> 12) & 0x3F) | 0x80);
*bytebuffer++ = (byte)((( ucs4 >> 6) & 0x3F) | 0x80);
*bytebuffer++ = (byte)(( ucs4 & 0x3F) | 0x80);
surrogate = false;
buffer++;
continue;
}
else // Then dw1 must be a three byte character
{
if (count < m3)
count += 3;
else
break;
*bytebuffer++ = (byte)(( dw1 >> 12) | 0xE0);
*bytebuffer++ = (byte)((( dw1 >> 6) & 0x3F) | 0x80);
*bytebuffer++ = (byte)(( dw1 & 0x3F) | 0x80);
}
surrogate = false;
}
if (dw < 0x80) // one byte, 0xxxxxxx
{
if (count < m1)
count++;
else
break;
*bytebuffer++ = (byte)dw;
}
else if ( dw < 0x800) // two WORDS, 110xxxxx 10xxxxxx
{
if (count < m2)
count += 2;
else
break;
*bytebuffer++ = (byte)((dw >> 6) | 0xC0);
*bytebuffer++ = (byte)((dw & 0x3F) | 0x80);
}
else if (dw >= 0xd800 && dw <= 0xdbff) // Assume that it is the first char of surrogate pair
{
if (i == 1) // last wchar in buffer
break;
dw1 = dw;
surrogate = true;
}
else // three bytes, 1110xxxx 10xxxxxx 10xxxxxx
{
if (count < m3)
count += 3;
else
break;
*bytebuffer++ = (byte)(( dw >> 12) | 0xE0);
*bytebuffer++ = (byte)((( dw >> 6) & 0x3F) | 0x80);
*bytebuffer++ = (byte)(( dw & 0x3F) | 0x80);
}
buffer++;
}
*cb = count;
}
/*
* AsciiToBSTR
*
* Purpose:
* Convert an ascii string to a BSTR
*
*/
static
HRESULT
AsciiToBSTR(BSTR * pbstr, char * sz, int cch)
{
int cwch;
// Determine how big the ascii string will be
cwch = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, cch,
NULL, 0);
*pbstr = SysAllocStringLen(NULL, cwch);
if (!*pbstr)
return E_OUTOFMEMORY;
cch = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, cch,
*pbstr, cwch);
return NOERROR;
}
/*
* BSTRToAscii
*
* Purpose:
* Convert a BSTR to an ascii string
*
*/
static
HRESULT
BSTRToAscii(char ** psz, BSTR bstr)
{
int cch;
*psz = NULL;
if (!bstr)
return S_OK;
// Determine how big the ascii string will be
cch = WideCharToMultiByte(CP_ACP, 0, bstr, SysStringLen(bstr),
NULL, 0, NULL, NULL);
*psz = New char[cch + 1];
if (!*psz)
return E_OUTOFMEMORY;
// Determine how big the ascii string will be
cch = WideCharToMultiByte(CP_ACP, 0, bstr, SysStringLen(bstr),
*psz, cch, NULL, NULL);
(*psz)[cch] = 0;
return S_OK;
}
/*
* GetBSTRFromVariant
*
* Purpose:
* Convert a VARIANT to a BSTR
*
*/
static BSTR GetBSTRFromVariant(VARIANT varVariant)
{
VARIANT varTemp;
HRESULT hr;
BSTR bstrResult = NULL;
if (V_VT(&varVariant) != VT_EMPTY && V_VT(&varVariant) != VT_NULL &&
V_VT(&varVariant) != VT_ERROR)
{
VariantInit(&varTemp);
hr = VariantChangeType(
&varTemp,
&varVariant,
0,
VT_BSTR);
if (FAILED(hr))
goto Error;
bstrResult = V_BSTR(&varTemp); // take over ownership of BSTR
}
hr = NOERROR;
Cleanup:
// DON'T clear the variant because we stole the BSTR
//VariantClear(&varTemp);
return bstrResult;
Error:
goto Cleanup;
}
/*
* GetBoolFromVariant
*
* Purpose:
* Convert a VARIANT to a Boolean
*
*/
static BOOL GetBoolFromVariant(VARIANT varVariant, BOOL fDefault)
{
HRESULT hr;
BOOL fResult = fDefault;
if (V_VT(&varVariant) != VT_EMPTY && V_VT(&varVariant) != VT_NULL &&
V_VT(&varVariant) != VT_ERROR)
{
VARIANT varTemp;
VariantInit(&varTemp);
hr = VariantChangeType(
&varTemp,
&varVariant,
0,
VT_BOOL);
if (FAILED(hr))
goto Cleanup;
fResult = V_BOOL(&varTemp) == VARIANT_TRUE ? TRUE : FALSE;
}
hr = NOERROR;
Cleanup:
return fResult;
}
/*
* GetDwordFromVariant
*
* Purpose:
* Convert a VARIANT to a DWORD
*
*/
static DWORD GetDwordFromVariant(VARIANT varVariant, DWORD dwDefault)
{
HRESULT hr;
DWORD dwResult = dwDefault;
if (V_VT(&varVariant) != VT_EMPTY && V_VT(&varVariant) != VT_NULL &&
V_VT(&varVariant) != VT_ERROR)
{
VARIANT varTemp;
VariantInit(&varTemp);
hr = VariantChangeType(
&varTemp,
&varVariant,
0,
VT_UI4);
if (FAILED(hr))
goto Cleanup;
dwResult = V_UI4(&varTemp);
}
hr = NOERROR;
Cleanup:
return dwResult;
}
/*
* GetDwordFromVariant
*
* Purpose:
* Convert a VARIANT to a DWORD
*
*/
static long GetLongFromVariant(VARIANT varVariant, long lDefault)
{
HRESULT hr;
long lResult = lDefault;
if (V_VT(&varVariant) != VT_EMPTY && V_VT(&varVariant) != VT_NULL &&
V_VT(&varVariant) != VT_ERROR)
{
VARIANT varTemp;
VariantInit(&varTemp);
hr = VariantChangeType(
&varTemp,
&varVariant,
0,
VT_I4);
if (FAILED(hr))
goto Cleanup;
lResult = V_I4(&varTemp);
}
hr = NOERROR;
Cleanup:
return lResult;
}
/**
* Helper to create a char safearray from a string
*/
static
HRESULT
CreateVector(VARIANT * pVar, const BYTE * pData, DWORD cElems)
{
HRESULT hr;
BYTE * pB;
SAFEARRAY * psa = SafeArrayCreateVector(VT_UI1, 0, cElems);
if (!psa)
{
hr = E_OUTOFMEMORY;
goto Cleanup;
}
hr = SafeArrayAccessData(psa, (void **)&pB);
if (hr)
goto Error;
memcpy(pB, pData, cElems);
SafeArrayUnaccessData(psa);
INET_ASSERT((pVar->vt == VT_EMPTY) || (pVar->vt == VT_NULL));
V_ARRAY(pVar) = psa;
pVar->vt = VT_ARRAY | VT_UI1;
hr = NOERROR;
Cleanup:
return hr;
Error:
if (psa)
SafeArrayDestroy(psa);
goto Cleanup;
}
/*
* ReadFromStream
*
* Purpose:
* Extract the contents of a stream into a buffer.
*
* Parameters:
* ppBuf IN/OUT Buffer
* pStm IN Stream
*
* Errors:
* E_INVALIDARG
* E_OUTOFMEMORY
*/
static
HRESULT
ReadFromStream(char ** ppData, ULONG * pcbData, IStream * pStm)
{
HRESULT hr = NOERROR;
char * pBuffer = NULL; // Buffer
ULONG cbBuffer = 0; // Bytes in buffer
ULONG cbData = 0; // Bytes of data in buffer
ULONG cbRead = 0; // Bytes read from stream
ULONG cbNewSize = 0;
char * pNewBuf = NULL;
if (!ppData || !pStm)
return E_INVALIDARG;
*ppData = NULL;
*pcbData = 0;
while (TRUE)
{
if (cbData + 512 > cbBuffer)
{
cbNewSize = (cbData ? cbData*2 : 4096);
pNewBuf = New char[cbNewSize+1];
if (!pNewBuf)
goto ErrorOutOfMemory;
if (cbData)
::memcpy(pNewBuf, pBuffer, cbData);
cbBuffer = cbNewSize;
delete[] pBuffer;
pBuffer = pNewBuf;
pBuffer[cbData] = 0;
}
hr = pStm->Read(
&pBuffer[cbData],
cbBuffer - cbData,
&cbRead);
if (FAILED(hr))
goto Error;
cbData += cbRead;
pBuffer[cbData] = 0;
// No more data
if (cbRead == 0)
break;
}
*ppData = pBuffer;
*pcbData = cbData;
hr = NOERROR;
Cleanup:
return hr;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
Error:
if (pBuffer)
delete [] pBuffer;
goto Cleanup;
}
static
void
MessageLoop()
{
MSG msg;
// There is a window message available. Dispatch it.
while (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
static
DWORD
UpdateTimeout(DWORD dwTimeout, DWORD dwStartTime)
{
if (dwTimeout != INFINITE)
{
DWORD dwTimeNow = GetTickCount();
DWORD dwElapsedTime;
if (dwTimeNow >= dwStartTime)
{
dwElapsedTime = dwTimeNow - dwStartTime;
}
else
{
dwElapsedTime = dwTimeNow + (0xFFFFFFFF - dwStartTime);
}
if (dwElapsedTime < dwTimeout)
{
dwTimeout -= dwElapsedTime;
}
else
{
dwTimeout = 0;
}
}
return dwTimeout;
}
DWORD CSinkArray::Add(IUnknown * pUnk)
{
ULONG iIndex;
IUnknown** pp = NULL;
if (_nSize == 0) // no connections
{
_pUnk = pUnk;
_nSize = 1;
return 1;
}
else if (_nSize == 1)
{
// create array
pp = (IUnknown **)ALLOCATE_FIXED_MEMORY(sizeof(IUnknown*)* _DEFAULT_VECTORLENGTH);
if (pp == NULL)
return 0;
memset(pp, 0, sizeof(IUnknown *) * _DEFAULT_VECTORLENGTH);
*pp = _pUnk;
_ppUnk = pp;
_nSize = _DEFAULT_VECTORLENGTH;
}
for (pp = begin(); pp < end(); pp++)
{
if (*pp == NULL)
{
*pp = pUnk;
iIndex = ULONG(pp-begin());
return iIndex+1;
}
}
int nAlloc = _nSize*2;
pp = (IUnknown **)REALLOCATE_MEMORY(_ppUnk, sizeof(IUnknown*)*nAlloc, LMEM_ZEROINIT);
if (pp == NULL)
return 0;
_ppUnk = pp;
memset(&_ppUnk[_nSize], 0, sizeof(IUnknown *)*_nSize);
_ppUnk[_nSize] = pUnk;
iIndex = _nSize;
_nSize = nAlloc;
return iIndex+1;
}
BOOL CSinkArray::Remove(DWORD dwCookie)
{
ULONG iIndex;
if (dwCookie == NULL)
return FALSE;
if (_nSize == 0)
return FALSE;
iIndex = dwCookie-1;
if (iIndex >= (ULONG)_nSize)
return FALSE;
if (_nSize == 1)
{
_nSize = 0;
return TRUE;
}
begin()[iIndex] = NULL;
return TRUE;
}
void CSinkArray::ReleaseAll()
{
for (IUnknown ** pp = begin(); pp < end(); pp++)
{
if (*pp != NULL)
{
SafeRelease(*pp);
}
}
}
HRESULT STDMETHODCALLTYPE
CSinkArray::QueryInterface(REFIID, void **)
{
return E_NOTIMPL;
}
ULONG STDMETHODCALLTYPE
CSinkArray::AddRef()
{
return 2;
}
ULONG STDMETHODCALLTYPE
CSinkArray::Release()
{
return 1;
}
void STDMETHODCALLTYPE
CSinkArray::OnResponseStart(long Status, BSTR bstrContentType)
{
for (IUnknown ** pp = begin(); pp < end(); pp++)
{
if (*pp != NULL)
{
IWinHttpRequestEvents * pSink;
pSink = static_cast<IWinHttpRequestEvents *>(*pp);
if (((*(DWORD_PTR **)pSink)[3]) != NULL)
{
pSink->OnResponseStart(Status, bstrContentType);
}
}
}
}
void STDMETHODCALLTYPE
CSinkArray::OnResponseDataAvailable(SAFEARRAY ** ppsaData)
{
for (IUnknown ** pp = begin(); pp < end(); pp++)
{
if (*pp != NULL)
{
IWinHttpRequestEvents * pSink;
pSink = static_cast<IWinHttpRequestEvents *>(*pp);
if (((*(DWORD_PTR **)pSink)[4]) != NULL)
{
pSink->OnResponseDataAvailable(ppsaData);
}
}
}
}
void STDMETHODCALLTYPE
CSinkArray::OnResponseFinished(void)
{
for (IUnknown ** pp = begin(); pp < end(); pp++)
{
if (*pp != NULL)
{
IWinHttpRequestEvents * pSink;
pSink = static_cast<IWinHttpRequestEvents *>(*pp);
if (((*(DWORD_PTR **)pSink)[5]) != NULL)
{
pSink->OnResponseFinished();
}
}
}
}
CWinHttpRequestEventsMarshaller::CWinHttpRequestEventsMarshaller
(
CSinkArray * pSinkArray,
HWND hWnd
)
{
INET_ASSERT((pSinkArray != NULL) && (hWnd != NULL));
_pSinkArray = pSinkArray;
_hWnd = hWnd;
_cRefs = 0;
_bFireEvents = true;
_cs.Init();
}
CWinHttpRequestEventsMarshaller::~CWinHttpRequestEventsMarshaller()
{
INET_ASSERT(_pSinkArray == NULL);
INET_ASSERT(_hWnd == NULL);
INET_ASSERT(_cRefs == 0);
}
HRESULT
CWinHttpRequestEventsMarshaller::Create
(
CSinkArray * pSinkArray,
CWinHttpRequestEventsMarshaller ** ppSinkMarshaller
)
{
CWinHttpRequestEventsMarshaller * pSinkMarshaller = NULL;
HWND hWnd = NULL;
HRESULT hr = NOERROR;
if (!RegisterWinHttpEventMarshallerWndClass())
goto ErrorFail;
hWnd = CreateWindowEx(0, s_szWinHttpEventMarshallerWndClass, NULL,
0, 0, 0, 0, 0,
(IsPlatformWinNT() && GlobalPlatformVersion5) ? HWND_MESSAGE : NULL,
NULL, GlobalDllHandle, NULL);
if (!hWnd)
goto ErrorFail;
pSinkMarshaller = New CWinHttpRequestEventsMarshaller(pSinkArray, hWnd);
if (!pSinkMarshaller)
goto ErrorOutOfMemory;
SetLastError(0);
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) pSinkMarshaller);
if (GetLastError() != 0)
goto ErrorFail;
pSinkMarshaller->AddRef();
*ppSinkMarshaller = pSinkMarshaller;
Exit:
if (FAILED(hr))
{
if (pSinkMarshaller)
{
delete pSinkMarshaller;
}
else if (hWnd)
{
DestroyWindow(hWnd);
}
}
return hr;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Exit;
}
void
CWinHttpRequestEventsMarshaller::Shutdown()
{
if (_cs.Lock())
{
FreezeEvents();
if (_hWnd)
{
MessageLoop();
DestroyWindow(_hWnd);
_hWnd = NULL;
}
_pSinkArray = NULL;
_cs.Unlock();
}
}
LRESULT CALLBACK
CWinHttpRequestEventsMarshaller::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (msg >= WHREM_MSG_ON_RESPONSE_START && msg <= WHREM_MSG_ON_RESPONSE_FINISHED)
{
CWinHttpRequestEventsMarshaller * pMarshaller;
CSinkArray * pSinkArray;
bool bOkToFireEvents = false;
pMarshaller = (CWinHttpRequestEventsMarshaller *) GetWindowLongPtr(hWnd, GWLP_USERDATA);
if (pMarshaller)
{
pSinkArray = pMarshaller->GetSinkArray();
bOkToFireEvents = pMarshaller->OkToFireEvents();
}
switch (msg)
{
case WHREM_MSG_ON_RESPONSE_START:
{
BSTR bstrContentType = (BSTR) lParam;
if (bOkToFireEvents)
{
pSinkArray->OnResponseStart((long) wParam, bstrContentType);
}
if (bstrContentType)
{
SysFreeString(bstrContentType);
}
}
break;
case WHREM_MSG_ON_RESPONSE_DATA_AVAILABLE:
{
SAFEARRAY * psaData = (SAFEARRAY *) wParam;
if (bOkToFireEvents)
{
pSinkArray->OnResponseDataAvailable(&psaData);
}
if (psaData)
{
SafeArrayDestroy(psaData);
}
}
break;
case WHREM_MSG_ON_RESPONSE_FINISHED:
if (bOkToFireEvents)
{
pSinkArray->OnResponseFinished();
}
break;
}
return 0;
}
else
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
HRESULT STDMETHODCALLTYPE
CWinHttpRequestEventsMarshaller::QueryInterface(REFIID riid, void ** ppv)
{
HRESULT hr = NOERROR;
if (ppv == NULL)
{
hr = E_INVALIDARG;
}
else if (riid == IID_IWinHttpRequestEvents || riid == IID_IUnknown)
{
*ppv = static_cast<IWinHttpRequestEvents *>(this);
AddRef();
}
else
hr = E_NOINTERFACE;
return hr;
}
ULONG STDMETHODCALLTYPE
CWinHttpRequestEventsMarshaller::AddRef()
{
return InterlockedIncrement(&_cRefs);
}
ULONG STDMETHODCALLTYPE
CWinHttpRequestEventsMarshaller::Release()
{
DWORD cRefs = InterlockedDecrement(&_cRefs);
if (cRefs == 0)
{
delete this;
return 0;
}
else
return cRefs;
}
void STDMETHODCALLTYPE
CWinHttpRequestEventsMarshaller::OnResponseStart(long Status, BSTR bstrContentType)
{
if (_cs.Lock())
{
if (OkToFireEvents())
{
BSTR bstrContentTypeCopy;
bstrContentTypeCopy = SysAllocString(bstrContentType);
PostMessage(_hWnd, WHREM_MSG_ON_RESPONSE_START,
(WPARAM) Status,
(LPARAM) bstrContentTypeCopy);
// Note: ownership of bstrContentType is transferred to the
// message window, so the string is not freed here.
}
_cs.Unlock();
}
}
void STDMETHODCALLTYPE
CWinHttpRequestEventsMarshaller::OnResponseDataAvailable(SAFEARRAY ** ppsaData)
{
if (_cs.Lock())
{
if (OkToFireEvents())
{
SAFEARRAY * psaDataCopy = NULL;
if (SUCCEEDED(SafeArrayCopy(*ppsaData, &psaDataCopy)))
{
PostMessage(_hWnd, WHREM_MSG_ON_RESPONSE_DATA_AVAILABLE,
(WPARAM) psaDataCopy, 0);
}
// Note: ownership of psaDataCopy is transferred to the
// message window, so the array is not freed here.
}
_cs.Unlock();
}
}
void STDMETHODCALLTYPE
CWinHttpRequestEventsMarshaller::OnResponseFinished(void)
{
if (_cs.Lock())
{
if (OkToFireEvents())
{
PostMessage(_hWnd, WHREM_MSG_ON_RESPONSE_FINISHED, 0, 0);
}
_cs.Unlock();
}
}
BOOL RegisterWinHttpEventMarshallerWndClass()
{
if (s_fWndClassRegistered)
return TRUE;
// only one thread should be here
if (!GeneralInitCritSec.Lock())
return FALSE;
if (s_fWndClassRegistered == FALSE)
{
WNDCLASS wndclass;
wndclass.style = 0;
wndclass.lpfnWndProc = &CWinHttpRequestEventsMarshaller::WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = GlobalDllHandle;
wndclass.hIcon = NULL;
wndclass.hCursor = NULL;;
wndclass.hbrBackground = (HBRUSH)NULL;
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = s_szWinHttpEventMarshallerWndClass;
// Register the window class
if (RegisterClass(&wndclass))
{
s_fWndClassRegistered = TRUE;
}
}
GeneralInitCritSec.Unlock();
return s_fWndClassRegistered;
}
void CleanupWinHttpRequestGlobals()
{
if (s_fWndClassRegistered)
{
// Register the window class
if (UnregisterClass(s_szWinHttpEventMarshallerWndClass, GlobalDllHandle))
{
s_fWndClassRegistered = FALSE;
}
}
}
static
BOOL
IsValidVariant(VARIANT v)
{
BOOL fOk = TRUE;
if (V_ISBYREF(&v))
{
if (IsBadReadPtr(v.pvarVal, sizeof(VARIANT)))
{
fOk = FALSE;
goto Exit;
}
else
v = *(v.pvarVal);
}
switch (v.vt)
{
case VT_BSTR:
fOk = IsValidBstr(v.bstrVal);
break;
case (VT_BYREF | VT_BSTR):
fOk = !IsBadReadPtr(v.pbstrVal, sizeof(BSTR));
break;
case (VT_BYREF | VT_VARIANT):
fOk = !IsBadReadPtr(v.pvarVal, sizeof(VARIANT)) &&
IsValidVariant(*(v.pvarVal));
break;
case VT_UNKNOWN:
case VT_DISPATCH:
fOk = !IsBadReadPtr(v.punkVal, sizeof(void *));
break;
}
Exit:
return fOk;
}