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.
 
 
 
 
 
 

7097 lines
177 KiB

/*
* HttpRequest.cxx
*
* 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>
#include "EnumConns.hxx" //IEnumConnections implementator
#include "EnumCP.hxx" //IEnumConnectionPoints implementator
#include "multilang.hxx"
/////////////////////////////////////////////////////////////////////////////
// private function prototypes
static void PreWideCharToUtf8(WCHAR * buffer, UINT cch, UINT * cb, bool * bSimpleConversion);
static void WideCharToUtf8(WCHAR * buffer, UINT cch, BYTE * bytebuffer, bool bSimpleConversion);
static HRESULT BSTRToUTF8(char ** psz, DWORD * pcbUTF8, BSTR bstr, bool * pbSetUtf8Charset);
static HRESULT AsciiToBSTR(BSTR * pbstr, char * sz, int cch);
static HRESULT GetBSTRFromVariant(VARIANT varVariant, BSTR * pBstr);
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 HRESULT ParseSelectedCert(BSTR bstrSelection,
LPBOOL pfLocalMachine,
BSTR *pbstrStore,
BSTR *pbstrSubject);
static BOOL GetContentLengthIfResponseNotChunked(HINTERNET hHttpRequest,
DWORD * pdwContentLength);
static HRESULT SecureFailureFromStatus(DWORD dwFlags);
static BOOL s_fWndClassRegistered;
// Change the name of the window class for each new version of WinHTTP
static const char * s_szWinHttpEventMarshallerWndClass = "_WinHttpEventMarshaller51";
static CMimeInfoCache* g_pMimeInfoCache = NULL;
BOOL IsValidHeaderName(LPCWSTR lpszHeaderName);
#define SafeRelease(p) \
{ \
if (p) \
(p)->Release();\
(p) = NULL;\
}
#ifndef HWND_MESSAGE
#define HWND_MESSAGE ((HWND)-3)
#endif
#define SIZEOF_BUFFER (8192)
inline BOOL IsValidBstr(BSTR bstr)
{
return (bstr == NULL) || (!IsBadStringPtrW(bstr, (UINT_PTR)-1));
}
#ifndef WINHTTP_STATIC_LIBRARY
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void ** ppv)
{
if (rclsid != CLSID_WinHttpRequest)
return CLASS_E_CLASSNOTAVAILABLE;
if (!WinHttpCheckPlatform())
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;
InterlockedIncrement(&g_cSessionCount);
}
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;
InterlockedDecrement(&g_cSessionCount);
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;
if( !DelayLoad(&g_moduleOle32)
|| !DelayLoad(&g_moduleOleAut32))
{
return E_UNEXPECTED;
}
return CreateHttpRequest(riid, ppvObject);
}
STDMETHODIMP
CClassFactory::LockServer(BOOL fLock)
{
if (fLock)
InterlockedIncrement(&g_cSessionCount);
else
InterlockedDecrement(&g_cSessionCount);
return NOERROR;
}
STDAPI DllCanUnloadNow()
{
return ((g_cSessionCount == 0) && (g_pAsyncCount == NULL || g_pAsyncCount->GetRef() == 0)) ? S_OK : S_FALSE;
}
#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()
{
InterlockedIncrement(&g_cSessionCount);
Initialize();
}
/*
* CHttpRequest::~CHttpRequest destructor
*
*/
CHttpRequest::~CHttpRequest()
{
ReleaseResources();
InterlockedDecrement(&g_cSessionCount);
}
#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
MIDL_DEFINE_GUID(IID, IID_IWinHttpRequest_TechBeta,0x06f29373,0x5c5a,0x4b54,0xb0,0x25,0x6e,0xf1,0xbf,0x8a,0xbf,0x0e);
MIDL_DEFINE_GUID(IID, IID_IWinHttpRequestEvents_TechBeta,0xcff7bd4c,0x6689,0x4bbe,0x91,0xc2,0x0f,0x55,0x9e,0x8b,0x88,0xa7);
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_IWinHttpRequest_TechBeta ||
riid == IID_IUnknown)
{
*ppv = static_cast<IWinHttpRequest *>(this);
AddRef();
}
else if (riid == IID_IConnectionPointContainer)
{
*ppv = static_cast<IConnectionPointContainer *>(this);
AddRef();
}
else if (riid == IID_ISupportErrorInfo)
{
*ppv = static_cast<ISupportErrorInfo *>(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+1];
OLECHAR wszPath[MAX_PATH+1];
GetModuleFileName(GlobalDllHandle, szPath, sizeof(szPath)-1); // leave room for null char
szPath[sizeof(szPath)-1] = '\0'; // guarantee null-termination
MultiByteToWideChar(CP_ACP, 0, szPath, -1, wszPath, MAX_PATH);
hr = DL(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"SetAutoLogonPolicy", DISPID_HTTPREQUEST_SETAUTOLOGONPOLICY },
{ L"SetClientCertificate", DISPID_HTTPREQUEST_SETCLIENTCERTIFICATE },
{ 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;
}
// _DispGetParamSafe
//
// A wrapper around the OLE Automation DispGetParam API that protects
// the call with a __try/__except block. Needed for casting to BSTR,
// as bogus BSTR pointers can cause an AV in VariantChangeType (which
// DispGetParam calls).
//
static HRESULT _DispGetParamSafe
(
DISPPARAMS * pDispParams,
DISPID dispid,
VARTYPE vt,
VARIANT * pvarResult,
unsigned int * puArgErr
)
{
HRESULT hr;
__try
{
hr = DL(DispGetParam)(pDispParams, dispid, vt, pvarResult, puArgErr);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
hr = E_INVALIDARG;
}
return hr;
}
// _DispGetOptionalParam
//
// Helper routine to fetch optional parameters. If DL(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 = _DispGetParamSafe(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;
DL(VariantInit)(pVarResult);
}
switch (dispIdMember)
{
case DISPID_HTTPREQUEST_ABORT:
{
hr = Abort();
break;
}
case DISPID_HTTPREQUEST_SETPROXY:
{
VARIANT varProxySetting;
VARIANT varProxyServer;
VARIANT varBypassList;
DL(VariantInit)(&varProxySetting);
DL(VariantInit)(&varProxyServer);
DL(VariantInit)(&varBypassList);
hr = DL(DispGetParam)(pDispParams, 0, VT_I4, &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_I4(&varProxySetting), varProxyServer, varBypassList);
}
DL(VariantClear)(&varProxySetting);
DL(VariantClear)(&varProxyServer);
DL(VariantClear)(&varBypassList);
break;
}
case DISPID_HTTPREQUEST_SETCREDENTIALS:
{
VARIANT varUserName;
VARIANT varPassword;
VARIANT varAuthTarget;
DL(VariantInit)(&varUserName);
DL(VariantInit)(&varPassword);
DL(VariantInit)(&varAuthTarget);
hr = _DispGetParamSafe(pDispParams, 0, VT_BSTR, &varUserName, puArgErr);
if (SUCCEEDED(hr))
{
hr = _DispGetParamSafe(pDispParams, 1, VT_BSTR, &varPassword, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = DL(DispGetParam)(pDispParams, 2, VT_I4, &varAuthTarget, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = SetCredentials(V_BSTR(&varUserName), V_BSTR(&varPassword),
V_I4(&varAuthTarget));
}
DL(VariantClear)(&varUserName);
DL(VariantClear)(&varPassword);
DL(VariantClear)(&varAuthTarget);
break;
}
case DISPID_HTTPREQUEST_OPEN:
{
VARIANT varMethod;
VARIANT varUrl;
VARIANT varAsync;
DL(VariantInit)(&varMethod);
DL(VariantInit)(&varUrl);
DL(VariantInit)(&varAsync);
hr = _DispGetParamSafe(pDispParams, 0, VT_BSTR, &varMethod, puArgErr);
if (SUCCEEDED(hr))
{
hr = _DispGetParamSafe(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);
}
DL(VariantClear)(&varMethod);
DL(VariantClear)(&varUrl);
DL(VariantClear)(&varAsync);
break;
}
case DISPID_HTTPREQUEST_SETREQUESTHEADER:
{
VARIANT varHeader;
VARIANT varValue;
DL(VariantInit)(&varHeader);
DL(VariantInit)(&varValue);
hr = _DispGetParamSafe(pDispParams, 0, VT_BSTR, &varHeader, puArgErr);
if (SUCCEEDED(hr))
{
hr = _DispGetParamSafe(pDispParams, 1, VT_BSTR, &varValue, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = SetRequestHeader(V_BSTR(&varHeader), V_BSTR(&varValue));
}
DL(VariantClear)(&varHeader);
DL(VariantClear)(&varValue);
break;
}
case DISPID_HTTPREQUEST_GETRESPONSEHEADER:
{
VARIANT varHeader;
DL(VariantInit)(&varHeader);
hr = _DispGetParamSafe(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
DL(SysFreeString)(bstrValue);
}
DL(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
DL(SysFreeString)(bstrResponseHeaders);
break;
}
case DISPID_HTTPREQUEST_SEND:
{
if (pDispParams->cArgs <= 1)
{
VARIANT varEmptyBody;
DL(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
DL(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
DL(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;
DL(VariantInit)(&varOption);
hr = DL(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]);
}
DL(VariantClear)(&varOption);
break;
}
case DISPID_HTTPREQUEST_WAITFORRESPONSE:
{
VARIANT varTimeout;
VARIANT_BOOL boolSucceeded = FALSE;
DL(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;
}
DL(VariantClear)(&varTimeout);
break;
}
case DISPID_HTTPREQUEST_SETTIMEOUTS:
{
VARIANT varResolveTimeout;
VARIANT varConnectTimeout;
VARIANT varSendTimeout;
VARIANT varReceiveTimeout;
DL(VariantInit)(&varResolveTimeout);
DL(VariantInit)(&varConnectTimeout);
DL(VariantInit)(&varSendTimeout);
DL(VariantInit)(&varReceiveTimeout);
hr = DL(DispGetParam)(pDispParams, 0, VT_I4, &varResolveTimeout, puArgErr);
if (SUCCEEDED(hr))
{
hr = DL(DispGetParam)(pDispParams, 1, VT_I4, &varConnectTimeout, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = DL(DispGetParam)(pDispParams, 2, VT_I4, &varSendTimeout, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = DL(DispGetParam)(pDispParams, 3, VT_I4, &varReceiveTimeout, puArgErr);
}
if (SUCCEEDED(hr))
{
hr = SetTimeouts(V_I4(&varResolveTimeout), V_I4(&varConnectTimeout),
V_I4(&varSendTimeout),
V_I4(&varReceiveTimeout));
}
DL(VariantClear)(&varResolveTimeout);
DL(VariantClear)(&varConnectTimeout);
DL(VariantClear)(&varSendTimeout);
DL(VariantClear)(&varReceiveTimeout);
break;
}
case DISPID_HTTPREQUEST_SETCLIENTCERTIFICATE:
{
VARIANT varClientCertificate;
DL(VariantInit)(&varClientCertificate);
hr = _DispGetParamSafe(pDispParams, 0, VT_BSTR, &varClientCertificate, puArgErr);
if (SUCCEEDED(hr))
{
hr = SetClientCertificate(V_BSTR(&varClientCertificate));
}
DL(VariantClear)(&varClientCertificate);
break;
}
case DISPID_HTTPREQUEST_SETAUTOLOGONPOLICY:
{
VARIANT varAutoLogonPolicy;
DL(VariantInit)(&varAutoLogonPolicy);
hr = DL(DispGetParam)(pDispParams, 0, VT_I4, &varAutoLogonPolicy, puArgErr);
if (SUCCEEDED(hr))
{
WinHttpRequestAutoLogonPolicy AutoLogonPolicy;
AutoLogonPolicy = static_cast<WinHttpRequestAutoLogonPolicy>(V_I4(&varAutoLogonPolicy));
hr = SetAutoLogonPolicy(AutoLogonPolicy);
}
DL(VariantClear)(&varAutoLogonPolicy);
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
DL(GetErrorInfo)(0, &pei);
if (pei)
{
// give back to OLE
DL(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::InterfaceSupportsErrorInfo(REFIID riid)
{
return (riid == IID_IWinHttpRequest) ? S_OK : S_FALSE;
}
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 ** ppEnum)
{
if (!ppEnum)
return E_POINTER;
*ppEnum = static_cast<IEnumConnectionPoints*>(
new CEnumConnectionPoints(static_cast<IConnectionPoint*>(&_CP))
);
return (*ppEnum) ? S_OK : E_OUTOFMEMORY;
}
STDMETHODIMP
CHttpRequest::FindConnectionPoint(REFIID riid, IConnectionPoint ** ppCP)
{
if (!ppCP)
return E_POINTER;
if (riid == IID_IWinHttpRequestEvents)
{
return _CP.QueryInterface(IID_IConnectionPoint, (void **)ppCP);
}
else if (riid == IID_IWinHttpRequestEvents_TechBeta)
{
_CP.DisableOnError();
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);
// RENO 39279: if the QI for IWinHttpRequestEvents fails, try the older
// IID from the Tech Beta.
if (FAILED(hr))
{
hr = pUnk->QueryInterface(IID_IWinHttpRequestEvents_TechBeta, (void **)&pIWinHttpRequestEvents);
if (SUCCEEDED(hr))
{
// The OnError event should have already been disabled in
// CHttpRequest::FindConnectionPoint(), but it doesn't
// hurt to be paranoid.
DisableOnError();
}
}
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 ** ppEnum)
{
if (!ppEnum)
return E_POINTER;
*ppEnum = NULL;
DWORD_PTR size = _SinkArray.end() - _SinkArray.begin();
CONNECTDATA* pCD = NULL;
if (size != 0)
{
//allocate data on stack, we usually expect just 1 or few connections,
//so it's ok to allocate such ammount of data on stack
__try
{
pCD = (CONNECTDATA*)_alloca(size * sizeof(CONNECTDATA[1]));
}
__except (GetExceptionCode() == STATUS_STACK_OVERFLOW ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
return E_OUTOFMEMORY;
}
}
IUnknown** ppUnk = _SinkArray.begin();
for (DWORD i = 0; i < size; ++i)
{
pCD[i].pUnk = ppUnk[i];
pCD[i].dwCookie = i + 1;
}
CEnumConnections* pE = new CEnumConnections();
if (pE)
{
HRESULT hr = pE->Init(pCD, (DWORD)size);
if ( SUCCEEDED(hr) )
*ppEnum = static_cast<IEnumConnections*>(pE);
else
delete pE;
return hr;
}
else
return E_OUTOFMEMORY;
}
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;
DL(VariantInit)(&varData);
hr = CreateVector(&varData, rgbData, cbData);
if (SUCCEEDED(hr))
{
GetSink()->OnResponseDataAvailable(&V_ARRAY(&varData));
}
DL(VariantClear)(&varData);
}
}
void
CHttpRequest::CHttpRequestEventsCP::FireOnResponseFinished()
{
if (_cConnections > 0 && !Px()->_bAborted)
{
GetSink()->OnResponseFinished();
}
}
void
CHttpRequest::CHttpRequestEventsCP::FireOnError(HRESULT hr)
{
if ((_cConnections > 0) && (!Px()->_bAborted) && (!_bOnErrorDisabled))
{
IErrorInfo * pErrorInfo = CreateErrorObject(hr);
BSTR bstrErrorDescription = NULL;
if (pErrorInfo)
{
pErrorInfo->GetDescription(&bstrErrorDescription);
pErrorInfo->Release();
}
GetSink()->OnError((long) hr, bstrErrorDescription);
DL(SysFreeString)(bstrErrorDescription);
}
}
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 = WINHTTP_ACCESS_TYPE_DEFAULT_PROXY;
_bstrProxyServer = NULL;
_bstrBypassList = NULL;
_eState = CHttpRequest::CREATED;
_fAsync = FALSE;
#if !defined(TRUE_ASYNC)
_hWorkerThread = NULL;
#endif//!TRUE_ASYNC
_cRefsOnMainThread = 0;
_dwMainThreadId = GetCurrentThreadId();
_hrAsyncResult = NOERROR;
_bAborted = false;
_bSetTimeouts = false;
_bSetUtf8Charset = false;
_hInet = NULL;
_hConnection = NULL;
_hHTTP = NULL;
_ResolveTimeout = 0;
_ConnectTimeout = 0;
_SendTimeout = 0;
_ReceiveTimeout = 0;
_cbRequestBody = 0;
_szRequestBuffer = NULL;
_dwCodePage = CP_UTF8;
_dwEscapeFlag = WINHTTP_FLAG_ESCAPE_DISABLE_QUERY;
_cbResponseBody = 0;
_pResponseStream = NULL;
_hAbortedConnectObject = NULL;
_hAbortedRequestObject = NULL;
_bstrCertSubject = NULL;
_bstrCertStore = NULL;
_fCertLocalMachine = FALSE;
_fCheckForRevocation = FALSE;
_dwSslIgnoreFlags = 0;
_dwSecureProtocols = DEFAULT_SECURE_PROTOCOLS;
_hrSecureFailure = HRESULT_FROM_WIN32(ERROR_WINHTTP_SECURE_FAILURE);
_bEnableSslImpersonation = FALSE;
_bMethodGET = FALSE;
_bHttp1_1Mode = TRUE;
#ifdef TRUE_ASYNC
_hCompleteEvent = NULL;
_bRetriedWithCert = FALSE;
_Buffer = NULL;
#endif
_dwAutoLogonPolicy = WINHTTP_AUTOLOGON_SECURITY_LEVEL_DEFAULT;
_dwRedirectPolicy = WINHTTP_OPTION_REDIRECT_POLICY_DEFAULT;
_lMaxAutomaticRedirects = GlobalMaxHttpRedirects;
_lMaxResponseHeaderSize = GlobalMaxHeaderSize;
_lMaxResponseDrainSize = GlobalMaxDrainSize;
_dwPassportConfig = (WINHTTP_DISABLE_PASSPORT_AUTH | WINHTTP_DISABLE_PASSPORT_KEYRING);
}
/*
* CHttpRequest::ReleaseResources
*
* Purpose:
* Release all handles, events, and buffers
*
*/
void
CHttpRequest::ReleaseResources()
{
SafeRelease(_pTypeInfo);
#if !defined(TRUE_ASYNC)
if (_hWorkerThread)
{
CloseHandle(_hWorkerThread);
_hWorkerThread = NULL;
}
#endif//!TRUE_ASYNC
_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;
}
SafeRelease(_pResponseStream);
if (_bstrUserAgent)
{
DL(SysFreeString)(_bstrUserAgent);
_bstrUserAgent = NULL;
}
if (_bstrProxyServer)
{
DL(SysFreeString)(_bstrProxyServer);
_bstrProxyServer = NULL;
}
if (_bstrBypassList)
{
DL(SysFreeString)(_bstrBypassList);
_bstrBypassList = NULL;
}
if (_bstrCertSubject)
{
DL(SysFreeString)(_bstrCertSubject);
_bstrCertSubject = NULL;
}
if (_bstrCertStore)
{
DL(SysFreeString)(_bstrCertStore);
_bstrCertStore = NULL;
}
#ifdef TRUE_ASYNC
if (_hCompleteEvent != NULL)
{
CloseHandle(_hCompleteEvent);
_hCompleteEvent = NULL;
}
if (_Buffer != NULL)
{
delete [] _Buffer;
}
#endif
}
/*
* CHttpRequest::Reset
*
* Purpose:
* Release all resources and initialize data members
*
*/
void
CHttpRequest::Reset()
{
ReleaseResources();
Initialize();
}
/*
* CHttpRequest::Recycle
*
* Purpose:
* Recycle object
*
*/
void
CHttpRequest::Recycle()
{
DEBUG_ENTER((DBG_HTTP,
None,
"IWinHttpRequest::Recycle",
NULL));
//
// Wait for the worker thread to shut down. This shouldn't take long
// since the Abort will close the Request and Connection handles.
//
if (
#ifdef TRUE_ASYNC
_hCompleteEvent
#else
_hWorkerThread
#endif//TRUE_ASYNC
)
{
DWORD dwWaitResult;
for (;;)
{
dwWaitResult = MsgWaitForMultipleObjects(1,
#ifdef TRUE_ASYNC
&_hCompleteEvent
#else
&_hWorkerThread
#endif//TRUE_ASYNC
,
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;
}
}
#ifdef TRUE_ASYNC
CloseHandle(_hCompleteEvent);
_hCompleteEvent = NULL;
#else
CloseHandle(_hWorkerThread);
_hWorkerThread = NULL;
#endif//TRUE_ASYNC
}
_hConnection = NULL;
_hHTTP = NULL;
//
// Derefence aborted handle objects (if any).
//
if (_hAbortedRequestObject != NULL)
{
DereferenceObject(_hAbortedRequestObject);
_hAbortedRequestObject = NULL;
}
if (_hAbortedConnectObject != NULL)
{
DereferenceObject(_hAbortedConnectObject);
_hAbortedConnectObject = NULL;
}
//sergekh: we shouldn't reset _fAsync to know the state of _hInet
//_fAsync = FALSE;
_hrAsyncResult = NOERROR;
_bAborted = false;
// don't reset timeouts, keep any that were set.
_cbRequestBody = 0;
_cbResponseBody = 0;
if (_szRequestBuffer)
{
delete [] _szRequestBuffer;
_szRequestBuffer = NULL;
}
SafeRelease(_pResponseStream);
_CP.ShutdownEventSinksMarshaller();
_CP.ReleaseEventSinksMarshaller();
// Allow events to fire; Abort() would have frozen them from firing.
_CP.UnfreezeEvents();
SetState(CHttpRequest::CREATED);
DEBUG_LEAVE(0);
}
static BOOL GetContentLengthIfResponseNotChunked(
HINTERNET hHttpRequest,
DWORD * pdwContentLength
)
{
char szTransferEncoding[16]; // big enough for "chunked" or "identity"
DWORD cb;
BOOL fRetCode;
cb = sizeof(szTransferEncoding) - 1;
fRetCode = HttpQueryInfoA(
hHttpRequest,
WINHTTP_QUERY_CONTENT_TRANSFER_ENCODING,
WINHTTP_HEADER_NAME_BY_INDEX,
szTransferEncoding,
&cb,
0);
if (!fRetCode || lstrcmpi(szTransferEncoding, "identity") == 0)
{
// Determine the content length
cb = sizeof(DWORD);
fRetCode = HttpQueryInfoA(
hHttpRequest,
WINHTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX,
pdwContentLength,
&cb,
0);
}
else
{
fRetCode = FALSE;
}
return fRetCode;
}
/*
* 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;
BYTE * Buffer = NULL;
SetState(CHttpRequest::RECEIVING);
hr = get_Status(&lStatus);
if (FAILED(hr))
goto Error;
hr = _GetResponseHeader(L"Content-Type", &bstrContentType);
if (FAILED(hr))
{
bstrContentType = DL(SysAllocString)(L"");
if (bstrContentType == NULL)
goto ErrorOutOfMemory;
hr = NOERROR;
}
INET_ASSERT((_pResponseStream == NULL) && (_cbResponseBody == 0));
hr = DL(CreateStreamOnHGlobal)(NULL, TRUE, &_pResponseStream);
if (SUCCEEDED(hr))
{
// Determine the content length
fRetCode = GetContentLengthIfResponseNotChunked(_hHTTP, &dwContentLength);
// pre-set response stream size if we have a Content-Length
if (fRetCode)
{
ULARGE_INTEGER size;
size.LowPart = dwContentLength;
size.HighPart = 0;
_pResponseStream->SetSize(size);
}
else
{
// Content-Length was not specified in the response, but this
// does not mean Content-Length==0. We will keep reading until
// either no more data is available. Set dwContentLength to 4GB
// to trick our read loop into reading until eof is reached.
dwContentLength = (DWORD)(-1L);
ULARGE_INTEGER size;
// Set initial size of the response stream to 8K.
size.LowPart = SIZEOF_BUFFER;
size.HighPart = 0;
_pResponseStream->SetSize(size);
}
}
else
goto ErrorOutOfMemory;
//
// Allocate an 8K buffer to read the response data in chunks.
//
Buffer = New BYTE[SIZEOF_BUFFER];
if (!Buffer)
{
goto ErrorOutOfMemory;
}
//
// Fire the initial OnResponseStart event
//
_CP.FireOnResponseStart(lStatus, bstrContentType);
// Skip read loop if Content-Length==0.
if (dwContentLength == 0)
{
goto Finished;
}
//
// 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;
}
// Read up to 8K (sizeof Buffer) of data.
cbAvail = min(cbAvail, SIZEOF_BUFFER);
fRetCode = WinHttpReadData(_hHTTP, Buffer, cbAvail, &cbRead);
if (!fRetCode)
{
goto ErrorFail;
}
if (cbRead != 0)
{
hr = _pResponseStream->Write(Buffer, cbRead, NULL);
if (FAILED(hr))
{
goto ErrorOutOfMemory;
}
_CP.FireOnResponseDataAvailable((const BYTE *)Buffer, cbRead);
_cbResponseBody += cbRead;
}
// If WinHttpReadData indicates there is no more data to read,
// or we've read as much data as the Content-Length header tells
// us to expect, then we're finished reading the response.
if ((cbRead == 0) || (_cbResponseBody >= dwContentLength))
{
ULARGE_INTEGER size;
// set final size on stream
size.LowPart = _cbResponseBody;
size.HighPart = 0;
_pResponseStream->SetSize(size);
break;
}
}
Finished:
SetState(CHttpRequest::RESPONSE);
_CP.FireOnResponseFinished();
hr = NOERROR;
Cleanup:
if (bstrContentType)
DL(SysFreeString)(bstrContentType);
if (Buffer)
delete [] Buffer;
return hr;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(::GetLastError());
_CP.FireOnError(hr);
goto Error;
Error:
SafeRelease(_pResponseStream);
_cbResponseBody = NULL;
goto Cleanup;
}
STDMETHODIMP
CHttpRequest::SetProxy(HTTPREQUEST_PROXY_SETTING ProxySetting,
VARIANT varProxyServer,
VARIANT varBypassList)
{
HRESULT hr = NOERROR;
if (!IsValidVariant(varProxyServer) || !IsValidVariant(varBypassList))
return E_INVALIDARG;
DEBUG_ENTER_API((DBG_HTTP,
Dword,
"IWinHttpRequest::SetProxy",
"HTTPREQUEST_PROXY_SETTING: %d, VARIANT, VARIANT",
ProxySetting
));
if (_bstrProxyServer)
{
DL(SysFreeString)(_bstrProxyServer);
_bstrProxyServer = NULL;
}
if (_bstrBypassList)
{
DL(SysFreeString)(_bstrBypassList);
_bstrBypassList = NULL;
}
switch (ProxySetting)
{
case HTTPREQUEST_PROXYSETTING_PRECONFIG:
_dwProxySetting = WINHTTP_ACCESS_TYPE_DEFAULT_PROXY;
break;
case HTTPREQUEST_PROXYSETTING_DIRECT:
_dwProxySetting = WINHTTP_ACCESS_TYPE_NO_PROXY;
break;
case HTTPREQUEST_PROXYSETTING_PROXY:
_dwProxySetting = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
hr = GetBSTRFromVariant(varProxyServer, &_bstrProxyServer);
if (SUCCEEDED(hr))
{
hr = GetBSTRFromVariant(varBypassList, &_bstrBypassList);
}
if (FAILED(hr))
{
hr = E_INVALIDARG;
}
break;
default:
hr = E_INVALIDARG;
break;
}
if (SUCCEEDED(hr))
{
if (_hHTTP)
{
WINHTTP_PROXY_INFOW ProxyInfo;
memset(&ProxyInfo, 0, sizeof(ProxyInfo));
ProxyInfo.dwAccessType = _dwProxySetting;
ProxyInfo.lpszProxy = _bstrProxyServer;
ProxyInfo.lpszProxyBypass = _bstrBypassList;
if (!WinHttpSetOption(_hHTTP, WINHTTP_OPTION_PROXY,
&ProxyInfo,
sizeof(ProxyInfo)))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
if (SUCCEEDED(hr) &&
!WinHttpSetOption(_hInet, WINHTTP_OPTION_PROXY,
&ProxyInfo,
sizeof(ProxyInfo)))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
}
SetErrorInfo(hr);
DEBUG_LEAVE_API(hr);
return hr;
}
STDMETHODIMP
CHttpRequest::SetCredentials(
BSTR bstrUserName,
BSTR bstrPassword,
HTTPREQUEST_SETCREDENTIALS_FLAGS Flags)
{
HRESULT hr;
DEBUG_ENTER_API((DBG_HTTP,
Dword,
"IWinHttpRequest::SetCredentials",
"BSTR %Q, BSTR *, Flags: %x",
_hHTTP? bstrUserName: L"",
Flags
));
// 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.
if (!WinHttpSetOption(
_hHTTP,
WINHTTP_OPTION_USERNAME,
bstrUserName,
lstrlenW(bstrUserName)))
goto ErrorFail;
if (!WinHttpSetOption(
_hHTTP,
WINHTTP_OPTION_PASSWORD,
bstrPassword,
lstrlenW(bstrPassword)+1)) // 596411 allow empty/blank password
goto ErrorFail;
}
else if (Flags == HTTPREQUEST_SETCREDENTIALS_FOR_PROXY)
{
// Set Username and Password.
if (!WinHttpSetOption(
_hHTTP,
WINHTTP_OPTION_PROXY_USERNAME,
bstrUserName,
lstrlenW(bstrUserName)))
goto ErrorFail;
if (!WinHttpSetOption(
_hHTTP,
WINHTTP_OPTION_PROXY_PASSWORD,
bstrPassword,
lstrlenW(bstrPassword)+1)) // 596411 allow empty/blank password
goto ErrorFail;
}
else
{
DEBUG_LEAVE_API(E_INVALIDARG);
return E_INVALIDARG;
}
hr = NOERROR;
Cleanup:
SetErrorInfo(hr);
DEBUG_LEAVE_API(hr);
return hr;
ErrorCannotCallBeforeOpen:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN);
goto Cleanup;
ErrorFail:
hr = HRESULT_FROM_WIN32(::GetLastError());
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;
DWORD dw;
URL_COMPONENTSW url;
// Validate that we are called from our apartment's thread.
if (GetCurrentThreadId() != _dwMainThreadId)
return RPC_E_WRONG_THREAD;
// 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;
BOOL newAsync = GetBoolFromVariant(varAsync, FALSE);
DEBUG_ENTER_API((DBG_HTTP,
Dword,
"IWinHttpRequest::Open",
"method: %Q, url: %Q, async: %d",
bstrMethod,
bstrUrl,
newAsync
));
// Check for reinitialization
if (_eState != CHttpRequest::CREATED)
{
//
// Abort any request in progress.
// This will also recycle the object.
//
Abort();
}
//
//check if session has the async state we need
//
if (_hInet)
{
if ((_fAsync && !newAsync) || (!_fAsync && newAsync)) //XOR
{
//state is not the same, close session
WinHttpCloseHandle(_hInet);
_hInet = NULL;
}
}
_fAsync = newAsync;
//
// Open an Internet Session if one does not already exist.
//
if (!_hInet)
{
_hInet = WinHttpOpen(
GetUserAgentString(),
_dwProxySetting,
_bstrProxyServer,
_bstrBypassList,
#ifdef TRUE_ASYNC
_fAsync ? WINHTTP_FLAG_ASYNC : 0
#else
0
#endif//TRUE_ASYNC
);
if (!_hInet)
goto ErrorFail;
DWORD dwEnableFlags = _bEnableSslImpersonation ? 0 : WINHTTP_ENABLE_SSL_REVERT_IMPERSONATION;
if (dwEnableFlags)
{
WinHttpSetOption(_hInet,
WINHTTP_OPTION_ENABLE_FEATURE,
(LPVOID)&dwEnableFlags,
sizeof(dwEnableFlags));
}
}
//
// 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;
if (!WinHttpSetOption(_hInet,
WINHTTP_OPTION_SECURE_PROTOCOLS,
(LPVOID)&_dwSecureProtocols,
sizeof(_dwSecureProtocols)))
goto ErrorFail;
if (!WinHttpSetOption(_hInet,
WINHTTP_OPTION_REDIRECT_POLICY,
(LPVOID)&_dwRedirectPolicy,
sizeof(DWORD)))
goto ErrorFail;
if (!WinHttpSetOption(_hInet,
WINHTTP_OPTION_CONFIGURE_PASSPORT_AUTH,
(LPVOID)&_dwPassportConfig,
sizeof(DWORD)))
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 == 0);
url.lpszUrlPath = L"/";
url.dwUrlPathLength = 1;
}
bstrHostName = DL(SysAllocStringLen)(url.lpszHostName, url.dwHostNameLength);
bstrUrlPath = DL(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 |= _dwEscapeFlag;
_hHTTP = WinHttpOpenRequest(
_hConnection,
bstrMethod,
bstrUrlPath,
_bHttp1_1Mode ? L"HTTP/1.1" : L"HTTP/1.0",
NULL,
NULL,
dwHttpOpenFlags);
if (!_hHTTP)
goto ErrorFail;
if ((StrCmpIW(bstrMethod, L"GET") == 0) || (StrCmpIW(bstrMethod, L"HEAD") == 0))
{
_bMethodGET = TRUE;
}
if (_fCheckForRevocation)
{
DWORD dwOptions = WINHTTP_ENABLE_SSL_REVOCATION;
WinHttpSetOption(_hHTTP,
WINHTTP_OPTION_ENABLE_FEATURE,
(LPVOID)&dwOptions,
sizeof(dwOptions));
}
// Set the SSL ignore flags through an undocumented front door
if (_dwSslIgnoreFlags)
{
WinHttpSetOption(_hHTTP,
WINHTTP_OPTION_SECURITY_FLAGS,
(LPVOID)&_dwSslIgnoreFlags,
sizeof(_dwSslIgnoreFlags));
}
if (!WinHttpSetOption(_hHTTP, WINHTTP_OPTION_AUTOLOGON_POLICY,
(void *) &_dwAutoLogonPolicy,
sizeof(_dwAutoLogonPolicy)))
goto ErrorFail;
dw = (DWORD)_lMaxAutomaticRedirects;
if (!WinHttpSetOption(_hHTTP, WINHTTP_OPTION_MAX_HTTP_AUTOMATIC_REDIRECTS,
(void *) &dw, sizeof(dw)))
goto ErrorFail;
dw = (DWORD)_lMaxResponseHeaderSize;
if (!WinHttpSetOption(_hHTTP, WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE,
(void *) &dw, sizeof(dw)))
goto ErrorFail;
dw = (DWORD)_lMaxResponseDrainSize;
if (!WinHttpSetOption(_hHTTP, WINHTTP_OPTION_MAX_RESPONSE_DRAIN_SIZE,
(void *) &dw, sizeof(dw)))
goto ErrorFail;
SetState(CHttpRequest::OPENED);
hr = NOERROR;
Cleanup:
if (bstrHostName)
DL(SysFreeString)(bstrHostName);;
if (bstrUrlPath)
DL(SysFreeString)(bstrUrlPath);
SetErrorInfo(hr);
DEBUG_LEAVE_API(hr);
return hr;
ErrorUnsupportedScheme:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_UNRECOGNIZED_SCHEME);
goto Cleanup;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
ErrorFail:
if (_hHTTP)
{
WinHttpCloseHandle(_hHTTP);
_hHTTP = NULL;
}
if (_hConnection)
{
WinHttpCloseHandle(_hConnection);
_hConnection = NULL;
}
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)
|| !IsValidHeaderName(bstrHeader))
return E_INVALIDARG;
DEBUG_ENTER_API((DBG_HTTP,
Dword,
"IWinHttpRequest::SetRequestHeader",
"header: %Q, value: %Q",
bstrHeader,
bstrValue
));
// 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;
if (StrCmpIW(bstrHeader, L"Cookie") == 0)
dwModifiers |= HTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON;
cchHeaderValue = lstrlenW(bstrHeader) + lstrlenW(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 (lstrlenW(bstrValue) == 0)
{
dwModifiers |= HTTP_ADDREQ_FLAG_REPLACE;
}
if (! WinHttpAddRequestHeaders(_hHTTP, wszHeaderValue,
(DWORD)-1L,
dwModifiers))
goto ErrorFail;
hr = NOERROR;
Cleanup:
if (wszHeaderValue)
delete [] wszHeaderValue;
SetErrorInfo(hr);
DEBUG_LEAVE_API(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;
if (!(_bMethodGET && _cbRequestBody == 0))
{
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,
(DWORD)-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: */*",
(DWORD)-1L,
HTTP_ADDREQ_FLAG_ADD_IF_NEW);
if (_bSetUtf8Charset)
{
CHAR szContentType[256];
BOOL fRetCode;
DWORD cb = sizeof(szContentType) - sizeof("Content-Type: ") - sizeof("; Charset=UTF-8");
LPSTR pszNewContentType = NULL;
lstrcpy(szContentType, "Content-Type: ");
fRetCode = HttpQueryInfoA(_hHTTP,
HTTP_QUERY_FLAG_REQUEST_HEADERS | HTTP_QUERY_CONTENT_TYPE,
WINHTTP_HEADER_NAME_BY_INDEX,
szContentType + sizeof("Content-Type: ") - 1,
&cb,
0);
if (fRetCode)
{
if (!StrStrIA(szContentType, "charset"))
{
lstrcat(szContentType, "; Charset=UTF-8");
pszNewContentType = szContentType;
}
}
else if (GetLastError() == ERROR_WINHTTP_HEADER_NOT_FOUND)
{
pszNewContentType = "Content-Type: text/plain; Charset=UTF-8";
}
if (pszNewContentType)
{
fRetCode = HttpAddRequestHeadersA(_hHTTP, pszNewContentType,
(DWORD)-1L,
HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
INET_ASSERT(fRetCode);
}
}
return hr;
}
#if !defined(TRUE_ASYNC)
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;
}
#endif//!TRUE_ASYNC
/*
* CHttpRequest::CreateAsyncWorkerThread
*
*/
#if !defined(TRUE_ASYNC)
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)
{
(void)SetThreadToken(&_hWorkerThread, hThreadToken);
}
(void)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;
}
#endif//!TRUE_ASYNC
/*
* 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 that we are called from our apartment's thread.
if (GetCurrentThreadId() != _dwMainThreadId)
return RPC_E_WRONG_THREAD;
// Validate parameter
if (!IsValidVariant(varBody))
return E_INVALIDARG;
DEBUG_ENTER2_API((DBG_HTTP,
Dword,
"IWinHttpRequest::Send",
"VARIANT varBody"
));
TRACE_ENTER2_API((DBG_HTTP,
Dword,
"IWinHttpRequest::Send",
_hHTTP,
"VARIANT varBody"
));
// Validate state
if (_eState < CHttpRequest::OPENED)
goto ErrorCannotCallBeforeOpen;
if (_fAsync)
{
if ((_eState > CHttpRequest::OPENED) && (_eState < CHttpRequest::RESPONSE))
goto ErrorPending;
}
// Get the request body
hr = SetRequestBody(varBody);
if (FAILED(hr))
goto Error;
hr = SetRequiredRequestHeaders();
if (FAILED(hr))
goto Error;
try_again:
SetState(CHttpRequest::SENDING);
if (_fAsync)
{
#if !defined(TRUE_ASYNC)
hr = CreateAsyncWorkerThread();
#else
hr = StartAsyncSend();
#endif//!TRUE_ASYNC
if (FAILED(hr))
goto Error;
}
else
{
//register callback
if (WINHTTP_INVALID_STATUS_CALLBACK ==
WinHttpSetStatusCallback(_hHTTP,
SyncCallback,
WINHTTP_CALLBACK_STATUS_SECURE_FAILURE,
NULL))
goto ErrorFail;
// Send the HTTP request
fRetCode = WinHttpSendRequest(
_hHTTP,
NULL, 0, // No header info here
_szRequestBuffer,
_cbRequestBody,
_cbRequestBody,
reinterpret_cast<DWORD_PTR>(this));
if (!fRetCode)
{
goto ErrorFail;
}
SetState(CHttpRequest::SENT);
fRetCode = WinHttpReceiveResponse(_hHTTP, NULL);
if (!fRetCode)
{
goto ErrorFail;
}
// Read the response data
hr = ReadResponse();
if (FAILED(hr))
goto Error;
}
hr = NOERROR;
Cleanup:
SetErrorInfo(hr);
DEBUG_LEAVE_API(hr);
return hr;
ErrorCannotCallBeforeOpen:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN);
goto Error;
ErrorPending:
hr = E_PENDING;
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;
}
else if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_SECURE_FAILURE))
{
INET_ASSERT(FAILED(_hrSecureFailure));
hr = _hrSecureFailure;
}
_CP.FireOnError(hr);
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
*/
#if !defined(TRUE_ASYNC)
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;
SetState(CHttpRequest::SENT);
fRetCode = WinHttpReceiveResponse(_hHTTP, NULL);
if (!fRetCode)
goto ErrorFail;
if (!_bAborted)
{
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;
}
#endif//!TRUE_ASYNC
STDMETHODIMP
CHttpRequest::WaitForResponse(VARIANT varTimeout, VARIANT_BOOL * pboolSucceeded)
{
HRESULT hr = NOERROR;
bool bSucceeded= true;
DWORD dwTimeout;
// Validate that we are called from our apartment's thread.
if (GetCurrentThreadId() != _dwMainThreadId)
return RPC_E_WRONG_THREAD;
// 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);
DEBUG_ENTER_API((DBG_HTTP,
Dword,
"IWinHttpRequest::WaitForResponse",
"Timeout: %d, VARIANT_BOOL* pboolSucceeded: %#x",
dwTimeout,
pboolSucceeded
));
// Validate state.
if (_eState < CHttpRequest::SENDING)
goto ErrorCannotCallBeforeSend;
//
// WaitForResponse is a no-op if we're not in async mode.
//
if (_fAsync &&
#ifdef TRUE_ASYNC
_hCompleteEvent
#else
_hWorkerThread
#endif
)
{
//
// Has the worker thread has already finished or event signaled?
//
if (WaitForSingleObject(
#ifdef TRUE_ASYNC
_hCompleteEvent
#else
_hWorkerThread
#endif
, 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,
#ifdef TRUE_ASYNC
&_hCompleteEvent
#else
&_hWorkerThread
#endif
,
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();
//check if object was not aborted
if (_eState != CHttpRequest::CREATED)
bWaitAgain = true;
else
hr = _hrAsyncResult;
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);
}
}
//check if object was aborted
if (_eState == CHttpRequest::CREATED)
bSucceeded = false;
Cleanup:
if (pboolSucceeded)
{
*pboolSucceeded = (bSucceeded ? VARIANT_TRUE : VARIANT_FALSE);
}
if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_SECURE_FAILURE))
{
INET_ASSERT(FAILED(_hrSecureFailure));
hr = _hrSecureFailure;
}
SetErrorInfo(hr);
DEBUG_PRINT_API(ASYNC, INFO, (bSucceeded ? "Succeeded set to true\n" : "Succeeded set to false\n"))
DEBUG_LEAVE_API(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()
{
DEBUG_ENTER_API((DBG_HTTP,
Dword,
"IWinHttpRequest::Abort",
NULL
));
// Validate that we are called from our apartment's thread.
if (GetCurrentThreadId() != _dwMainThreadId)
return RPC_E_WRONG_THREAD;
//
// 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);
_hHTTP = NULL;
}
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);
_hConnection = NULL;
}
// Recycle the object.
Recycle();
}
DEBUG_LEAVE_API(NOERROR);
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;
}
STDMETHODIMP
CHttpRequest::SetClientCertificate(BSTR ClientCertificate)
{
BOOL fCertLocalMachine = FALSE;
BSTR bstrCertStore = NULL;
BSTR bstrCertSubject = NULL;
HRESULT hr;
if (!IsValidBstr(ClientCertificate))
goto ErrorInvalidArg;
hr = ParseSelectedCert(ClientCertificate,
&fCertLocalMachine,
&bstrCertStore,
&bstrCertSubject
);
// Only fill in new selection if parsed successfully.
if (hr == S_OK)
{
_fCertLocalMachine = fCertLocalMachine;
if (_bstrCertStore)
DL(SysFreeString)(_bstrCertStore);
_bstrCertStore = bstrCertStore;
if (_bstrCertSubject)
DL(SysFreeString)(_bstrCertSubject);
_bstrCertSubject = bstrCertSubject;
}
if (_hHTTP)
{
// We will try again later if this fails now, so
// don't worry about detecting an error.
SelectCertificate();
}
Exit:
SetErrorInfo(hr);
return hr;
ErrorInvalidArg:
hr = E_INVALIDARG;
goto Exit;
}
STDMETHODIMP
CHttpRequest::SetAutoLogonPolicy(WinHttpRequestAutoLogonPolicy AutoLogonPolicy)
{
HRESULT hr;
switch (AutoLogonPolicy)
{
case AutoLogonPolicy_Always:
_dwAutoLogonPolicy = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
break;
case AutoLogonPolicy_OnlyIfBypassProxy:
_dwAutoLogonPolicy = WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM;
break;
case AutoLogonPolicy_Never:
_dwAutoLogonPolicy = WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH;
break;
default:
goto ErrorInvalidArg;
}
if (_hHTTP)
{
if (!WinHttpSetOption(_hHTTP, WINHTTP_OPTION_AUTOLOGON_POLICY,
(void *) &_dwAutoLogonPolicy,
sizeof(_dwAutoLogonPolicy)))
goto ErrorFail;
}
hr = NOERROR;
Exit:
SetErrorInfo(hr);
return hr;
ErrorInvalidArg:
hr = E_INVALIDARG;
goto Exit;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
/*
* 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;
SAFEARRAY * psa = NULL;
VARIANT * pvarBody = NULL;
// varBody is validated by CHttpRequest::Send().
DL(VariantInit)(&varTemp);
// Free a previously set body and its response
if (_szRequestBuffer)
{
delete [] _szRequestBuffer;
_szRequestBuffer = NULL;
_cbRequestBody = 0;
}
_bSetUtf8Charset = false;
SafeRelease(_pResponseStream);
_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 DL(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 (DL(SafeArrayGetDim)(psa) != 1)
goto ErrorFail;
// Get access to the blob
hr = DL(SafeArrayAccessData)(psa, (void **)&pb);
if (FAILED(hr))
goto Error;
// Compute the data size from the upper and lower array bounds
hr = DL(SafeArrayGetLBound)(psa, 1, &lLBound);
if (FAILED(hr))
goto Error;
hr = DL(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);
}
DL(SafeArrayUnaccessData)(psa);
psa = NULL;
}
else
{
BSTR bstrBody = NULL;
bool bFreeString = false;
//
// Try a BSTR; avoiding to call GetBSTRFromVariant (which makes
// a copy) if possible.
//
if (V_VT(pvarBody) == VT_BSTR)
{
bstrBody = V_BSTR(pvarBody); // direct BSTR string, do not free
bFreeString = false;
}
else
{
hr = GetBSTRFromVariant(*pvarBody, &bstrBody);
if (SUCCEEDED(hr))
{
bFreeString = true;
}
else if (hr == E_INVALIDARG)
{
// GetBSTRFromVariant will return E_INVALIDARG if the
// call to VariantChangeType AVs. The VARIANT may be
// valid, but may simply not contain a BSTR, so if
// some other error is returned (most likely
// DISP_E_MISMATCH), then continue and see if the
// VARIANT contains an IStream object.
goto Error;
}
}
if (bstrBody)
{
hr = BSTRToUTF8(&_szRequestBuffer, &_cbRequestBody, bstrBody, &_bSetUtf8Charset);
if (bFreeString)
DL(SysFreeString)(bstrBody);
if (FAILED(hr))
goto Error;
}
else
{
// Try a Stream
if (V_VT(pvarBody) == VT_UNKNOWN || V_VT(pvarBody) == VT_DISPATCH)
{
IStream * pStm = NULL;
__try
{
hr = pvarBody->punkVal->QueryInterface(
IID_IStream,
(void **)&pStm);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
hr = E_INVALIDARG;
}
if (FAILED(hr))
goto Error;
hr = ReadFromStream(
&_szRequestBuffer,
&_cbRequestBody,
pStm);
pStm->Release();
if (FAILED(hr))
goto Error;
}
}
}
hr = NOERROR;
Cleanup:
DL(VariantClear)(&varTemp);
if (psa)
DL(SafeArrayUnaccessData)(psa);
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);
}
#ifdef TRUE_ASYNC
HRESULT
CHttpRequest::_GetResponseHeader(OLECHAR * wszHeader, BSTR * pbstrValue)
{
HRESULT hr;
// Validate state
if (_eState < CHttpRequest::SENDING)
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND);
else
hr = _GetResponseHeader2(wszHeader, pbstrValue, _hHTTP);
SetErrorInfo(hr);
return hr;
}
HRESULT
CHttpRequest::_GetResponseHeader2(OLECHAR * wszHeader, BSTR * pbstrValue, HINTERNET hInternet)
{
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;
*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(
hInternet,
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 = DL(SysAllocString)(wszHeaderValue);
if (!*pbstrValue)
goto ErrorOutOfMemory;
hr = NOERROR;
Cleanup:
if (wszHeaderValue)
delete [] wszHeaderValue;
return hr;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Error;
Error:
goto Cleanup;
}
#else//TRUE_ASYNC
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 = DL(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;
}
#endif//TRUE_ASYNC
/*
* 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)
{
if (wszAllHeaders != NULL)
{
delete [] wszAllHeaders;
wszAllHeaders = NULL;
}
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 = DL(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)
{
DL(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;
else if (_eState < CHttpRequest::RECEIVING)
goto ErrorRequestInProgress;
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;
ErrorRequestInProgress:
hr = E_PENDING;
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
// Validate state
if (_eState < CHttpRequest::SENDING)
goto ErrorCannotCallBeforeSend;
else if (_eState < CHttpRequest::RECEIVING)
goto ErrorRequestInProgress;
*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 = DL(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;
ErrorRequestInProgress:
hr = E_PENDING;
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;
DL(VariantInit)(pvarBody);
if (_cbResponseBody != 0)
{
HGLOBAL hGlobal;
hr = DL(GetHGlobalFromStream)(_pResponseStream, &hGlobal);
if (FAILED(hr))
goto Error;
const BYTE * pResponseData = (const BYTE *) GlobalLock(hGlobal);
if (!pResponseData)
goto ErrorFail;
hr = CreateVector(pvarBody, pResponseData, _cbResponseBody);
GlobalUnlock(hGlobal);
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;
ErrorFail:
hr = HRESULT_FROM_WIN32(::GetLastError());
goto Error;
Error:
if (pvarBody)
DL(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;
MIMECSETINFO mimeSetInfo;
IMultiLanguage * pMultiLanguage = NULL;
IMultiLanguage2 * pMultiLanguage2 = NULL;
CHAR szContentType[1024];
DWORD cb;
BSTR bstrCharset = NULL;
HGLOBAL hGlobal = NULL;
char * pResponseData = NULL;
// 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;
if (!_hHTTP)
{
hr = E_UNEXPECTED;
goto Error;
}
if (_cbResponseBody != 0)
{
hr = DL(GetHGlobalFromStream)(_pResponseStream, &hGlobal);
if (FAILED(hr))
goto Error;
pResponseData = (char *) GlobalLock(hGlobal);
if (!pResponseData)
goto ErrorFail;
}
else
{
*pbstrBody = NULL;
}
// Check if charset is present.
cb = sizeof(szContentType);
if (HttpQueryInfoA(_hHTTP,
WINHTTP_QUERY_CONTENT_TYPE,
NULL,
szContentType,
&cb,
NULL))
{
LPSTR lpszCharset;
LPSTR lpszCharsetEnd;
if ((lpszCharset = StrStrIA(szContentType, "charset=")) != NULL)
{
LPSTR lpszBeginQuote = NULL;
LPSTR lpszEndQuote = NULL;
if ((lpszBeginQuote = StrChrA(lpszCharset, '\"')) != NULL)
{
lpszEndQuote = StrChrA(lpszBeginQuote+1, '\"');
}
if (lpszEndQuote)
{
lpszCharset = lpszBeginQuote + 1;
lpszCharsetEnd = lpszEndQuote;
}
else
{
lpszCharset += sizeof("charset=")-1;
lpszCharsetEnd = StrChrA(lpszCharset, ';');
}
// only **STRLEN** to be passed to AsciiToBSTR
AsciiToBSTR(&bstrCharset, lpszCharset, (int)(lpszCharsetEnd ? (lpszCharsetEnd-lpszCharset) : lstrlen(lpszCharset)));
}
}
if (!bstrCharset)
{
// use ISO-8859-1
mimeSetInfo.uiInternetEncoding = 28591;
mimeSetInfo.uiCodePage = 1252;
// note unitialized wszCharset - not cached, not used.
}
else
{
// obtain codepage corresponding to charset
if (!g_pMimeInfoCache)
{
//create the mimeinfo cache.
DWORD dwStatus = 0;
if (!GlobalDataInitCritSec.Lock())
{
goto ErrorOutOfMemory;
}
if (!g_pMimeInfoCache)
{
g_pMimeInfoCache = new CMimeInfoCache(&dwStatus);
if(!g_pMimeInfoCache
|| dwStatus)
{
if (g_pMimeInfoCache)
delete g_pMimeInfoCache;
g_pMimeInfoCache = NULL;
GlobalDataInitCritSec.Unlock();
goto ErrorOutOfMemory;
}
}
GlobalDataInitCritSec.Unlock();
}
//check the cache for info
if (S_OK != g_pMimeInfoCache->GetCharsetInfo(bstrCharset, &mimeSetInfo))
{
// if info not in cache, get from mlang
hr = DL(CoCreateInstance)(CLSID_CMultiLanguage, NULL, CLSCTX_INPROC_SERVER,
IID_IMultiLanguage, (void**)&pMultiLanguage);
if (FAILED(hr))
{
goto ConvertError;
}
INET_ASSERT (pMultiLanguage);
pMultiLanguage->QueryInterface(IID_IMultiLanguage2, (void **)&pMultiLanguage2);
pMultiLanguage->Release();
if (!pMultiLanguage2)
{
goto ConvertError;
}
if (FAILED((pMultiLanguage2)->GetCharsetInfo(bstrCharset, &mimeSetInfo)))
{
//Check for known-exceptions
if (!StrCmpNIW(bstrCharset, L"ISO8859_1", MAX_MIMECSET_NAME))
{
mimeSetInfo.uiInternetEncoding = 28591;
mimeSetInfo.uiCodePage = 1252;
StrCpyNW(mimeSetInfo.wszCharset, L"ISO8859_1", lstrlenW(L"ISO8859_1")+1);
}
else
{
goto ConvertError;
}
}
// add obtained info to cache.
g_pMimeInfoCache->AddCharsetInfo(&mimeSetInfo);
}
}
// here only if we have mimeSetInfo filled in correctly.
hr = MultiByteToWideCharInternal(pbstrBody, pResponseData, (int)_cbResponseBody, mimeSetInfo.uiInternetEncoding, &pMultiLanguage2);
if (hr != S_OK)
{
MultiByteToWideCharInternal(pbstrBody, pResponseData, (int)_cbResponseBody, mimeSetInfo.uiCodePage, &pMultiLanguage2);
}
Cleanup:
if (hGlobal)
GlobalUnlock(hGlobal);
if (pMultiLanguage2)
{
pMultiLanguage2->Release();
}
SetErrorInfo(hr);
return hr;
ErrorCannotCallBeforeSend:
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND);
goto Error;
ErrorPending:
hr = E_PENDING;
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(::GetLastError());
goto Error;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
Error:
goto Cleanup;
ConvertError:
hr = HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION);
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;
DL(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)
DL(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;
LARGE_INTEGER liReset = { 0, 0 };
if (!ppStm)
goto ErrorInvalidArg;
*ppStm = NULL;
if (_cbResponseBody)
{
INET_ASSERT(_pResponseStream);
hr = _pResponseStream->Clone(ppStm);
if (FAILED(hr))
goto Error;
}
else
{
//
// No response body, return an empty stream object
//
ULARGE_INTEGER size = { 0, 0 };
hr = DL(CreateStreamOnHGlobal)(NULL, TRUE, ppStm);
if (FAILED(hr))
goto Error;
(*ppStm)->SetSize(size);
}
hr = (*ppStm)->Seek(liReset, STREAM_SEEK_SET, NULL);
if (FAILED(hr))
goto Error;
hr = NOERROR;
Cleanup:
return hr;
ErrorInvalidArg:
hr = E_INVALIDARG;
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) = DL(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) = DL(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) = (_dwEscapeFlag & WINHTTP_FLAG_ESCAPE_PERCENT) ? VARIANT_TRUE : VARIANT_FALSE;
V_VT(Value) = VT_BOOL;
break;
case WinHttpRequestOption_EnableCertificateRevocationCheck:
V_BOOL(Value) = _fCheckForRevocation ? VARIANT_TRUE : VARIANT_FALSE;
V_VT(Value) = VT_BOOL;
break;
case WinHttpRequestOption_SslErrorIgnoreFlags:
if (_dwSslIgnoreFlags)
V_I4(Value) = (long) (_dwSslIgnoreFlags & SslErrorFlag_Ignore_All);
else
V_I4(Value) = (long) 0;
V_VT(Value) = VT_I4;
break;
case WinHttpRequestOption_UrlEscapeDisable:
V_BOOL(Value) = (_dwEscapeFlag & WINHTTP_FLAG_ESCAPE_DISABLE) ? VARIANT_TRUE : VARIANT_FALSE;
V_VT(Value) = VT_BOOL;
break;
case WinHttpRequestOption_UrlEscapeDisableQuery:
V_BOOL(Value) = (_dwEscapeFlag & WINHTTP_FLAG_ESCAPE_DISABLE_QUERY) ? VARIANT_TRUE : VARIANT_FALSE;
V_VT(Value) = VT_BOOL;
break;
case WinHttpRequestOption_EnableRedirects:
BOOL bEnableRedirects;
if (_dwRedirectPolicy == WINHTTP_OPTION_REDIRECT_POLICY_NEVER)
{
bEnableRedirects = FALSE;
}
else
{
bEnableRedirects = TRUE; // for this particular query we return TRUE even HTTPS->HTTP is not allowed
// we are consistent with the SetOption where TRUE means "we allow redirs
// except HTTPS->HTTP ones
}
V_BOOL(Value) = bEnableRedirects ? VARIANT_TRUE: VARIANT_FALSE;
V_VT(Value) = VT_BOOL;
break;
case WinHttpRequestOption_EnableHttpsToHttpRedirects:
V_BOOL(Value) = ((_dwRedirectPolicy == WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS) ? VARIANT_TRUE : VARIANT_FALSE);
V_VT(Value) = VT_BOOL;
break;
case WinHttpRequestOption_EnablePassportAuthentication:
V_BOOL(Value) = ((_dwPassportConfig == (WINHTTP_DISABLE_PASSPORT_AUTH | WINHTTP_DISABLE_PASSPORT_KEYRING)) ? VARIANT_FALSE : VARIANT_TRUE);
V_VT(Value) = VT_BOOL;
break;
case WinHttpRequestOption_MaxAutomaticRedirects:
V_I4(Value) = _lMaxAutomaticRedirects;
V_VT(Value) = VT_I4;
break;
case WinHttpRequestOption_MaxResponseHeaderSize:
V_I4(Value) = _lMaxResponseHeaderSize;
V_VT(Value) = VT_I4;
break;
case WinHttpRequestOption_MaxResponseDrainSize:
V_I4(Value) = _lMaxResponseDrainSize;
V_VT(Value) = VT_I4;
break;
case WinHttpRequestOption_EnableTracing:
{
BOOL fEnableTracing;
DWORD dwBufferSize = sizeof(BOOL);
if (!WinHttpQueryOption(NULL,
WINHTTP_OPTION_ENABLETRACING,
(LPVOID)&fEnableTracing,
&dwBufferSize))
goto ErrorFail;
V_BOOL(Value) = fEnableTracing ? VARIANT_TRUE : VARIANT_FALSE;
V_VT(Value) = VT_BOOL;
break;
}
case WinHttpRequestOption_RevertImpersonationOverSsl:
V_BOOL(Value) = _bEnableSslImpersonation ? VARIANT_FALSE : VARIANT_TRUE;
V_VT(Value) = VT_BOOL;
break;
case WinHttpRequestOption_EnableHttp1_1:
V_BOOL(Value) = _bHttp1_1Mode ? VARIANT_TRUE : VARIANT_FALSE;
V_VT(Value) = VT_BOOL;
break;
default:
DL(VariantInit)(Value);
hr = E_INVALIDARG;
goto Error;
}
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;
hr = GetBSTRFromVariant(Value, &bstrUserAgent);
if (FAILED(hr) || !bstrUserAgent)
return E_INVALIDARG;
if (*bstrUserAgent == L'\0')
{
DL(SysFreeString)(bstrUserAgent);
return E_INVALIDARG;
}
if (_hInet)
{
if (!WinHttpSetOption(_hInet, WINHTTP_OPTION_USER_AGENT,
bstrUserAgent,
lstrlenW(bstrUserAgent)))
{
goto ErrorFail;
}
}
if (_hHTTP)
{
if (!WinHttpSetOption(_hHTTP, WINHTTP_OPTION_USER_AGENT,
bstrUserAgent,
lstrlenW(bstrUserAgent)))
{
goto ErrorFail;
}
}
if (_bstrUserAgent)
DL(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);
if (fEscapePercent)
_dwEscapeFlag |= WINHTTP_FLAG_ESCAPE_PERCENT;
else
_dwEscapeFlag &= ~(DWORD)WINHTTP_FLAG_ESCAPE_PERCENT;
break;
}
case WinHttpRequestOption_UrlEscapeDisable:
{
BOOL fEscape = GetBoolFromVariant(Value, FALSE);
if (fEscape)
_dwEscapeFlag |= WINHTTP_FLAG_ESCAPE_DISABLE;
else
_dwEscapeFlag &= ~(DWORD)WINHTTP_FLAG_ESCAPE_DISABLE;
break;
}
case WinHttpRequestOption_UrlEscapeDisableQuery:
{
BOOL fEscape = GetBoolFromVariant(Value, FALSE);
if (fEscape)
_dwEscapeFlag |= WINHTTP_FLAG_ESCAPE_DISABLE_QUERY;
else
_dwEscapeFlag &= ~(DWORD)WINHTTP_FLAG_ESCAPE_DISABLE_QUERY;
break;
}
case WinHttpRequestOption_EnableCertificateRevocationCheck:
{
_fCheckForRevocation = GetBoolFromVariant(Value, TRUE);
if (_hHTTP && _fCheckForRevocation)
{
DWORD dwOptions = WINHTTP_ENABLE_SSL_REVOCATION;
WinHttpSetOption(_hHTTP,
WINHTTP_OPTION_ENABLE_FEATURE,
(LPVOID)&dwOptions,
sizeof(dwOptions));
}
break;
}
case WinHttpRequestOption_SslErrorIgnoreFlags:
{
DWORD dwSslIgnoreFlags = GetDwordFromVariant(Value, _dwSslIgnoreFlags);
if (dwSslIgnoreFlags & ~SECURITY_INTERNET_MASK)
{
return E_INVALIDARG;
}
dwSslIgnoreFlags &= SslErrorFlag_Ignore_All;
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 bstrCertSelection;
hr = GetBSTRFromVariant(Value, &bstrCertSelection);
if (FAILED(hr))
{
hr = E_INVALIDARG;
goto Error;
}
hr = SetClientCertificate(bstrCertSelection);
DL(SysFreeString)(bstrCertSelection);
if (FAILED(hr))
goto Error;
break;
}
case WinHttpRequestOption_EnableRedirects:
{
BOOL fEnableRedirects = GetBoolFromVariant(Value, TRUE);
DWORD dwRedirectPolicy =
fEnableRedirects ? WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP : WINHTTP_OPTION_REDIRECT_POLICY_NEVER;
if ((dwRedirectPolicy == WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP)
&& (_dwRedirectPolicy == WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS))
{
// "always" -> "disalow" transition no-op
// this means the app enabled HTTPS->HTTP before this call, and attempt to enable redir
// should not move us away from the always state
break;
}
if (_hInet)
{
if (!WinHttpSetOption(_hInet,
WINHTTP_OPTION_REDIRECT_POLICY,
(LPVOID)&dwRedirectPolicy,
sizeof(DWORD)))
goto ErrorFail;
}
if (_hHTTP)
{
if (!WinHttpSetOption(_hHTTP,
WINHTTP_OPTION_REDIRECT_POLICY,
(LPVOID)&dwRedirectPolicy,
sizeof(DWORD)))
goto ErrorFail;
}
_dwRedirectPolicy = dwRedirectPolicy;
break;
}
case WinHttpRequestOption_EnableHttpsToHttpRedirects:
{
BOOL fEnableHttpsToHttpRedirects = GetBoolFromVariant(Value, FALSE);
DWORD dwRedirectPolicy = (fEnableHttpsToHttpRedirects
? WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS
: WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP);
if (_dwRedirectPolicy == WINHTTP_OPTION_REDIRECT_POLICY_NEVER)
{
// "never" -> "always" or "never" -> "disalow" transition not allowed
// this means the app disabled redirect before this call, it will have to re-enable redirect
// before it can enable or disable HTTPS->HTTP
break;
}
if (_hInet)
{
if (!WinHttpSetOption(_hInet,
WINHTTP_OPTION_REDIRECT_POLICY,
(LPVOID)&dwRedirectPolicy,
sizeof(DWORD)))
goto ErrorFail;
}
if (_hHTTP)
{
if (!WinHttpSetOption(_hHTTP,
WINHTTP_OPTION_REDIRECT_POLICY,
(LPVOID)&dwRedirectPolicy,
sizeof(DWORD)))
goto ErrorFail;
}
_dwRedirectPolicy = dwRedirectPolicy;
break;
}
case WinHttpRequestOption_SecureProtocols:
{
DWORD dwSecureProtocols = GetDwordFromVariant(Value, _dwSecureProtocols);
if (dwSecureProtocols & ~WINHTTP_FLAG_SECURE_PROTOCOL_ALL)
{
return E_INVALIDARG;
}
_dwSecureProtocols = dwSecureProtocols;
if (_hInet)
{
// Set the per session SSL protocols.
// This can be done at any time, so there's no need to
// keep track of this here.
if (!WinHttpSetOption(_hInet,
WINHTTP_OPTION_SECURE_PROTOCOLS,
(LPVOID)&dwSecureProtocols,
sizeof(dwSecureProtocols)))
goto ErrorFail;
}
break;
}
case WinHttpRequestOption_EnablePassportAuthentication:
{
BOOL fEnablePassportAuth = GetBoolFromVariant(Value, FALSE);
DWORD dwPassportConfig = (fEnablePassportAuth
? (WINHTTP_ENABLE_PASSPORT_AUTH | WINHTTP_ENABLE_PASSPORT_KEYRING)
: (WINHTTP_DISABLE_PASSPORT_AUTH | WINHTTP_DISABLE_PASSPORT_KEYRING));
if (_hInet)
{
if (!WinHttpSetOption(_hInet,
WINHTTP_OPTION_CONFIGURE_PASSPORT_AUTH,
(LPVOID)&dwPassportConfig,
sizeof(DWORD)))
goto ErrorFail;
}
_dwPassportConfig = dwPassportConfig;
break;
}
case WinHttpRequestOption_EnableTracing:
{
BOOL fEnableTracing = GetBoolFromVariant(Value, TRUE);
if (!WinHttpSetOption(NULL,
WINHTTP_OPTION_ENABLETRACING,
(LPVOID)&fEnableTracing,
sizeof(BOOL)))
goto ErrorFail;
break;
}
case WinHttpRequestOption_RevertImpersonationOverSsl:
{
if (_hInet)
{
// Must be called before the Open method because
// the open method also creates a request handle.
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_AFTER_OPEN);
goto Error;
}
else
{
_bEnableSslImpersonation = (GetBoolFromVariant(Value, TRUE) ? FALSE : TRUE);
}
break;
}
case WinHttpRequestOption_MaxAutomaticRedirects:
case WinHttpRequestOption_MaxResponseHeaderSize:
case WinHttpRequestOption_MaxResponseDrainSize:
{
DWORD dwSetting = (DWORD)-1;
LONG* plResultTarget = NULL;
switch(Option)
{
case WinHttpRequestOption_MaxAutomaticRedirects:
dwSetting = WINHTTP_OPTION_MAX_HTTP_AUTOMATIC_REDIRECTS;
plResultTarget = &_lMaxAutomaticRedirects;
break;
case WinHttpRequestOption_MaxResponseHeaderSize:
dwSetting = WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE;
plResultTarget = &_lMaxResponseHeaderSize;
break;
case WinHttpRequestOption_MaxResponseDrainSize:
dwSetting = WINHTTP_OPTION_MAX_RESPONSE_DRAIN_SIZE;
plResultTarget = &_lMaxResponseDrainSize;
break;
default:
INET_ASSERT(0); // shouldn't be possible
goto ErrorFail;
}
LONG lInput = GetLongFromVariant(Value, -1);
DWORD dwInput = (DWORD)lInput;
DWORD dwInputSize = sizeof(dwInput);
if (lInput < 0 || lInput != (LONG)dwInput)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
goto Error;
}
if (_hHTTP != NULL
&& TRUE != WinHttpSetOption( _hHTTP, dwSetting, &dwInput, dwInputSize))
{
goto ErrorFail;
}
*plResultTarget = lInput;
break;
}
case WinHttpRequestOption_EnableHttp1_1:
if (_hHTTP != NULL)
{
hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_CANNOT_CALL_AFTER_OPEN);
goto Error;
}
_bHttp1_1Mode = GetBoolFromVariant(Value, TRUE);
break;
default:
return E_INVALIDARG;
}
hr = NOERROR;
Cleanup:
SetErrorInfo(hr);
return hr;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Error;
Error:
goto Cleanup;
}
IErrorInfo *
CHttpRequest::CreateErrorObject(HRESULT hr)
{
INET_ASSERT(FAILED(hr));
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 NULL;
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("winhttp.dll");
error = errcode;
}
}
rc = ::FormatMessageW(dwFmtMsgFlag, hModule,
error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
pwszMessage,
dwSize,
NULL);
if (rc != 0)
{
if (SUCCEEDED(DL(CreateErrorInfo)(&pCErrInfo)))
{
if (SUCCEEDED(pCErrInfo->QueryInterface(IID_IErrorInfo, (void **) &pErrInfo)))
{
pCErrInfo->SetSource(L"WinHttp.WinHttpRequest");
pCErrInfo->SetGUID(IID_IWinHttpRequest);
pCErrInfo->SetDescription(pwszMessage);
}
pCErrInfo->Release();
}
}
delete [] pwszMessage;
return pErrInfo;
}
void
CHttpRequest::SetErrorInfo(HRESULT hr)
{
if (FAILED(hr))
{
IErrorInfo * pErrInfo = CreateErrorObject(hr);
if (pErrInfo)
{
DL(SetErrorInfo)(0, pErrInfo);
pErrInfo->Release();
}
}
}
BOOL
CHttpRequest::SelectCertificate()
{
HCERTSTORE hCertStore = NULL;
BOOL fRet = FALSE;
HANDLE hThreadToken = NULL;
PCCERT_CONTEXT pCertContext = NULL;
// Make sure security DLLs are loaded
if (LoadSecurity() != ERROR_SUCCESS)
return FALSE;
// If impersonating, revert while trying to obtain the cert
if (!_bEnableSslImpersonation && OpenThreadToken(GetCurrentThread(), (TOKEN_IMPERSONATE | TOKEN_READ),
FALSE,
&hThreadToken))
{
INET_ASSERT(hThreadToken != 0);
RevertToSelf();
}
hCertStore = (*g_pfnCertOpenStore)(CERT_STORE_PROV_SYSTEM,
0,
0,
CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG |
(_fCertLocalMachine ? CERT_SYSTEM_STORE_LOCAL_MACHINE:
CERT_SYSTEM_STORE_CURRENT_USER),
_bstrCertStore ? _bstrCertStore : L"MY");
if (!hCertStore)
{
TRACE_PRINT_API(THRDINFO,
INFO,
("Unable to open certificate store %s\\%Q; GetLastError() = %s [%d]\n",
_fCertLocalMachine? "Local Machine": "Current User",
_bstrCertStore ? _bstrCertStore : L"MY",
InternetMapError(::GetLastError()),
::GetLastError()
));
goto Cleanup;
}
if (_bstrCertSubject && _bstrCertSubject[0])
{
CERT_RDN SubjectRDN;
CERT_RDN_ATTR rdnAttr;
rdnAttr.pszObjId = szOID_COMMON_NAME;
rdnAttr.dwValueType = CERT_RDN_ANY_TYPE;
rdnAttr.Value.cbData = lstrlenW(_bstrCertSubject) * sizeof(WCHAR);
rdnAttr.Value.pbData = (BYTE *) _bstrCertSubject;
SubjectRDN.cRDNAttr = 1;
SubjectRDN.rgRDNAttr = &rdnAttr;
//
// First try an exact match for the certificate lookup.
// If that fails, then try a prefix match.
//
pCertContext = (*g_pfnCertFindCertificateInStore)(hCertStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
CERT_CASE_INSENSITIVE_IS_RDN_ATTRS_FLAG |
CERT_UNICODE_IS_RDN_ATTRS_FLAG,
CERT_FIND_SUBJECT_ATTR,
&SubjectRDN,
NULL);
if (! pCertContext)
{
pCertContext = (*g_pfnCertFindCertificateInStore)(hCertStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR,
(LPVOID) _bstrCertSubject,
NULL);
}
}
else
{
pCertContext = (*g_pfnCertFindCertificateInStore)(hCertStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0,
CERT_FIND_ANY,
NULL,
NULL);
}
if (pCertContext)
{
fRet = WinHttpSetOption(_hHTTP,
WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
(LPVOID) pCertContext,
sizeof(CERT_CONTEXT));
}
else
{
TRACE_PRINT_API(THRDINFO,
INFO,
("Unable to find certificate %Q in store %s\\%Q; GetLastError() = %s [%d]\n",
_bstrCertSubject,
_fCertLocalMachine? "Local Machine": "Current User",
_bstrCertStore ? _bstrCertStore : L"MY",
InternetMapError(::GetLastError()),
::GetLastError()
));
}
Cleanup:
if (pCertContext)
(*g_pfnCertFreeCertificateContext)(pCertContext);
if (hCertStore)
(*g_pfnCertCloseStore)(hCertStore, 0);
// Restore the impersonating state for the current thread.
if (hThreadToken)
{
(void)SetThreadToken(NULL, hThreadToken);
CloseHandle(hThreadToken);
}
return fRet;
}
/*
* ParseSelectedCert
*
* Purpose:
* Given a certificate, breakdown the location
* (local machine vs. current user), store (MY, CA, etc.), and
* subject name of the form:
* "[CURRENT_USER | LOCAL_MACHINE [\store]\]cert_subject_name"
*
* The backslash character is the delimiter, and the CURRENT_USER vs.
* LOCAL_MACHINE choice can optionally include a store (any store
* can be chosen). If there are more than two backslash characters
* present in the string, this function assumes everything after the
* second backslash is the cert subject name fragment to use for finding
* a match.
*
* If the optional pieces are not specified "CURRENT_USER\MY" are
* the defaults chosen.
*
* The pbstrLocation, pbstrStore, and pbstrSubject parameters
* are allocated and filled in with the default or parsed strings, or set
* to NULL if a failure occurs (e.g. out of memory or invalid param).
* The caller should free all params on success via DL(SysFreeString).
*/
HRESULT ParseSelectedCert(BSTR bstrSelection,
LPBOOL pfLocalMachine,
BSTR *pbstrStore,
BSTR *pbstrSubject
)
{
HRESULT hr = S_OK;
LPWSTR lpwszSelection = bstrSelection;
LPWSTR lpwszStart = lpwszSelection;
*pfLocalMachine = FALSE;
*pbstrStore = NULL;
*pbstrSubject = NULL;
if (!bstrSelection)
{
// When NULL, fill in an empty string to simulate first enum
*pbstrSubject = DL(SysAllocString)(L"");
if (!*pbstrSubject)
{
hr = E_OUTOFMEMORY;
goto quit;
}
// Need to also fill in the default "MY" store.
goto DefaultStore;
}
while (*lpwszSelection && *lpwszSelection != L'\\')
lpwszSelection++;
if (*lpwszSelection == L'\\')
{
// LOCAL_MACHINE vs. CURRENT_USER was selected.
// Check for invalid arg since it must match either.
if (!wcsncmp(lpwszStart, L"LOCAL_MACHINE", lpwszSelection-lpwszStart))
{
*pfLocalMachine = TRUE;
}
else if (wcsncmp(lpwszStart, L"CURRENT_USER", lpwszSelection-lpwszStart))
{
hr = E_INVALIDARG;
goto quit;
}
// else already defaults to having *pfLocalMachine initialized to FALSE
lpwszStart = ++lpwszSelection;
// Now look for the optional choice on the store
while (*lpwszSelection && *lpwszSelection != L'\\')
lpwszSelection++;
if (*lpwszSelection == L'\\')
{
// Accept any store name.
// When opening the store, it will fail if the selected
// store does not exist.
*pbstrStore = DL(SysAllocStringLen)(lpwszStart, (UINT) (lpwszSelection-lpwszStart));
if (!*pbstrStore)
{
hr = E_OUTOFMEMORY;
goto Cleanup;
}
lpwszStart = ++lpwszSelection;
}
}
// lpwszStart points to the portion designating the subject string
// which could be part or all of pbstrSelection.
//
// If the string is empty, then fill in an empty string, which
// will mean to use the first enumerated cert.
*pbstrSubject = DL(SysAllocString)(lpwszStart);
if (!*pbstrSubject)
{
hr = E_OUTOFMEMORY;
goto Cleanup;
}
DefaultStore:
// Fill in MY store default if the store name wasn't specified.
if (!*pbstrStore)
{
// Default to MY store
*pbstrStore = DL(SysAllocString)(L"MY");
if (!*pbstrStore)
{
hr = E_OUTOFMEMORY;
goto Cleanup;
}
}
quit:
return hr;
Cleanup:
if (*pbstrStore)
{
DL(SysFreeString)(*pbstrStore);
*pbstrStore = NULL;
}
if (*pbstrSubject)
{
DL(SysFreeString)(*pbstrSubject);
*pbstrSubject = NULL;
}
goto quit;
}
#ifdef TRUE_ASYNC
HRESULT
CHttpRequest::PrepareToReadData(HINTERNET hInternet)
{
HRESULT hr = NOERROR;
BSTR bstrContentType = NULL;
DWORD dwStatus = 0;
BOOL fRetCode;
DWORD cb;
// Get the content length
_dwContentLength = 0;
fRetCode = GetContentLengthIfResponseNotChunked(hInternet, &_dwContentLength);
INET_ASSERT((_pResponseStream == NULL) && (_cbResponseBody == 0));
hr = DL(CreateStreamOnHGlobal)(NULL, TRUE, &_pResponseStream);
if (FAILED(hr))
goto ErrorFail;
// pre-set response stream size if we have a Content-Length
if (fRetCode)
{
ULARGE_INTEGER size;
size.LowPart = _dwContentLength;
size.HighPart = 0;
_pResponseStream->SetSize(size);
}
else
{
// Content-Length was not specified in the response, but this
// does not mean Content-Length==0. We will keep reading until
// either no more data is available. Set dwContentLength to 4GB
// to trick our read loop into reading until QDA reports EOF
_dwContentLength = (DWORD)(-1L);
ULARGE_INTEGER size;
size.LowPart = SIZEOF_BUFFER;
size.HighPart = 0;
_pResponseStream->SetSize(size);
}
if ((_dwContentLength > 0) && (_Buffer == NULL))
{
_Buffer = New BYTE[SIZEOF_BUFFER];
if (!_Buffer)
{
goto ErrorOutOfMemory;
}
}
//get status
cb = sizeof(dwStatus);
if (!HttpQueryInfoA(
hInternet,
HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX,
&dwStatus,
&cb,
0))
goto ErrorFail;
//get content type
hr = _GetResponseHeader2(L"Content-Type", &bstrContentType, hInternet);
if (FAILED(hr))
{
bstrContentType = DL(SysAllocString)(L"");
if (bstrContentType == NULL)
goto ErrorOutOfMemory;
hr = NOERROR;
}
_CP.FireOnResponseStart((long)dwStatus, bstrContentType);
hr = NOERROR;
Cleanup:
if (bstrContentType)
DL(SysFreeString)(bstrContentType);
return hr;
ErrorOutOfMemory:
hr = E_OUTOFMEMORY;
goto Error;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Error;
Error:
SafeRelease(_pResponseStream);
_cbResponseBody = NULL;
goto Cleanup;
}
#define ASYNC_SEND_CALLBACK_NOTIFICATIONS \
WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE |\
WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE |\
WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE |\
WINHTTP_CALLBACK_STATUS_READ_COMPLETE |\
WINHTTP_CALLBACK_STATUS_REQUEST_ERROR |\
WINHTTP_CALLBACK_STATUS_SECURE_FAILURE
HRESULT CHttpRequest::StartAsyncSend()
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"IWinHttpRequest::StartAsyncSend",
NULL
));
HRESULT hr;
hr = _CP.CreateEventSinksMarshaller();
if (FAILED(hr))
goto Error;
hr = NOERROR;
//init vars
_hrAsyncResult = NOERROR;
_dwNumberOfBytesAvailable = 0;
_cbNumberOfBytesRead = 0;
if (!_hCompleteEvent)
{
_hCompleteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (_hCompleteEvent == NULL)
goto ErrorFail;
}
else
{
if (!ResetEvent(_hCompleteEvent))
goto ErrorFail;
}
//register callback
if (WINHTTP_INVALID_STATUS_CALLBACK ==
WinHttpSetStatusCallback(_hHTTP,
AsyncCallback,
ASYNC_SEND_CALLBACK_NOTIFICATIONS,
NULL))
goto ErrorFail;
// Initiate async HTTP request
SetState(SENDING);
if (!WinHttpSendRequest(
_hHTTP,
NULL, 0, // No header info here
_szRequestBuffer,
_cbRequestBody,
_cbRequestBody,
reinterpret_cast<DWORD_PTR>(this)))
goto ErrorFailWinHttpAPI;
Cleanup:
DEBUG_LEAVE(hr);
return hr;
ErrorFail:
hr = HRESULT_FROM_WIN32(GetLastError());
Error:
if (_hCompleteEvent)
{
CloseHandle(_hCompleteEvent);
_hCompleteEvent = NULL;
}
goto Cleanup;
ErrorFailWinHttpAPI:
hr = HRESULT_FROM_WIN32(GetLastError());
_CP.FireOnError(hr);
goto Error;
}
void CHttpRequest::CompleteDataRead(bool bNotAborted, HINTERNET hInternet)
{
DEBUG_ENTER((DBG_HTTP,
None,
"IWinHttpRequest::CompleteDataRead",
"bNotAborted: %s",
bNotAborted ? "true" : "false"
));
//unregister callback
WinHttpSetStatusCallback(hInternet,
NULL,
ASYNC_SEND_CALLBACK_NOTIFICATIONS,
NULL);
if (_pResponseStream)
{
ULARGE_INTEGER size;
// set final size on stream
size.LowPart = _cbResponseBody;
size.HighPart = 0;
_pResponseStream->SetSize(size);
}
if (bNotAborted)
_CP.FireOnResponseFinished();
SetEvent(_hCompleteEvent);
DEBUG_LEAVE(0);
}
void CALLBACK CHttpRequest::SyncCallback(
HINTERNET hInternet,
DWORD_PTR dwContext,
DWORD dwInternetStatus,
LPVOID lpvStatusInformation,
DWORD dwStatusInformationLength)
{
UNREFERENCED_PARAMETER(hInternet);
UNREFERENCED_PARAMETER(dwStatusInformationLength);
DEBUG_ENTER((DBG_HTTP,
None,
"CHttpRequest::SyncCallback",
"hInternet: %#x, dwContext: %#x, dwInternetStatus:%#x, %#x, %#d",
hInternet,
dwContext,
dwInternetStatus,
lpvStatusInformation,
dwStatusInformationLength
));
if (dwContext == NULL)
{
return;
}
CHttpRequest * pRequest = reinterpret_cast<CHttpRequest*>(dwContext);
// unexpected notification?
INET_ASSERT(dwInternetStatus == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE);
if (!pRequest->_bAborted)
{
switch (dwInternetStatus)
{
case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
pRequest->_hrSecureFailure = SecureFailureFromStatus(*((DWORD *)lpvStatusInformation));
break;
}
}
DEBUG_LEAVE(0);
}
void CALLBACK CHttpRequest::AsyncCallback(HINTERNET hInternet,
DWORD_PTR dwContext,
DWORD dwInternetStatus,
LPVOID lpvStatusInformation,
DWORD dwStatusInformationLength)
{
DEBUG_ENTER((DBG_HTTP,
None,
"IWinHttpRequest::AsyncCallback",
"hInternet: %#x, dwContext: %#x, dwInternetStatus:%#x, %#x, %#d",
hInternet,
dwContext,
dwInternetStatus,
lpvStatusInformation,
dwStatusInformationLength
));
if (dwContext == NULL)
{
DEBUG_PRINT_API(ASYNC, FATAL, ("Unexpected: dwContext parameter is zero!\n"))
DEBUG_LEAVE(0);
return;
}
CHttpRequest* pRequest = reinterpret_cast<CHttpRequest*>(dwContext);
if ((dwInternetStatus & ASYNC_SEND_CALLBACK_NOTIFICATIONS) == 0)
{
//unexpected notification
DEBUG_PRINT_API(ASYNC, FATAL, ("Unexpected dwInternetStatus value!\n"))
pRequest->_hrAsyncResult = HRESULT_FROM_WIN32(ERROR_WINHTTP_INTERNAL_ERROR);
goto Error;
}
if (pRequest->_bAborted)
goto Aborted;
DWORD dwBytesToRead = 0;
switch (dwInternetStatus)
{
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE :
//SR completed
pRequest->SetState(SENT);
if (!::WinHttpReceiveResponse(hInternet, NULL))
goto ErrorFail;
break;
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE :
//RR completed, read data
pRequest->SetState(RECEIVING);
pRequest->_hrAsyncResult = pRequest->PrepareToReadData(hInternet);
if (FAILED(pRequest->_hrAsyncResult))
goto Error;
if (pRequest->_dwContentLength == 0)
{
goto RequestComplete;
}
//start reading data
dwBytesToRead = min(pRequest->_dwContentLength, SIZEOF_BUFFER);
break;
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE :
//QDA completed
INET_ASSERT(dwStatusInformationLength == sizeof(DWORD));
pRequest->_dwNumberOfBytesAvailable = *(LPDWORD)lpvStatusInformation;
if (pRequest->_dwNumberOfBytesAvailable)
dwBytesToRead = min(pRequest->_dwNumberOfBytesAvailable, SIZEOF_BUFFER); //continue read
else
goto RequestComplete; //no more data to read
break;
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE :
//RD completed
pRequest->_cbNumberOfBytesRead = dwStatusInformationLength;
if (pRequest->_cbNumberOfBytesRead)
{
HRESULT hr = pRequest->_pResponseStream->Write(pRequest->_Buffer,
pRequest->_cbNumberOfBytesRead,
NULL);
if (FAILED(hr))
{
pRequest->_hrAsyncResult = E_OUTOFMEMORY;
goto Error;
}
pRequest->_CP.FireOnResponseDataAvailable((const BYTE *)pRequest->_Buffer, pRequest->_cbNumberOfBytesRead);
pRequest->_cbResponseBody += pRequest->_cbNumberOfBytesRead;
if (pRequest->_cbResponseBody >= pRequest->_dwContentLength)
{
goto RequestComplete;
}
else
{
//perform QDA to make sure there is no more data to read
if (pRequest->_bAborted)
goto Aborted;
if (!WinHttpQueryDataAvailable(hInternet, NULL))
goto ErrorFail;
}
}
else
goto RequestComplete; //no more data to read
break;
case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
pRequest->_hrSecureFailure = SecureFailureFromStatus(*((DWORD *)lpvStatusInformation));
goto Cleanup;
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR :
{
DWORD dwError = ((LPWINHTTP_ASYNC_RESULT)lpvStatusInformation)->dwError;
if (dwError == ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED)
{
if (!pRequest->_bRetriedWithCert)
{
pRequest->_bRetriedWithCert = TRUE;
if (pRequest->SelectCertificate())
{
// Initiate async HTTP request
pRequest->SetState(SENDING);
if (!WinHttpSendRequest(
hInternet,
NULL, 0, // No header info here
pRequest->_szRequestBuffer,
pRequest->_cbRequestBody,
pRequest->_cbRequestBody,
reinterpret_cast<DWORD_PTR>(pRequest)))
goto ErrorFail;
break;
}
}
}
goto ErrorFail;
}
}
if (dwBytesToRead)
{
if (pRequest->_bAborted)
goto Aborted;
if (!WinHttpReadData(hInternet,
pRequest->_Buffer,
dwBytesToRead,
NULL))
goto ErrorFail;
}
Cleanup:
DEBUG_LEAVE(0);
return;
Aborted:
pRequest->CompleteDataRead(false, hInternet);
goto Cleanup;
ErrorFail:
pRequest->_hrAsyncResult = HRESULT_FROM_WIN32(GetLastError());
if (pRequest->_hrAsyncResult == HRESULT_FROM_WIN32(ERROR_WINHTTP_SECURE_FAILURE))
{
INET_ASSERT(FAILED(pRequest->_hrSecureFailure));
pRequest->_hrAsyncResult = pRequest->_hrSecureFailure;
}
pRequest->_CP.FireOnError(pRequest->_hrAsyncResult);
Error:
DEBUG_PRINT_API(ASYNC, ERROR, ("Error set: %#x\n", pRequest->_hrAsyncResult))
pRequest->CompleteDataRead(false, hInternet);
goto Cleanup;
RequestComplete:
pRequest->SetState(RESPONSE);
pRequest->CompleteDataRead(true, hInternet);
goto Cleanup;
}
#endif//TRUE_ASYNC
/*
* BSTRToUTF8
*
* Purpose:
* Convert a BSTR to UTF-8
*
*/
static
HRESULT
BSTRToUTF8(char ** psz, DWORD * pcbUTF8, BSTR bstr, bool * pbSetUtf8Charset)
{
UINT cch = lstrlenW(bstr);
bool bSimpleConversion = false;
*pcbUTF8 = 0;
*psz = NULL;
if (cch == 0)
return NOERROR;
PreWideCharToUtf8(bstr, cch, (UINT *)pcbUTF8, &bSimpleConversion);
*psz = New char [*pcbUTF8 + 1];
if (!*psz)
return E_OUTOFMEMORY;
WideCharToUtf8(bstr, cch, (BYTE *)*psz, bSimpleConversion);
(*psz)[*pcbUTF8] = 0;
if (pbSetUtf8Charset)
{
*pbSetUtf8Charset = !bSimpleConversion;
}
return NOERROR;
}
/**
* Scans buffer and translates Unicode characters into UTF8 characters
*/
static
void
PreWideCharToUtf8(
WCHAR * buffer,
UINT cch,
UINT * cb,
bool * bSimpleConversion)
{
UINT count = 0;
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
count += 4;
surrogate = false;
buffer++;
continue;
}
else // Then dw1 must be a three byte character
{
count += 3;
}
surrogate = false;
}
if (dw < 0x80) // one byte, 0xxxxxxx
{
count++;
}
else if (dw < 0x800) // two WORDS, 110xxxxx 10xxxxxx
{
count += 2;
}
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
{
count += 3;
}
buffer++;
}
*cb = count;
*bSimpleConversion = (cch == count);
}
/**
* Scans buffer and translates Unicode characters into UTF8 characters
*/
static
void
WideCharToUtf8(
WCHAR * buffer,
UINT cch,
BYTE * bytebuffer,
bool bSimpleConversion)
{
DWORD dw1 = 0;
bool surrogate = false;
INET_ASSERT(bytebuffer != NULL);
if (bSimpleConversion)
{
for (UINT i = cch; i > 0; i--)
{
DWORD dw = *buffer;
*bytebuffer++ = (byte)dw;
buffer++;
}
}
else
{
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
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
{
*bytebuffer++ = (byte)(( dw1 >> 12) | 0xE0);
*bytebuffer++ = (byte)((( dw1 >> 6) & 0x3F) | 0x80);
*bytebuffer++ = (byte)(( dw1 & 0x3F) | 0x80);
}
surrogate = false;
}
if (dw < 0x80) // one byte, 0xxxxxxx
{
*bytebuffer++ = (byte)dw;
}
else if ( dw < 0x800) // two WORDS, 110xxxxx 10xxxxxx
{
*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
{
*bytebuffer++ = (byte)(( dw >> 12) | 0xE0);
*bytebuffer++ = (byte)((( dw >> 6) & 0x3F) | 0x80);
*bytebuffer++ = (byte)(( dw & 0x3F) | 0x80);
}
buffer++;
}
}
}
/*
* AsciiToBSTR
*
* Purpose:
* Convert an ascii string to a BSTR
*
* only **STRLEN** to be passed to AsciiToBSTR (not including terminating NULL, if any)
*
*/
static
HRESULT
AsciiToBSTR(BSTR * pbstr, char * sz, int cch)
{
int cwch;
INET_ASSERT (cch != -1);
// Determine how big the ascii string will be
cwch = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, cch,
NULL, 0);
*pbstr = DL(SysAllocStringLen)(NULL, cwch);
if (!*pbstr)
return E_OUTOFMEMORY;
cch = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, cch,
*pbstr, cwch);
return NOERROR;
}
/*
* GetBSTRFromVariant
*
* Purpose:
* Convert a VARIANT to a BSTR
*
* If VariantChangeType raises an exception, then an E_INVALIDARG
* error is returned.
*/
static HRESULT GetBSTRFromVariant(VARIANT varVariant, BSTR * pBstr)
{
VARIANT varTemp;
HRESULT hr = NOERROR;
*pBstr = NULL;
if (V_VT(&varVariant) != VT_EMPTY && V_VT(&varVariant) != VT_NULL &&
V_VT(&varVariant) != VT_ERROR)
{
DL(VariantInit)(&varTemp);
__try
{
hr = DL(VariantChangeType)(
&varTemp,
&varVariant,
0,
VT_BSTR);
if (SUCCEEDED(hr))
{
*pBstr = V_BSTR(&varTemp); // take over ownership of BSTR
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
hr = E_INVALIDARG;
}
}
// DON'T clear the variant because we stole the BSTR
//DL(VariantClear)(&varTemp);
return hr;
}
/*
* 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;
DL(VariantInit)(&varTemp);
hr = DL(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;
DL(VariantInit)(&varTemp);
hr = DL(VariantChangeType)(
&varTemp,
&varVariant,
0,
VT_UI4);
if (FAILED(hr))
goto Cleanup;
dwResult = V_UI4(&varTemp);
}
hr = NOERROR;
Cleanup:
return dwResult;
}
/*
* GetLongFromVariant
*
* 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;
DL(VariantInit)(&varTemp);
hr = DL(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 = DL(SafeArrayCreateVector)(VT_UI1, 0, cElems);
if (!psa)
{
hr = E_OUTOFMEMORY;
goto Cleanup;
}
hr = DL(SafeArrayAccessData)(psa, (void **)&pB);
if (FAILED(hr))
goto Error;
memcpy(pB, pData, cElems);
DL(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)
DL(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_ZERO_MEMORY(sizeof(IUnknown*)* _DEFAULT_VECTORLENGTH);
if (pp == NULL)
return 0;
*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_ZERO(_ppUnk, sizeof(IUnknown*)*nAlloc);
if (pp == NULL)
return 0;
_ppUnk = pp;
_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();
}
}
}
}
void STDMETHODCALLTYPE
CSinkArray::OnError(long ErrorNumber, BSTR ErrorDescription)
{
for (IUnknown ** pp = begin(); pp < end(); pp++)
{
if (*pp != NULL)
{
IWinHttpRequestEvents * pSink;
pSink = static_cast<IWinHttpRequestEvents *>(*pp);
if (((*(DWORD_PTR **)pSink)[6]) != NULL)
{
pSink->OnError(ErrorNumber, ErrorDescription);
}
}
}
}
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_ERROR)
{
CWinHttpRequestEventsMarshaller * pMarshaller;
CSinkArray * pSinkArray = NULL;
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)
{
DL(SysFreeString)(bstrContentType);
}
}
break;
case WHREM_MSG_ON_RESPONSE_DATA_AVAILABLE:
{
SAFEARRAY * psaData = (SAFEARRAY *) wParam;
if (bOkToFireEvents)
{
pSinkArray->OnResponseDataAvailable(&psaData);
}
if (psaData)
{
DL(SafeArrayDestroy)(psaData);
}
}
break;
case WHREM_MSG_ON_RESPONSE_FINISHED:
if (bOkToFireEvents)
{
pSinkArray->OnResponseFinished();
}
break;
case WHREM_MSG_ON_ERROR:
{
BSTR bstrErrorDescription = (BSTR) lParam;
if (bOkToFireEvents)
{
pSinkArray->OnError((long) wParam, bstrErrorDescription);
}
if (bstrErrorDescription)
{
DL(SysFreeString)(bstrErrorDescription);
}
}
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 = DL(SysAllocString)(bstrContentType);
PostMessage(_hWnd, WHREM_MSG_ON_RESPONSE_START,
(WPARAM) Status,
(LPARAM) bstrContentTypeCopy);
// Note: ownership of bstrContentTypeCopy 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(DL(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();
}
}
void STDMETHODCALLTYPE
CWinHttpRequestEventsMarshaller::OnError(long ErrorNumber, BSTR ErrorDescription)
{
if (_cs.Lock())
{
if (OkToFireEvents())
{
BSTR bstrErrorDescriptionCopy;
bstrErrorDescriptionCopy = DL(SysAllocString)(ErrorDescription);
PostMessage(_hWnd, WHREM_MSG_ON_ERROR,
(WPARAM) ErrorNumber,
(LPARAM) bstrErrorDescriptionCopy);
// Note: ownership of bstrErrorDescriptionCopy is transferred to
// the message window, so the string is not freed here.
}
_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;
}
}
if (g_pMimeInfoCache)
{
delete g_pMimeInfoCache;
g_pMimeInfoCache = NULL;
}
}
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;
}
static
HRESULT
SecureFailureFromStatus(DWORD dwFlags)
{
DWORD error;
if (dwFlags & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED)
{
error = ERROR_WINHTTP_SECURE_CERT_REV_FAILED;
}
else if (dwFlags & WINHTTP_CALLBACK_STATUS_FLAG_CERT_WRONG_USAGE)
{
error = ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE;
}
else if (dwFlags & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT)
{
error = ERROR_WINHTTP_SECURE_INVALID_CERT;
}
else if (dwFlags & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED)
{
error = ERROR_WINHTTP_SECURE_CERT_REVOKED;
}
else if (dwFlags & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA)
{
error = ERROR_WINHTTP_SECURE_INVALID_CA;
}
else if (dwFlags & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID)
{
error = ERROR_WINHTTP_SECURE_CERT_CN_INVALID;
}
else if (dwFlags & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID)
{
error = ERROR_WINHTTP_SECURE_CERT_DATE_INVALID;
}
else if (dwFlags & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR)
{
error = ERROR_WINHTTP_SECURE_CHANNEL_ERROR;
}
else
{
error = ERROR_WINHTTP_SECURE_FAILURE;
}
return HRESULT_FROM_WIN32(error);
}