//+--------------------------------------------------------------------------- // // 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 #pragma hdrstop #include #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; }