2025-11-28 00:35:46 +09:00

954 lines
20 KiB
C++

//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
//
#include <windows.h>
#include <wincrypt.h>
#include <cryptuiapi.h>
#include "XMLHttpRequest3Callback.h"
CXMLHttpRequest3Callback::CXMLHttpRequest3Callback()
{
m_hr = S_OK;
m_dwStatus = 0;
m_hComplete = NULL;
m_fRetry = FALSE;
m_dwCertIgnoreFlags = 0;
m_cIssuerList = 0;
m_rgpwszIssuerList = NULL;
}
CXMLHttpRequest3Callback::~CXMLHttpRequest3Callback()
{
if (m_hComplete)
{
CloseHandle(m_hComplete);
m_hComplete = NULL;
}
if (m_rgpwszIssuerList)
{
FreeIssuerList(m_cIssuerList, m_rgpwszIssuerList);
m_cIssuerList = 0;
m_rgpwszIssuerList = NULL;
}
}
STDMETHODIMP
CXMLHttpRequest3Callback::RuntimeClassInitialize()
/*++
Routine Description:
Initalize the call back instance.
Arguments:
None.
Return Value:
HRESULT.
--*/
{
HRESULT hr = S_OK;
HANDLE hEvent = NULL;
//
// Callers needing to receive completion or status events on a STA or UI
// thread must use a mechanism that will not block the threads window message
// pump. One example is by posting a window message to the STA or UI thread
// window handle.
//
hEvent = CreateEventEx(NULL, NULL, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);
if (hEvent == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
m_hComplete = hEvent;
hEvent = NULL;
Exit:
if (hEvent)
{
CloseHandle(hEvent);
hEvent = NULL;
}
return hr;
}
STDMETHODIMP
CXMLHttpRequest3Callback::OnRedirect(
__RPC__in_opt IXMLHTTPRequest2 *pXHR,
__RPC__in_string const WCHAR *pwszRedirectUrl
)
/*++
Routine Description:
This funciton is called when the HTTP request is being redirected to a new URL.
This callback function must not throw any exceptions.
Arguments:
pXHR - The interface pointer of IXMLHTTPRequest2 object.
pwszRedirectUrl - The new URL for the HTTP request.
Return Value:
HRESULT.
--*/
{
UNREFERENCED_PARAMETER(pXHR);
UNREFERENCED_PARAMETER(pwszRedirectUrl);
return S_OK;
}
STDMETHODIMP
CXMLHttpRequest3Callback::OnHeadersAvailable(
__RPC__in_opt IXMLHTTPRequest2 *pXHR,
DWORD dwStatus,
__RPC__in_string const WCHAR *pwszStatus
)
/*++
Routine Description:
Sends a request using the Request Handle specified and implements
proxy failover if supported.
This callback function must not throw any exceptions.
Arguments:
pXHR - The interface pointer of IXMLHTTPRequest2 object.
dwStatus - The value of HTTP status code.
pwszStatus - The description text of HTTP status code.
Return Value:
HRESULT.
--*/
{
UNREFERENCED_PARAMETER(pwszStatus);
HRESULT hr = S_OK;
PWSTR pwszHeaders = NULL;
PWSTR pwszSingleHeader = NULL;
if (pXHR == NULL)
{
hr = E_INVALIDARG;
goto Exit;
}
//
// Demonstrate how to get all response headers.
//
hr = pXHR->GetAllResponseHeaders(&pwszHeaders);
if (FAILED(hr))
{
goto Exit;
}
//
// Demonstrate how to get a specific response header.
//
hr = pXHR->GetResponseHeader(L"server", &pwszSingleHeader);
if (FAILED(hr) &&
hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
{
goto Exit;
}
hr = S_OK;
Exit:
if (pwszHeaders != NULL)
{
CoTaskMemFree(pwszHeaders);
pwszHeaders = NULL;
}
if (pwszSingleHeader != NULL)
{
CoTaskMemFree(pwszSingleHeader);
pwszSingleHeader = NULL;
}
m_dwStatus = dwStatus;
return hr;
}
STDMETHODIMP
CXMLHttpRequest3Callback::ReadFromStream(
_In_opt_ ISequentialStream *pStream
)
/*++
Routine Description:
Demonstrate how to read from the HTTP response stream.
Arguments:
pStream - the data stream read form the http response.
Return Value:
HRESULT.
--*/
{
HRESULT hr = S_OK;
BYTE buffer[MAX_BUFFER_LENGTH];
DWORD cbRead = 0;
if (pStream == NULL)
{
hr = E_INVALIDARG;
goto Exit;
}
for(;;)
{
hr = pStream->Read(buffer, MAX_BUFFER_LENGTH - 1, &cbRead);
if (FAILED(hr) ||
cbRead == 0)
{
goto Exit;
}
buffer[cbRead] = 0;
}
Exit:
return hr;
}
STDMETHODIMP
CXMLHttpRequest3Callback::OnDataAvailable(
__RPC__in_opt IXMLHTTPRequest2 *pXHR,
__RPC__in_opt ISequentialStream *pResponseStream
)
/*++
Routine Description:
This function is called when a portion of the entity body has been received.
The application can begin processing the data at this point,
or wait until the whole response is complete.
This callback function must not throw any exceptions.
Arguments:
pXHR - The interface pointer of IXMLHTTPRequest2 object.
pResponseStream - a pointer to the input stream.
Return Value:
HRESULT.
--*/
{
UNREFERENCED_PARAMETER(pXHR);
//
// This sample function is processing data as it is received by reading from
// the response stream. If real-time chunk-by-chunk processing (such as for
// streaming applications) is not needed, then the entire response is available
// from the OnResponseReceived callback. Receiving will be suspended until
// this callback function returns and this callback will be invoked multiple
// times during a request. This callback function must not block and
// should not perform costly operations such as UI updates.
//
return ReadFromStream(pResponseStream);
}
STDMETHODIMP
CXMLHttpRequest3Callback::OnResponseReceived(
__RPC__in_opt IXMLHTTPRequest2 *pXHR,
__RPC__in_opt ISequentialStream *pResponseStream
)
/*++
Routine Description:
Called when the entire entity body has been received.
At this point the application can begin processing the data by calling Read
on the response ISequentialStream or store a reference to the ISequentialStream
for later processing.
This callback function must not throw any exceptions.
Arguments:
pXHR - The interface pointer of IXMLHTTPRequest2 object.
pResponseStream - a pointer to the input stream.
Return Value:
HRESULT.
--*/
{
UNREFERENCED_PARAMETER(pXHR);
UNREFERENCED_PARAMETER(pResponseStream);
m_hr = S_OK;
SetEvent(m_hComplete);
return m_hr;
}
STDMETHODIMP
CXMLHttpRequest3Callback::OnError(
__RPC__in_opt IXMLHTTPRequest2 *pXHR,
HRESULT hrError
)
/*++
Routine Description:
Called when an error occurs during the HTTP request. The error is indicated in hrError.
This callback function must not throw any exceptions.
Arguments:
pXHR - The interface pointer of IXMLHTTPRequest2 object.
hrError - The errocode for the httprequest.
Return Value:
HRESULT.
--*/
{
UNREFERENCED_PARAMETER(pXHR);
m_hr = hrError;
SetEvent(m_hComplete);
return S_OK;
}
STDMETHODIMP
CXMLHttpRequest3Callback::OnServerCertificateReceived(
__RPC__in_opt IXMLHTTPRequest3 *pXHR,
DWORD dwCertErrors,
DWORD cServerCertChain,
__RPC__in_ecount_full_opt(cServerCertChain) const XHR_CERT *rgServerCertChain
)
/*++
Routine Description:
Called when server certificate errors or the server certificate chain is avaiable.
This callback function must not throw any exceptions.
Arguments:
pXHR - The interface pointer of IXMLHTTPRequest3 object.
dwCertErrors - A bitmask of server certificate errors (XHR_CERT_ERROR_*).
cServerCertChain - The number of certificates in the server certificate chain.
rgServerCertChain - The server certificate chain as an array of encoded certificates.
Return Value:
HRESULT.
--*/
{
UNREFERENCED_PARAMETER(pXHR);
PCCERT_CONTEXT pCertContext = NULL;
BYTE *pbCertEncoded = NULL;
DWORD cbCertEncoded = 0;
DWORD i = 0;
if (cServerCertChain == 0 || rgServerCertChain == NULL)
{
goto Exit;
}
//
// Each element of the chain is an encoded certificate.
//
for (i = 0; i < cServerCertChain; i++)
{
pbCertEncoded = rgServerCertChain[i].pbCert;
cbCertEncoded = rgServerCertChain[i].cbCert;
pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
pbCertEncoded,
cbCertEncoded);
//
// Use the certificate context here.
//
if (pCertContext)
{
CertFreeCertificateContext(pCertContext);
pCertContext = NULL;
}
}
//
// On the retry connection, ignore each specific cert error.
//
if ((dwCertErrors & XHR_CERT_ERROR_REVOCATION_FAILED) != 0)
{
m_dwCertIgnoreFlags |= XHR_CERT_IGNORE_REVOCATION_FAILED;
m_fRetry = TRUE;
}
if ((dwCertErrors & XHR_CERT_ERROR_UNKNOWN_CA) != 0)
{
m_dwCertIgnoreFlags |= XHR_CERT_IGNORE_UNKNOWN_CA;
m_fRetry = TRUE;
}
if ((dwCertErrors & XHR_CERT_ERROR_CERT_CN_INVALID) != 0)
{
m_dwCertIgnoreFlags |= XHR_CERT_IGNORE_CERT_CN_INVALID;
m_fRetry = TRUE;
}
if ((dwCertErrors & XHR_CERT_ERROR_CERT_DATE_INVALID) != 0)
{
m_dwCertIgnoreFlags |= XHR_CERT_IGNORE_CERT_DATE_INVALID;
m_fRetry = TRUE;
}
Exit:
return S_OK;
}
STDMETHODIMP
CXMLHttpRequest3Callback::OnClientCertificateRequested(
__RPC__in_opt IXMLHTTPRequest3 *pXHR,
DWORD cIssuerList,
__RPC__in_ecount_full_opt(cIssuerList) const WCHAR **rgpwszIssuerList
)
/*++
Routine Description:
Called when the server requests client certificates.
This callback function must not throw any exceptions.
Arguments:
pXHR - The interface pointer of IXMLHTTPRequest3 object.
cIssuerList - The number of issuer strings in the issuer list.
rgpwszIssuerList - The array of issuer strings for filtering client certificates.
Return Value:
HRESULT.
--*/
{
UNREFERENCED_PARAMETER(pXHR);
HRESULT hr = S_OK;
if (cIssuerList == 0 || rgpwszIssuerList == NULL)
{
goto Exit;
}
//
// On the retry connection, use issuer list to filter client certificates.
//
hr = DuplicateIssuerList(cIssuerList, rgpwszIssuerList, &m_rgpwszIssuerList);
if (FAILED(hr))
{
goto Exit;
}
m_cIssuerList = cIssuerList;
m_fRetry = TRUE;
Exit:
return hr;
}
STDMETHODIMP
CXMLHttpRequest3Callback::DuplicateIssuerList(
_In_ DWORD cIssuerList,
_In_reads_(cIssuerList) const WCHAR **rgpwszIssuerList,
_Out_ const WCHAR ***prgpwszDuplicateIssuerList
)
/*++
Routine Description:
Create a duplicate of the issuer list string array.
Arguments:
cIssuerList - The number of issuers in the issuer list.
rgpwszIssuerList - The source issuer list.
prgpwszDuplicateIssuerList - The newly created duplicate issuer list.
Return Value:
HRESULT.
--*/
{
HRESULT hr = S_OK;
DWORD i = 0;
DWORD cchIssuer = 0;
WCHAR *pwszIssuer = NULL;
const WCHAR **rgpwszDuplicateIssuerList = NULL;
if (cIssuerList == 0 || rgpwszIssuerList == NULL)
{
hr = E_INVALIDARG;
goto Exit;
}
rgpwszDuplicateIssuerList = (const WCHAR**) new WCHAR*[cIssuerList];
if (rgpwszDuplicateIssuerList == NULL)
{
hr = E_OUTOFMEMORY;
goto Exit;
}
memset(rgpwszDuplicateIssuerList, 0, sizeof(WCHAR*) * cIssuerList);
for (i = 0; i < cIssuerList && rgpwszIssuerList[i] != NULL; i++)
{
cchIssuer = (DWORD) wcslen(rgpwszIssuerList[i]);
pwszIssuer = new WCHAR[cchIssuer + 1];
if (pwszIssuer == NULL)
{
hr = E_OUTOFMEMORY;
goto Exit;
}
wcscpy_s(pwszIssuer, cchIssuer + 1, rgpwszIssuerList[i]);
(rgpwszDuplicateIssuerList)[i] = pwszIssuer;
}
Exit:
if (FAILED(hr))
{
FreeIssuerList(i, rgpwszDuplicateIssuerList);
rgpwszDuplicateIssuerList = NULL;
}
*prgpwszDuplicateIssuerList = rgpwszDuplicateIssuerList;
return hr;
}
STDMETHODIMP
CXMLHttpRequest3Callback::FreeIssuerList(
_In_ DWORD cIssuerList,
_Frees_ptr_ const WCHAR **rgpwszIssuerList
)
/*++
Routine Description:
Free a duplicated issuer list.
Arguments:
cIssuerList - The count of the issuers in the duplicate issuer list.
rgpwszIssuerList - The duplicated issuer list to be freed.
Return Value:
HRESULT.
--*/
{
DWORD i = 0;
if (cIssuerList == 0 || rgpwszIssuerList == NULL)
{
goto Exit;
}
for (i = 0; i < cIssuerList; i++)
{
delete[] rgpwszIssuerList[i];
rgpwszIssuerList[i] = NULL;
}
delete[] rgpwszIssuerList;
Exit:
return S_OK;
}
STDMETHODIMP
CXMLHttpRequest3Callback::CompareIssuer(
_In_ VOID *pvCertContext,
_In_ DWORD cIssuerList,
_In_reads_(cIssuerList) const WCHAR **rgpwszIssuerList,
_Out_ BOOL *pfMatch
)
/*++
Routine Description:
Check to see if the issuer of the certificate context is among any of the
issuers in the issuer list.
Arguments:
pvCertContext - A pointer to a certificate context.
cIssuerList - The count of issuers in the issuer list.
rgpwszIssuerList - The issuer list.
pfMatch - A pointer to whether a match was found.
Return Value:
HRESULT.
--*/
{
DWORD i = 0;
WCHAR *pwszName = NULL;
DWORD cchName = 0;
PCCERT_CONTEXT pCertContext = (PCCERT_CONTEXT) pvCertContext;
*pfMatch = FALSE;
cchName = CertNameToStr(X509_ASN_ENCODING,
&pCertContext->pCertInfo->Issuer,
CERT_SIMPLE_NAME_STR | CERT_NAME_STR_CRLF_FLAG | CERT_NAME_STR_NO_PLUS_FLAG,
NULL,
0);
pwszName = new WCHAR[cchName];
if (pwszName == NULL)
{
goto Exit;
}
CertNameToStr(X509_ASN_ENCODING,
&pCertContext->pCertInfo->Issuer,
CERT_SIMPLE_NAME_STR | CERT_NAME_STR_CRLF_FLAG | CERT_NAME_STR_NO_PLUS_FLAG,
pwszName,
cchName);
for (i = 0; i < cIssuerList; i++)
{
if (wcsncmp(pwszName, rgpwszIssuerList[i], cchName) == 0)
{
*pfMatch = TRUE;
break;
}
}
Exit:
if (pwszName != NULL)
{
delete[] pwszName;
}
return S_OK;
}
STDMETHODIMP
CXMLHttpRequest3Callback::SelectCert(
_In_ DWORD cIssuerList,
_Frees_ptr_ const WCHAR **rgpwszIssuerList,
_Inout_ DWORD *pcbCertHash,
_Inout_updates_(*pcbCertHash) BYTE *pbCertHash
)
/*++
Routine Description:
Display a certificate picker UI for the user to select a client certificate
to use for the current retry connection using the issuer list as a display
filter, and return the thumbprint of the selected certificate.
Arguments:
pcIssuerList - The count of issuers in the issuer list.
rgpwszIssuerList - The issuer list used to filter the certificates displayed.
Return Value:
HRESULT.
--*/
{
HRESULT hr = S_OK;
PCCERT_CONTEXT pCertContext = NULL;
PCCERT_CONTEXT pSelectedContext = NULL;
HCERTSTORE hMyCertStore = NULL;
HCERTSTORE hMemStore = NULL;
BOOL fMatch = FALSE;
hMyCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_W,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
NULL,
CERT_SYSTEM_STORE_CURRENT_USER,
L"MY");
if (hMyCertStore == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
hMemStore = CertOpenStore(CERT_STORE_PROV_MEMORY,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
NULL,
CERT_STORE_CREATE_NEW_FLAG,
NULL);
if (hMemStore == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
for (pCertContext = CertEnumCertificatesInStore(hMyCertStore, pCertContext);
pCertContext != NULL;
pCertContext = CertEnumCertificatesInStore(hMyCertStore, pCertContext))
{
hr = CompareIssuer((VOID*) pCertContext, cIssuerList, rgpwszIssuerList, &fMatch);
if (FAILED(hr) || !fMatch)
{
continue;
}
if (!CertAddCertificateContextToStore(hMemStore,
pCertContext,
CERT_STORE_ADD_ALWAYS,
NULL))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
}
pSelectedContext = CryptUIDlgSelectCertificateFromStore(hMemStore,
NULL,
NULL,
NULL,
0,
0,
NULL);
if (pSelectedContext == NULL)
{
hr = E_POINTER;
goto Exit;
}
if (!CertGetCertificateContextProperty(pSelectedContext,
CERT_HASH_PROP_ID,
pbCertHash,
pcbCertHash))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
Exit:
if (pSelectedContext != NULL)
{
CertFreeCertificateContext(pSelectedContext);
}
if (pCertContext != NULL)
{
CertFreeCertificateContext(pCertContext);
}
if (hMemStore != NULL)
{
CertCloseStore(hMemStore, 0);
}
if (hMyCertStore != NULL)
{
CertCloseStore(hMyCertStore, 0);
}
FreeIssuerList(cIssuerList, rgpwszIssuerList);
return hr;
}
STDMETHODIMP
CXMLHttpRequest3Callback::GetCertResult(
_Out_ BOOL *pfRetry,
_Out_ DWORD *pdwCertIgnoreFlags,
_Out_ DWORD *pcIssuerList,
_Out_ const WCHAR ***prgpwszIssuerList
)
/*++
Routine Description:
Retrieve certificate state for the retry connection.
Arguments:
pfRetry - A pointer to the retry connection state.
pdwCertIgnoreFlags - A pointer to the certificate ignore flags.
pcIssuerList - A pointer to the count of issuers in the issuer list.
prgpwszIssuerList - A pointer to the array of issuer strings.
Return Value:
HRESULT.
--*/
{
HRESULT hr = S_OK;
*pfRetry = m_fRetry;
*pdwCertIgnoreFlags = m_dwCertIgnoreFlags;
if (m_cIssuerList != 0 && m_rgpwszIssuerList != NULL)
{
*pcIssuerList = m_cIssuerList;
hr = DuplicateIssuerList(m_cIssuerList, m_rgpwszIssuerList, prgpwszIssuerList);
}
else
{
*pcIssuerList = 0;
*prgpwszIssuerList = NULL;
}
return hr;
}
STDMETHODIMP
CXMLHttpRequest3Callback::WaitForComplete(
_Out_ PDWORD pdwStatus
)
/*++
Routine Description:
Waiting for completion of the request. Once it's done, get the execution
result of final call backs, and http status code if it's available.
N.B. Callers needing to receive completion or status events on a STA or UI
thread must use a mechanism that will not block the threads window message
pump. One example is by posting a window message to the STA or UI thread
window handle.
Arguments:
pdwStatus - Supplies a pointer to access the status code.
Return Value:
HRESULT.
--*/
{
DWORD dwError = ERROR_SUCCESS;
HRESULT hr = S_OK;
if (pdwStatus == NULL)
{
hr = E_INVALIDARG;
goto Exit;
}
dwError = WaitForSingleObject(m_hComplete, INFINITE);
if (dwError == WAIT_FAILED)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
else if (dwError != WAIT_OBJECT_0)
{
hr = E_ABORT;
goto Exit;
}
if (FAILED(m_hr))
{
hr = m_hr;
goto Exit;
}
hr = S_OK;
*pdwStatus = m_dwStatus;
Exit:
return hr;
}