//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 2000. // // File: V A L I D A T I O N M A N A G E R . C P P // // Contents: Validates device host inputs // // Notes: // // Author: mbend 9 Oct 2000 // //---------------------------------------------------------------------------- #include "pch.h" #pragma hdrstop #include "uhbase.h" #include "ValidationManager.h" #include "uhutil.h" #include "ncstring.h" #include "validate.h" #include "uhcommon.h" // Functions declarationc HRESULT HrValidateDevice( IXMLDOMNodePtr & pNodeDevice, CUString & strErrorString); CValidationManager::CValidationManager () { } CValidationManager::~CValidationManager () { } HRESULT HrGetDocumentAndRootNode( BSTR bstrTemplate, IXMLDOMDocumentPtr & pDoc, IXMLDOMNodePtr & pRootNode) { TraceTag(ttidValidate, "HrGetDocumentAndRootNode"); HRESULT hr = S_OK; // Load document and fetch needed items hr = HrLoadDocument(bstrTemplate, pDoc); if(SUCCEEDED(hr)) { hr = pRootNode.HrAttach(pDoc); } TraceHr(ttidValidate, FAL, hr, FALSE, "HrGetDocumentAndRootNode"); return hr; } struct PresenceItem { const wchar_t * m_szName; bool m_bEmpty; bool m_bSuffix; bool m_bOptional; LONG m_cchMax; }; // m_szName EMPTY SUFFIX OPTONAL CCH // ---------------------------------------------------------- const PresenceItem g_arpiDeviceItems[] = { {L"deviceType", false, true, false, 64}, {L"friendlyName", false, false, false, 64}, {L"manufacturer", false, false, false, 64}, {L"manufacturerURL", false, false, true, -1}, {L"modelDescription", false, false, true, 128}, {L"modelName", false, false, false, 32}, {L"modelNumber", false, false, true, 32}, {L"modelURL", false, false, true, -1}, {L"serialNumber", false, false, true, 64}, {L"UDN", false, false, false, -1}, {L"UPC", false, false, true, 12}, }; const long c_nDeviceItems = celems(g_arpiDeviceItems); PresenceItem g_arpiServiceItems[] = { {L"serviceType", false, true, false, 64}, {L"serviceId", false, true, false, 64}, {L"SCPDURL", false, false, false, -1}, {L"controlURL", true, false, false, -1}, {L"eventSubURL", true, false, false, -1}, }; const long c_nServiceItems = celems(g_arpiServiceItems); PresenceItem g_arpiIconItems[] = { {L"mimetype", false, false, false, -1}, {L"width", false, false, false, -1}, {L"height", false, false, false, -1}, {L"depth", false, false, false, -1}, {L"url", false, false, false, -1}, }; const long c_nIconItems = celems(g_arpiIconItems); PresenceItem g_arpiRootItems[] = { {L"/root/specVersion", false, false, false, -1}, }; const long c_nRootItems = celems(g_arpiRootItems); HRESULT HrValidateSufixes(IXMLDOMNodePtr & pNode, const wchar_t * szName, LONG cchMax, CUString & strErrorString) { HRESULT hr = S_OK; DWORD ctok = 0; LPCWSTR pchText; CUString strText; IXMLDOMNodePtr pNodeItem; // This function validates the serviceType, deviceType, and serviceId // elements in the following way: // Each of these is of the form: urn:domain-name:keyword:SUFFIX:version // Since they all follow the same format (which currently we DO NOT // validate), we can make an assumption that the 4th token (SUFFIX) is the // one that we need to validate. The validation is strictly as according // to UPnP architecture 1.0 where this suffix must be <= 64 characters in // length. // hr = HrSelectNode(szName, pNode, pNodeItem); if (SUCCEEDED(hr)) { hr = HrGetNodeText(pNodeItem, strText); if (SUCCEEDED(hr)) { pchText = strText.GetBuffer(); while (*pchText) { if (*pchText == L':') { ctok++; pchText++; if (ctok == 3) { // Fourth token is the one we need to examine LONG cch = 0; while (*pchText && *pchText != L':') { pchText++; cch++; } // ISSUE-2000/11/29-danielwe: We don't yet // validate the format of the suffix // if (cch > cchMax) { hr = strErrorString.HrPrintf( WszLoadString(_Module.GetResourceInstance(), IDS_SUFFIX_TOO_LONG), strText); if (SUCCEEDED(hr)) { hr = UPNP_E_SUFFIX_TOO_LONG; } } break; } } else { pchText++; } } } } TraceHr(ttidValidate, FAL, hr, FALSE, "HrValidateSufixes(%S)", strErrorString.GetLength() ? strErrorString.GetBuffer(): L"Unspecified"); return hr; } HRESULT HrValidatePresenceItems( IXMLDOMNodePtr & pNode, long nPresenceItems, const PresenceItem * arPresenceItems, CUString & strErrorString) { HRESULT hr = S_OK; for(long n = 0; n < nPresenceItems && SUCCEEDED(hr); ++n) { AssertSz(FImplies(arPresenceItems[n].m_bEmpty, arPresenceItems[n].m_cchMax == -1), "Empty elements mean there shouldn't be a size to verify " "against! Fix the array above!"); if(arPresenceItems[n].m_bEmpty) { hr = HrIsNodePresentOnceAndEmpty(arPresenceItems[n].m_szName, pNode); if(S_OK != hr) { hr = strErrorString.HrPrintf( WszLoadString(_Module.GetResourceInstance(), IDS_EMPTY_NODE_NOT_PRESENT), arPresenceItems[n].m_szName); if(SUCCEEDED(hr)) { hr = UPNP_E_REQUIRED_ELEMENT_ERROR; } } } else if (!arPresenceItems[n].m_bSuffix) { hr = HrIsNodePresentOnceAndNotEmpty(arPresenceItems[n].m_szName, pNode); if(S_OK != hr) { if (UPNP_E_DUPLICATE_NOT_ALLOWED == hr) { // Didn't find the item and it's not optional hr = strErrorString.HrPrintf( WszLoadString(_Module.GetResourceInstance(), IDS_DUPLICATES_NOT_ALLOWED), arPresenceItems[n].m_szName); if(SUCCEEDED(hr)) { hr = UPNP_E_DUPLICATE_NOT_ALLOWED; } } else if (!arPresenceItems[n].m_bOptional) { // Didn't find the item and it's not optional hr = strErrorString.HrPrintf( WszLoadString(_Module.GetResourceInstance(), IDS_NON_EMPTY_NODE_NOT_PRESENT), arPresenceItems[n].m_szName); if(SUCCEEDED(hr)) { hr = UPNP_E_REQUIRED_ELEMENT_ERROR; } } else { // Element was optional hr = S_OK; } } else if (arPresenceItems[n].m_cchMax != -1) { // Check length if one is specified // hr = HrIsNodeOfValidLength(arPresenceItems[n].m_szName, pNode, arPresenceItems[n].m_cchMax); if(S_FALSE == hr) { hr = strErrorString.HrPrintf( WszLoadString(_Module.GetResourceInstance(), IDS_ELEMENT_VALUE_TOO_LONG), arPresenceItems[n].m_szName); if(SUCCEEDED(hr)) { hr = UPNP_E_VALUE_TOO_LONG; } } else if (arPresenceItems[n].m_bOptional && (FAILED(hr))) { // If item was optional, forget any errors hr = S_OK; } } } if (SUCCEEDED(hr)) { if (arPresenceItems[n].m_bSuffix && arPresenceItems[n].m_cchMax != -1) { hr = HrValidateSufixes(pNode, arPresenceItems[n].m_szName, arPresenceItems[n].m_cchMax, strErrorString); } } } TraceHr(ttidValidate, FAL, hr, FALSE, "HrValidatePresenceItems(%S)", strErrorString.GetLength() ? strErrorString.GetBuffer(): L"Unspecified"); return hr; } HRESULT HrValidateDeviceService( IXMLDOMNodePtr & pNodeService, CUString & strErrorString) { HRESULT hr = S_OK; hr = HrValidatePresenceItems(pNodeService, c_nServiceItems, g_arpiServiceItems, strErrorString); TraceHr(ttidValidate, FAL, hr, FALSE, "HrValidateDeviceService"); return hr; } HRESULT HrCheckForDuplicatesInList( IXMLDOMNodeListPtr & pNodeList, CUString & strErrorString) { HRESULT hr = S_OK; CUArray arstrValues; while(SUCCEEDED(hr)) { IXMLDOMNodePtr pNode; HRESULT hrTemp = pNodeList->nextNode(pNode.AddressOf()); if(S_OK != hrTemp) { break; } CUString strText; hr = HrGetNodeText(pNode, strText); if(SUCCEEDED(hr)) { long nIndex = 0; hrTemp = arstrValues.HrFind(strText, nIndex); if(S_OK == hrTemp) { // We found a duplicate hr = strErrorString.HrPrintf( WszLoadString(_Module.GetResourceInstance(), IDS_DUPLICATES_NOT_ALLOWED), strText.GetBuffer()); if(SUCCEEDED(hr)) { hr = UPNP_E_DUPLICATE_NOT_ALLOWED; } } else { hr = arstrValues.HrPushBack(strText); } } } TraceHr(ttidValidate, FAL, hr, FALSE, "HrCheckForDuplicatesInList"); return hr; } HRESULT HrValidateDeviceServices( IXMLDOMNodePtr & pNodeDevice, CUString & strErrorString) { HRESULT hr = S_OK; // serviceList is required and must contain a service BOOL bServiceNotPresent = TRUE; HRESULT hrTemp = HrIsNodePresentOnce(L"serviceList", pNodeDevice); if(S_OK == hrTemp) { IXMLDOMNodeListPtr pNodeList; hrTemp = HrSelectNodes(L"serviceList/service", pNodeDevice, pNodeList); if(S_OK == hrTemp) { while(SUCCEEDED(hr)) { IXMLDOMNodePtr pNode; hrTemp = pNodeList->nextNode(pNode.AddressOf()); if(S_OK != hrTemp) { break; } // We have a service bServiceNotPresent = FALSE; hr = HrValidateDeviceService(pNode, strErrorString); } } // Make sure all ServiceId's are unique if(SUCCEEDED(hr)) { pNodeList.Release(); hrTemp = HrSelectNodes(L"serviceList/service/serviceId", pNodeDevice, pNodeList); if(S_OK == hrTemp) { hr = HrCheckForDuplicatesInList(pNodeList, strErrorString); } } } if(bServiceNotPresent) { hr = strErrorString.HrAssign( WszLoadString(_Module.GetResourceInstance(), IDS_SERVICE_MISSING)); if(SUCCEEDED(hr)) { hr = UPNP_E_INVALID_SERVICE; } } TraceHr(ttidValidate, FAL, hr, FALSE, "HrValidateDeviceServices"); return hr; } HRESULT HrValidateDeviceIcon( IXMLDOMNodePtr & pNodeIcon, CUString & strErrorString) { HRESULT hr = S_OK; hr = HrValidatePresenceItems(pNodeIcon, c_nIconItems, g_arpiIconItems, strErrorString); TraceHr(ttidValidate, FAL, hr, FALSE, "HrValidateDeviceIcon"); return hr; } HRESULT HrValidateDeviceIcons( IXMLDOMNodePtr & pNodeDevice, CUString & strErrorString) { HRESULT hr = S_OK; HRESULT hrTemp = HrIsNodePresentOnce(L"iconList", pNodeDevice); if(S_OK == hrTemp) { BOOL fGotAnIcon = FALSE; IXMLDOMNodeListPtr pNodeList; hrTemp = HrSelectNodes(L"iconList/icon", pNodeDevice, pNodeList); if(S_OK == hrTemp) { while(SUCCEEDED(hr)) { IXMLDOMNodePtr pNode; hrTemp = pNodeList->nextNode(pNode.AddressOf()); if(S_OK != hrTemp) { break; } fGotAnIcon = TRUE; hr = HrValidateDeviceIcon(pNode, strErrorString); } } if (!fGotAnIcon) { hr = strErrorString.HrAssign( WszLoadString(_Module.GetResourceInstance(), IDS_ICON_MISSING)); if(SUCCEEDED(hr)) { hr = UPNP_E_INVALID_ICON; } } } TraceHr(ttidValidate, FAL, hr, FALSE, "HrValidateDeviceIcons"); return hr; } HRESULT HrValidateDeviceChildren( IXMLDOMNodePtr & pNodeDevice, CUString & strErrorString) { HRESULT hr = S_OK; BOOL fGotADevice = FALSE; HRESULT hrTemp = HrIsNodePresentOnce(L"deviceList", pNodeDevice); if(S_OK == hrTemp) { IXMLDOMNodeListPtr pNodeList; hrTemp = HrSelectNodes(L"deviceList/device", pNodeDevice, pNodeList); if(S_OK == hrTemp) { while(SUCCEEDED(hr)) { IXMLDOMNodePtr pNode; hrTemp = pNodeList->nextNode(pNode.AddressOf()); if(S_OK != hrTemp) { break; } fGotADevice = TRUE; hr = HrValidateDevice(pNode, strErrorString); } } if (!fGotADevice) { hr = strErrorString.HrAssign( WszLoadString(_Module.GetResourceInstance(), IDS_DEVICE_MISSING)); if(SUCCEEDED(hr)) { hr = UPNP_E_INVALID_DOCUMENT; } } } TraceHr(ttidValidate, FAL, hr, FALSE, "HrValidateDeviceChildren"); return hr; } HRESULT HrValidateDevice( IXMLDOMNodePtr & pNodeDevice, CUString & strErrorString) { TraceTag(ttidValidate, "HrValidateDevice"); HRESULT hr = S_OK; hr = HrValidatePresenceItems(pNodeDevice, c_nDeviceItems, g_arpiDeviceItems, strErrorString); if(SUCCEEDED(hr)) { hr = HrValidateDeviceServices(pNodeDevice, strErrorString); if(SUCCEEDED(hr)) { hr = HrValidateDeviceIcons(pNodeDevice, strErrorString); if(SUCCEEDED(hr)) { hr = HrValidateDeviceChildren(pNodeDevice, strErrorString); } } } TraceHr(ttidValidate, FAL, hr, FALSE, "HrValidateDevice"); return hr; } HRESULT HrValidateUDNs( IXMLDOMNodePtr & pNodeDevice, CUString & strErrorString) { HRESULT hr = S_OK; IXMLDOMNodeListPtr pNodeList; hr = HrSelectNodes(L"//UDN", pNodeDevice, pNodeList); if(SUCCEEDED(hr)) { hr = HrCheckForDuplicatesInList(pNodeList, strErrorString); } TraceHr(ttidValidate, FAL, hr, FALSE, "HrValidateUDNs"); return hr; } /* HRESULT HrValidateDevice( IXMLDOMNodePtr & pNodeDevice) { TraceTag(ttidValidate, ""); HRESULT hr = S_OK; TraceHr(ttidValidate, FAL, hr, FALSE, ""); return hr; } */ // IUPnPValidationManager methods STDMETHODIMP CValidationManager::ValidateDescriptionDocument( /*[in]*/ BSTR bstrTemplate, /*[out, string]*/ wchar_t ** pszErrorString) { CHECK_POINTER(bstrTemplate); CHECK_POINTER(pszErrorString); HRESULT hr = S_OK; CUString strErrorString; *pszErrorString = NULL; IXMLDOMDocumentPtr pDoc; IXMLDOMNodePtr pRootNode; hr = HrIsAllowedCOMCallLocality(CALL_LOCALITY_INPROC); if (SUCCEEDED(hr)) { hr = HrGetDocumentAndRootNode(bstrTemplate, pDoc, pRootNode); } if(SUCCEEDED(hr)) { hr = HrValidatePresenceItems(pRootNode, c_nRootItems, g_arpiRootItems, strErrorString); if(SUCCEEDED(hr)) { hr = HrIsNodePresentOnce(L"/root/device", pRootNode); if(S_OK == hr) { hr = HrIsNodePresentOnce(L"/root/URLBase", pRootNode); if (S_OK != hr) { hr = HrValidateUDNs(pRootNode, strErrorString); if(SUCCEEDED(hr)) { IXMLDOMNodePtr pNodeDevice; hr = HrSelectNode(L"/root/device", pRootNode, pNodeDevice); if(SUCCEEDED(hr)) { hr = HrValidateDevice(pNodeDevice, strErrorString); } } } else { hr = strErrorString.HrAssign( WszLoadString(_Module.GetResourceInstance(), IDS_URLBASE_PRESENT)); if(SUCCEEDED(hr)) { hr = UPNP_E_REQUIRED_ELEMENT_ERROR; } } } else { hr = strErrorString.HrAssign( WszLoadString(_Module.GetResourceInstance(), IDS_ROOT_DEVICE_MISSING)); if(SUCCEEDED(hr)) { hr = UPNP_E_REQUIRED_ELEMENT_ERROR; } } } } // Let's make sure the root namespace is according to spec // if (SUCCEEDED(hr)) { IXMLDOMNodePtr pNodeRootSub; hr = HrSelectNode(L"/root", pRootNode, pNodeRootSub); if (SUCCEEDED(hr)) { BSTR bstrUri; hr = pNodeRootSub->get_namespaceURI(&bstrUri); if (S_OK != hr || lstrcmpi(bstrUri, L"urn:schemas-upnp-org:device-1-0")) { hr = strErrorString.HrPrintf( WszLoadString(_Module.GetResourceInstance(), IDS_INVALID_ROOT_NAMESPACE), bstrUri); if(SUCCEEDED(hr)) { hr = UPNP_E_INVALID_ROOT_NAMESPACE; } } } } if(FAILED(hr)) { if(strErrorString.GetLength()) { strErrorString.HrGetCOM(pszErrorString); } } TraceHr(ttidValidate, FAL, hr, FALSE, "CValidationManager::ValidateDescriptionDocument(%S)", *pszErrorString ? *pszErrorString : L"Unspecified"); return hr; } STDMETHODIMP CValidationManager::ValidateServiceDescription( /*[in, string]*/ const wchar_t * szFullPath, /*[out, string]*/ wchar_t ** pszErrorString) { CHECK_POINTER(szFullPath); CHECK_POINTER(pszErrorString); HRESULT hr = S_OK; CUString strErrorString; *pszErrorString = NULL; BSTR bstrPath; IXMLDOMDocumentPtr pDoc; hr = HrIsAllowedCOMCallLocality(CALL_LOCALITY_INPROC); if (SUCCEEDED(hr)) { bstrPath = SysAllocString(szFullPath); } if (bstrPath) { hr = HrLoadDocumentFromFile(bstrPath, pDoc); if (SUCCEEDED(hr)) { IXMLDOMElementPtr pxdeSDRoot; hr = pDoc->get_documentElement(pxdeSDRoot.AddressOf()); if (S_OK == hr) { hr = HrValidateServiceDescription(pxdeSDRoot, pszErrorString); } } else { hr = strErrorString.HrPrintf( WszLoadString(_Module.GetResourceInstance(), IDS_INVALID_XML), szFullPath); if(SUCCEEDED(hr)) { hr = UPNP_E_INVALID_XML; } } SysFreeString(bstrPath); } else { hr = E_OUTOFMEMORY; } if(FAILED(hr)) { if(strErrorString.GetLength()) { strErrorString.HrGetCOM(pszErrorString); } } TraceHr(ttidValidate, FAL, hr, FALSE, "CValidationManager::ValidateServiceDescription(%S)", *pszErrorString ? *pszErrorString : L"Unspecified"); return hr; } HRESULT CValidationManager::ValidateServiceDescriptions(const wchar_t * szResourcePath, IXMLDOMNodePtr pRootNode, wchar_t ** pszErrorString) { HRESULT hr = S_OK; IXMLDOMNodeListPtr pNodeList; // Select all of the SCPDURL nodes in the description document hr = HrSelectNodes(L"//device/serviceList/service/SCPDURL", pRootNode, pNodeList); while (S_OK == hr) { IXMLDOMNodePtr pNode; CUString strUrl; hr = pNodeList->nextNode(pNode.AddressOf()); if (S_OK == hr) { hr = HrGetNodeText(pNode, strUrl); if (SUCCEEDED(hr)) { CUString strFullPath; hr = HrMakeFullPath(szResourcePath, strUrl, strFullPath); if (SUCCEEDED(hr)) { hr = ValidateServiceDescription(strFullPath, pszErrorString); } } } } if (SUCCEEDED(hr)) { // normalize error code hr = S_OK; } TraceHr(ttidValidate, FAL, hr, FALSE, "CValidationManager::ValidateServiceDescriptions(%S)", *pszErrorString ? *pszErrorString : L"Unspecified"); return hr; } HRESULT CValidationManager::ValidateIconFiles(const wchar_t * szResourcePath, IXMLDOMNodePtr pRootNode, wchar_t ** pszErrorString) { HRESULT hr = S_OK; IXMLDOMNodeListPtr pNodeList; CUString strErrorString; // Select all of the SCPDURL nodes in the description document hr = HrSelectNodes(L"//device/iconList/icon/url", pRootNode, pNodeList); while (S_OK == hr) { IXMLDOMNodePtr pNode; CUString strUrl; hr = pNodeList->nextNode(pNode.AddressOf()); if (S_OK == hr) { hr = HrGetNodeText(pNode, strUrl); if (SUCCEEDED(hr)) { CUString strFullPath; hr = HrMakeFullPath(szResourcePath, strUrl, strFullPath); if (SUCCEEDED(hr)) { if (!FFileExists((LPTSTR)strFullPath.GetBuffer(), FALSE)) { hr = strErrorString.HrPrintf( WszLoadString(_Module.GetResourceInstance(), IDS_INVALID_ICON), strFullPath); if(SUCCEEDED(hr)) { hr = UPNP_E_INVALID_ICON; } } } } } } if(FAILED(hr)) { if(strErrorString.GetLength()) { strErrorString.HrGetCOM(pszErrorString); } } if (SUCCEEDED(hr)) { // normalize error code hr = S_OK; } TraceHr(ttidValidate, FAL, hr, FALSE, "CValidationManager::ValidateIconFiles(%S)", *pszErrorString ? *pszErrorString : L"Unspecified"); return hr; } STDMETHODIMP CValidationManager::ValidateDescriptionDocumentAndReferences( /*[in]*/ BSTR bstrTemplate, /*[in, string]*/ const wchar_t * szResourcePath, /*[out, string]*/ wchar_t ** pszErrorString) { CHECK_POINTER(bstrTemplate); CHECK_POINTER(szResourcePath); CHECK_POINTER(pszErrorString); HRESULT hr = S_OK; *pszErrorString = NULL; hr = HrIsAllowedCOMCallLocality(CALL_LOCALITY_INPROC); if (SUCCEEDED(hr)) { hr = ValidateDescriptionDocument(bstrTemplate, pszErrorString); } if (SUCCEEDED(hr)) { IXMLDOMDocumentPtr pDoc; IXMLDOMNodePtr pRootNode; hr = HrGetDocumentAndRootNode(bstrTemplate, pDoc, pRootNode); if (SUCCEEDED(hr)) { hr = ValidateServiceDescriptions(szResourcePath, pRootNode, pszErrorString); if (SUCCEEDED(hr)) { hr = ValidateIconFiles(szResourcePath, pRootNode, pszErrorString); } } } TraceHr(ttidValidate, FAL, hr, FALSE, "CValidationManager::ValidateDescriptionDocumentAndReferences(%S)", *pszErrorString ? *pszErrorString : L"Unspecified"); return hr; }