windows-nt/Source/XPSP1/NT/net/upnp/host/udhisapi/validatesoap.cpp
2020-09-26 16:20:57 +08:00

913 lines
26 KiB
C++

//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 2000.
//
// File: V A L I D A T E S O A P . C P P
//
// Contents: Implementation of SOAP request validation.
//
// Notes:
//
// Author: spather 2000/11/8
//
//----------------------------------------------------------------------------
#include <pch.h>
#pragma hdrstop
#include <httpext.h>
#include "hostp.h"
#include "ncxml.h"
#include "ncbase.h"
#include "ValidateSOAP.h"
const WCHAR CSZ_SOAP_NAMESPACE_URI[] =
L"http://schemas.xmlsoap.org/soap/envelope/";
const WCHAR CSZ_SOAP_ENCODING_STYLE_URI[] =
L"http://schemas.xmlsoap.org/soap/encoding/";
HRESULT
HrValidateArguments(
IN IXMLDOMNode * pxdnAction,
IN IXMLDOMNodeList * pxdnlArgs,
IN IUPnPServiceDescriptionInfo * pServiceDescInfo)
{
HRESULT hr = S_OK;
BSTR bstrActionName = NULL;
hr = pxdnAction->get_baseName(&bstrActionName);
if (SUCCEEDED(hr))
{
BOOL fIsQueryStateVariable = FALSE;
DWORD cInArgs = 0;
BSTR * rgbstrNames = NULL;
BSTR * rgbstrTypes = NULL;
Assert(bstrActionName);
if (0 == lstrcmpW(bstrActionName, L"QueryStateVariable"))
{
fIsQueryStateVariable = TRUE;
cInArgs = 1;
}
else
{
fIsQueryStateVariable = FALSE;
hr = pServiceDescInfo->GetInputArgumentNamesAndTypes(bstrActionName,
&cInArgs,
&rgbstrNames,
&rgbstrTypes);
}
if (SUCCEEDED(hr))
{
long listLength = 0;
hr = pxdnlArgs->get_length(&listLength);
if (SUCCEEDED(hr))
{
if ((DWORD)listLength == cInArgs)
{
if (cInArgs > 0)
{
for (DWORD i = 0; i < cInArgs; i++)
{
IXMLDOMNode * pxdnArg = NULL;
hr = pxdnlArgs->get_item(i, &pxdnArg);
if (SUCCEEDED(hr))
{
BSTR bstrArgName = NULL;
// Check that the name matches.
Assert(pxdnArg);
hr = pxdnArg->get_baseName(&bstrArgName);
if (SUCCEEDED(hr))
{
Assert(bstrArgName);
if (fIsQueryStateVariable)
{
if (0 == lstrcmpiW(bstrArgName,
L"varName"))
{
hr = S_OK;
}
else
{
hr = E_FAIL;
TraceError("HrValidateArguments(): "
"Invalid argument name",
hr);
}
}
else
{
if (0 == lstrcmpiW(bstrArgName,
rgbstrNames[i]))
{
hr = S_OK;
}
else
{
hr = E_FAIL;
TraceError("HrValidateArguments(): "
"Invalid argument name",
hr);
}
}
SysFreeString(bstrArgName);
}
pxdnArg->Release();
}
else
{
TraceError("HrValidateArguments(): "
"Failed to get item",
hr);
}
}
}
}
else
{
hr = E_FAIL;
TraceError("HrValidateArguments(): "
"Wrong number of input arguments in request",
hr);
}
}
else
{
TraceError("HrValidateArguments(): "
"Failed to get list length",
hr);
}
if (FALSE == fIsQueryStateVariable)
{
if (cInArgs > 0)
{
for (DWORD i = 0; i < cInArgs; i++)
{
SysFreeString(rgbstrNames[i]);
rgbstrNames[i] = NULL;
SysFreeString(rgbstrTypes[i]);
rgbstrTypes[i] = NULL;
}
CoTaskMemFree(rgbstrNames);
rgbstrNames = NULL;
CoTaskMemFree(rgbstrTypes);
rgbstrTypes = NULL;
}
}
}
SysFreeString(bstrActionName);
}
else
{
TraceError("HrValidateArguments(): "
"Failed to get action name",
hr);
}
TraceError("HrValidateArguments(): "
"Exiting",
hr);
return hr;
}
HRESULT
HrValidateActionName(
IN IXMLDOMNode * pxdnAction,
IN LPWSTR szSOAPActionHeader)
{
HRESULT hr = S_OK;
LPWSTR szStrippedHeader = NULL;
DWORD cchSOAPActionHeader = 0;
LPWSTR szActionName = NULL;
cchSOAPActionHeader = lstrlenW(szSOAPActionHeader);
// Due to bugs in some SOAP implementations (specifically, the URT, not us)
// the SOAPActionHeader may or may not have double-quotes around it. We'll
// just remove them here if they are there (they should be, according to
// the SOAP spec).
if ((L'"' == szSOAPActionHeader[0]) &&
(L'"' == szSOAPActionHeader[cchSOAPActionHeader-1]))
{
// Modify the string in place - insert a NULL where the close
// quote is, and start the string 1 character from the beginning.
// Doing it this way, rather than copying the string saves us 1
// memory allocation.
szSOAPActionHeader[cchSOAPActionHeader-1] = UNICODE_NULL;
szStrippedHeader = szSOAPActionHeader+1;
}
else
{
szStrippedHeader = szSOAPActionHeader;
}
// Check that the action name matches the SOAPAction header.
// SOAPAction header is of the form servicetype#actionName.
szActionName = wcsstr(szStrippedHeader, L"#");
if (szActionName)
{
BSTR bstrActionName = NULL;
szActionName++; // Advance to the character after the "#"
hr = pxdnAction->get_baseName(&bstrActionName);
if (S_OK == hr)
{
if (0 == lstrcmpiW(bstrActionName, szActionName))
{
hr = S_OK;
TraceTag(ttidValidate,
"HrValidateActionName(): "
"Action node name and SOAPAction header value match!");
}
else
{
hr = E_FAIL;
TraceError("HrValidateActionName(): "
"Action node name did not match SOAPAction header",
hr);
}
SysFreeString(bstrActionName);
}
else
{
TraceError("HrValidateActionName(): "
"Failed to get node name",
hr);
}
}
else
{
hr = E_FAIL;
TraceError("HrValidateActionName(): "
"SOAPActionHeader did not contain \"#\" character",
hr);
}
TraceError("HrValidateActionName(): "
"Exiting",
hr);
return hr;
}
HRESULT
HrValidateBody(
IN IXMLDOMNode * pxdnBody,
IN IUPnPServiceDescriptionInfo * pServiceDescInfo,
IN LPWSTR szSOAPActionHeader)
{
HRESULT hr = S_OK;
IXMLDOMNode * pxdnAction = NULL;
hr = pxdnBody->get_firstChild(&pxdnAction);
if (SUCCEEDED(hr))
{
if (pxdnAction)
{
hr = HrValidateActionName(pxdnAction, szSOAPActionHeader);
if (SUCCEEDED(hr))
{
IXMLDOMNodeList * pxdnlArgs = NULL;
hr = pxdnAction->get_childNodes(&pxdnlArgs);
if (SUCCEEDED(hr))
{
Assert(pxdnlArgs);
hr = HrValidateArguments(pxdnAction,
pxdnlArgs,
pServiceDescInfo);
pxdnlArgs->Release();
}
}
// Finally, if all the above succeeded, check that the
// action element does not have a sibling (there should
// only be one child of the body element).
if (SUCCEEDED(hr))
{
IXMLDOMNode * pxdnSibling = NULL;
hr = pxdnAction->get_nextSibling(&pxdnSibling);
if (S_FALSE == hr)
{
Assert(NULL == pxdnSibling);
hr = S_OK;
TraceTag(ttidValidate,
"HrValidateBody(): "
"Body is valid!");
}
else
{
if (SUCCEEDED(hr))
{
Assert(pxdnSibling);
pxdnSibling->Release();
hr = E_FAIL;
TraceError("HrValidateBody(): "
"Body element had more than one child",
hr);
}
else
{
TraceError("HrValidateBody(): "
"Failure trying to get next sibling",
hr);
}
}
}
pxdnAction->Release();
}
else
{
hr = E_FAIL;
TraceError("HrValidateBody(): "
"Body element was empty",
hr);
}
}
else
{
TraceError("HrValidateBody(): "
"Failed to get first child of body element",
hr);
}
TraceError("HrValidateBody(): "
"Exiting",
hr);
return hr;
}
HRESULT
HrValidateSOAPHeader(
IN IXMLDOMNode * pxdnHeader)
{
HRESULT hr = S_OK;
IXMLDOMNode * pxdnChild = NULL;
// A request may have headers as long as none of them have the
// "mustUnderstand" attribute set because we don't understand any
// headers.
hr = pxdnHeader->get_firstChild(&pxdnChild);
while (SUCCEEDED(hr) && pxdnChild)
{
IXMLDOMNode * pxdnNextSibling = NULL;
BSTR bstrMustUnderstand = NULL;
hr = HrGetTextValueFromAttribute(pxdnChild,
L"mustUnderstand",
&bstrMustUnderstand);
if (SUCCEEDED(hr))
{
if (NULL == bstrMustUnderstand)
{
hr = S_OK;
TraceTag(ttidValidate,
"HrValidateSOAPHeader(): "
"Found header without mustUnderstand attribute - ok!");
}
else
{
if (0 == lstrcmpW(bstrMustUnderstand, L"0"))
{
hr = S_OK;
TraceTag(ttidValidate,
"HrValidateSOAPHeader(): "
"Found header with mustUnderstand "
"attribute == 0 - ok!");
}
else if (0 == lstrcmpW(bstrMustUnderstand, L"1"))
{
hr = E_FAIL;
TraceError("HrValidateSOAPHeader(): "
"Found header with mustUnderstand attribute set",
hr);
}
else
{
hr = E_FAIL;
TraceError("HrValidateSOAPHeader(): "
"Found header with invalid "
"mustUnderstand attribute value",
hr);
}
SysFreeString(bstrMustUnderstand);
}
}
else
{
TraceError("HrValidateSOAPError(): "
"Failed to get mustUnderstand attribute value",
hr);
}
if (FAILED(hr))
{
pxdnChild->Release();
break;
}
hr = pxdnChild->get_nextSibling(&pxdnNextSibling);
pxdnChild->Release();
pxdnChild = pxdnNextSibling;
}
TraceError("HrValidateSOAPHeader(): "
"Exiting",
hr);
return hr;
}
HRESULT
HrValidateSOAPStructure(
IN IXMLDOMNode * pxdnReqEnvelope,
IN IUPnPServiceDescriptionInfo * pServiceDescInfo,
IN LPWSTR szSOAPActionHeader)
{
HRESULT hr = S_OK;
if (FIsThisTheNodeNameWithNamespace(pxdnReqEnvelope,
L"Envelope",
CSZ_SOAP_NAMESPACE_URI))
{
IXMLDOMNode * pxdnChild = NULL;
BOOL bFoundHeader = FALSE;
BOOL bFoundBody = FALSE;
hr = pxdnReqEnvelope->get_firstChild(&pxdnChild);
while (SUCCEEDED(hr) && pxdnChild)
{
IXMLDOMNode * pxdnNextSibling = NULL;
if (FIsThisTheNodeNameWithNamespace(pxdnChild,
L"Header",
CSZ_SOAP_NAMESPACE_URI))
{
if (FALSE == bFoundHeader)
{
bFoundHeader = TRUE;
hr = HrValidateSOAPHeader(pxdnChild);
}
else
{
hr = E_FAIL;
TraceError("HrValidateSOAPStructure(): "
"Duplicate header element found",
hr);
}
}
else if (FIsThisTheNodeNameWithNamespace(pxdnChild,
L"Body",
CSZ_SOAP_NAMESPACE_URI))
{
if (FALSE == bFoundBody)
{
bFoundBody = TRUE;
hr = HrValidateBody(pxdnChild,
pServiceDescInfo,
szSOAPActionHeader);
}
else
{
hr = E_FAIL;
TraceError("HrValidateSOAPStructure(): "
"Duplicate body element found",
hr);
}
}
else
{
hr = E_FAIL;
TraceError("HrValidateSOAPStructure(): "
"Unknown element found inside SOAP envelope",
hr);
}
if (FAILED(hr))
{
pxdnChild->Release();
break;
}
hr = pxdnChild->get_nextSibling(&pxdnNextSibling);
pxdnChild->Release();
pxdnChild = pxdnNextSibling;
}
if (SUCCEEDED(hr))
{
if (FALSE == bFoundBody)
{
hr = E_FAIL;
TraceError("HrValidateSOAPStrucutre(): "
"Request was missing body element",
hr);
}
else
{
hr = S_OK;
TraceTag(ttidValidate,
"HrValidateSOAPStructure(): "
"SOAP structure is valid!");
}
}
}
else
{
hr = E_FAIL;
TraceError("HrValidateSOAPStructure(): "
"Root element is not a SOAP envelope",
hr);
}
TraceError("HrValidateSOAPStructure(): "
"Exiting",
hr);
return hr;
}
HRESULT
HrGetSOAPActionHeader(
IN LPEXTENSION_CONTROL_BLOCK pecb,
OUT LPWSTR * pszSOAPActionHeader)
{
HRESULT hr = S_OK;
LPWSTR szSOAPActionHeader = NULL;
CHAR * szaBuffer = NULL;
DWORD cbBuffer = 0;
BOOL bReturn = FALSE;
// First we need to find the size of buffer we need to get all the HTTP
// headers.
bReturn = pecb->GetServerVariable(pecb->ConnID,
"ALL_RAW",
szaBuffer,
&cbBuffer);
if (FALSE == bReturn)
{
DWORD dwError = 0;
// Expect to be here - we passed in a null buffer and a zero size,
// in order to find what the real size should be.
dwError = GetLastError();
if (ERROR_INSUFFICIENT_BUFFER == dwError)
{
Assert(cbBuffer > 0);
szaBuffer = new CHAR[cbBuffer+1];
if (szaBuffer)
{
bReturn = pecb->GetServerVariable(pecb->ConnID,
"ALL_RAW",
szaBuffer,
&cbBuffer);
if (bReturn)
{
hr = S_OK;
TraceTag(ttidValidate,
"HrGetSOAPActionHeader(): "
"All HTTP Headers:\n%s", szaBuffer);
}
else
{
hr = HrFromLastWin32Error();
TraceError("HrGetSOAPActionHeader(): "
"Failed to get all HTTP headers",
hr);
}
}
else
{
hr = E_OUTOFMEMORY;
TraceError("HrGetSOAPActionHeader(): "
"Failed to allocate buffer for all HTTP headers",
hr);
}
}
else
{
hr = HrFromLastWin32Error();
TraceError("HrGetSOAPActionHeader(): "
"Failed to get buffer size for all HTTP headers",
hr);
}
}
else
{
// Should never be here.
Assert(FALSE);
}
if (SUCCEEDED(hr))
{
CHAR * szaSOAPActionStart = NULL;
// Convert the buffer to uppercase.
for (DWORD i = 0; i < cbBuffer; i++)
{
szaBuffer[i] = (CHAR) toupper(szaBuffer[i]);
}
// Now we need find the SOAPAction header and get it's value.
szaSOAPActionStart = strstr(szaBuffer, "SOAPACTION:");
if (szaSOAPActionStart)
{
// Count the number of characters in the value of the header.
DWORD cbSOAPActionHeader = 0;
CHAR * szaSOAPActionValueStart = NULL;
CHAR * szaTemp = NULL;
szaSOAPActionValueStart = szaSOAPActionStart +
strlen("SOAPACTION:");
while (' ' == *szaSOAPActionValueStart || '\t' == *szaSOAPActionValueStart)
{
szaSOAPActionValueStart++;
}
szaTemp = szaSOAPActionValueStart;
while (('\0' != *szaTemp) &&
('\r' != *szaTemp) &&
('\n' != *szaTemp))
{
szaTemp++;
cbSOAPActionHeader++;
}
if (cbSOAPActionHeader > 0)
{
CHAR * szaSOAPActionHeader = NULL;
szaSOAPActionHeader = new CHAR[cbSOAPActionHeader + 1];
if (szaSOAPActionHeader)
{
strncpy(szaSOAPActionHeader,
szaSOAPActionValueStart,
cbSOAPActionHeader);
szaSOAPActionHeader[cbSOAPActionHeader] = '\0';
szSOAPActionHeader = WszFromSz(szaSOAPActionHeader);
if (szSOAPActionHeader)
{
hr = S_OK;
TraceTag(ttidUDHISAPI,
"HrGetSOAPActionHeader(): "
"SOAPAction header value is %S",
szSOAPActionHeader);
}
else
{
hr = E_OUTOFMEMORY;
TraceError("HrGetSOAPActionHeader(): "
"Failed to allocate memory for "
"wide character SOAPAction header",
hr);
}
delete [] szaSOAPActionHeader;
}
else
{
hr = E_OUTOFMEMORY;
TraceError("HrGetSOAPActionHeader(): "
"Failed to allocate memory for SOAPAction header",
hr);
}
}
else
{
hr = E_FAIL;
TraceError("HrGetSOAPActionHeader(): "
"SOAPAction header had no value",
hr);
}
}
else
{
hr = UPNP_E_MISSING_SOAP_ACTION;
TraceError("HrGetSOAPActionHeader(): "
"Could not find SOAPAction header",
hr);
}
}
// Cleanup.
if (szaBuffer)
{
delete [] szaBuffer;
szaBuffer = NULL;
}
// Copy the string to the out parameter if succeeded, otherwise
// clean it up.
if (SUCCEEDED(hr))
{
*pszSOAPActionHeader = szSOAPActionHeader;
}
else
{
// Cleanup.
if (szSOAPActionHeader)
{
delete [] szSOAPActionHeader;
szSOAPActionHeader = NULL;
}
}
TraceError("HrGetSOAPActionHeader(): "
"Exiting",
hr);
return hr;
}
HRESULT
HrValidateContentType(
IN LPEXTENSION_CONTROL_BLOCK pecb)
{
HRESULT hr = S_OK;
BOOL bReturn = FALSE;
const DWORD cdwBufSize = 512;
CHAR szaBuffer[cdwBufSize];
DWORD cbBuffer = cdwBufSize;
LPCSTR cszaExpectedContentType = "text/xml";
bReturn = pecb->GetServerVariable(pecb->ConnID,
"CONTENT_TYPE",
szaBuffer,
&cbBuffer);
if (bReturn)
{
Assert(cbBuffer <= cdwBufSize);
if (0 == strncmp(szaBuffer,
cszaExpectedContentType,
strlen(cszaExpectedContentType)))
{
hr = S_OK;
TraceTag(ttidValidate,
"HrValidateContentType(): "
"Valid content type %s",
szaBuffer);
}
else
{
hr = UPNP_E_INVALID_CONTENT_TYPE;
TraceTag(ttidValidate,
"HrValidateContentType(): "
"Invalid content type %s",
szaBuffer);
}
}
else
{
hr = HrFromLastWin32Error();
TraceError("HrValidateContentType(): "
"Failed to get content type",
hr);
}
TraceError("HrValidateContentType(): "
"Exiting",
hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: HrValidateSOAPRequest
//
// Purpose: Validates the structure of a SOAP request.
//
// Arguments:
// pxdnReqEnvelope [in] The XML DOM Node for the SOAP envelope element
// pecb [in] The extension control block for the request
// pServiceDescInfo [in] The service description info object for the
// service at which the request is targeted
//
// Returns:
// If the function succeeds, the return value is S_OK. Otherwise, the
// function returns one of the COM error codes defined in WinError.h.
//
// Author: spather 2000/11/8
//
// Notes:
//
HRESULT
HrValidateSOAPRequest(
IN IXMLDOMNode * pxdnReqEnvelope,
IN LPEXTENSION_CONTROL_BLOCK pecb,
IN IUPnPServiceDescriptionInfo * pServiceDescInfo)
{
HRESULT hr = S_OK;
Assert(pxdnReqEnvelope);
Assert(pecb);
Assert(pServiceDescInfo);
hr = HrValidateContentType(pecb);
if (SUCCEEDED(hr))
{
LPWSTR szSOAPActionHeader = NULL;
hr = HrGetSOAPActionHeader(pecb, &szSOAPActionHeader);
if (SUCCEEDED(hr))
{
Assert(szSOAPActionHeader);
hr = HrValidateSOAPStructure(pxdnReqEnvelope,
pServiceDescInfo,
szSOAPActionHeader);
delete [] szSOAPActionHeader;
}
}
if (E_FAIL == hr)
{
hr = UPNP_E_BAD_REQUEST;
}
TraceError("HrValidateSOAPRequest(): "
"Exiting",
hr);
return hr;
}