2025-04-27 07:49:33 -04:00

743 lines
32 KiB
C++

// acallfnd.cpp
//
// PROPFIND and PROPPATCH response handlers
#include <objbase.h>
#include <assert.h>
#include <wininet.h>
#include "asynccall.h"
#include "qxml.h"
#include "strutil.h"
#include "regexp.h"
#include "mischlpr.h"
#define MAX_PREFIX 100
// creates a new string which is pwszXMLPath with "<pwszNamespace>:" added before each tag
//
// ex. "foo/bar/baz", "DAV" -> "DAV:foo/DAV:bar/DAV:baz"
//
// new string is allocated and returned, client is responsible for freeing it.
LPWSTR CAsyncWntCallbackImpl::_XMLNSExtend (LPWSTR pwszNamespace,
LPWSTR pwszXMLPath)
{
DWORD cch;
DWORD cchWritten = 0;
LPWSTR pwsz = NULL;
LPWSTR pwszTemp = NULL;
LPWSTR pwszTemp2 = NULL;
UINT cSlashes = 0;
UINT cchNamespace;
UINT cchXMLPath;
if (pwszNamespace != NULL && pwszXMLPath != NULL)
{
cchXMLPath = lstrlen(pwszXMLPath);
cchNamespace = lstrlen(pwszNamespace);
// count the slashes
for (UINT i = 0; i < cchXMLPath ; i++)
{
if (pwszXMLPath[i] == '/')
{
cSlashes++;
}
}
// need length of initial string,
// plus (length of namespace + 1) for each token,
// and there is ((number of slashes) + 1) tokens
cch = cchXMLPath + (cSlashes+1) * (1 + cchNamespace);
pwsz = AllocateStringW(cch);
if (pwsz != NULL)
{
// initialize the string so we can use lstrcat
pwsz[0] = NULL;
pwszTemp = pwszXMLPath; // we advance pwszTemp down pwszXMLPath, building up pwsz as we go
for (i = 0 ; i < cSlashes ; i++) // for each token
{
if (pwszTemp == NULL) // have we reached the end?
{
free(pwsz);
pwsz = NULL;
}
else
{
// find the next slash after the read head
pwszTemp2=LStrStr(pwszTemp, L"/");
if (pwszTemp2 == NULL)
{
free(pwsz);
pwsz = NULL;
}
else
{
// copy namespace:<token>, where token is the text up to the next slash
// -- add the "namespace:"
lstrcat(pwsz, pwszNamespace);
lstrcat(pwsz, L":");
cchWritten+= lstrlen(pwszNamespace) + 1;
// -- add the next token
lstrcpyn(pwsz + cchWritten, pwszTemp, pwszTemp2 - pwszTemp + 1);
lstrcat(pwsz, L"/");
cchWritten+= (pwszTemp2 - pwszTemp + 1 + 1);
// advance read head to after the next slash
pwszTemp = pwszTemp2+1;
}
}
}
}
if (pwsz != NULL) // if we haven't failed yet
{
// we stop after the last slash, but we still need to add in the last
// token in the path, which is the token AFTER the last slash
lstrcat(pwsz, pwszNamespace);
lstrcat(pwsz, L":");
lstrcat(pwsz, pwszTemp);
}
}
return pwsz;
}
////////////////////////
// take in a buffer, initialize pqxml with everything between "<?xml" and :"multistatus>"
// also store the alias for DAV: in this XML blob
STDMETHODIMP CAsyncWntCallbackImpl::_InitQXMLFromMessyBuffer(CQXML* pqxml,
LPSTR pszXML,
WCHAR wszDAVAlias[])
{
// locals
HRESULT hres = S_OK;
LPWSTR pwszTemp = NULL;
LPWSTR pwszResponse = NULL;
LPWSTR pwszResponsePtr = NULL;
ULONG cch;
// code
// -- first, convert the ANSI string that holds the XML into a CQXML object
// is the response coming back ANSI or Unicode? Is there a way we can tell?
// ---- pwszResponse is the ptr to the start of the response string
pwszResponse = ConvertToUnicode (CP_ACP, pszXML);
if (pwszResponse == NULL)
{
hres = E_OUTOFMEMORY;
}
else
{
// ---- pwszResponsePtr is the ptr to the first meaningful part of the response string
pwszResponsePtr = pwszResponse;
// -- strip off the <?xml versioninfo ?> thing
pwszTemp = LStrStr(pwszResponse, L"<?xml");
if (pwszTemp != NULL)
{
pwszTemp = LStrStr(pwszTemp, L"?>");
if (pwszTemp != NULL)
{
pwszResponsePtr = pwszTemp + 3;
pwszTemp = NULL;
}
else
{
hres = E_FAIL;
}
}
else
{
hres = E_FAIL;
}
if (SUCCEEDED(hres))
{
// -- strip off any garbage after </D:multistatus>
pwszTemp = LStrStr(pwszResponsePtr, L"multistatus>");
if (pwszTemp != NULL)
{
*(pwszTemp+12) = NULL; // cutt of stuff after
// -- initialize the qxml object
hres = pqxml->InitFromBuffer(pwszResponsePtr);
if (SUCCEEDED(hres) && hres != S_FALSE)
{
// find the alias associated with DAV:
if (FindEnclosedText(pwszResponse, L"xmlns:", L"=\"DAV:\"", &pwszTemp, &cch))
{
if (pwszTemp == NULL)
{
lstrcpy(wszDAVAlias, L"DAV"); // default to "DAV" as the name for DAV:
}
else
{
lstrcpyn(wszDAVAlias, pwszTemp, cch+1); // may be that user has defined A: to mean DAV:
}
}
else
{
hres = E_FAIL;
}
}
}
else
{
hres = E_FAIL;
}
}
}
// release stuff
if (pwszResponse != NULL)
{
free(pwszResponse);
}
// return value
return hres;
}
////////////////////////
LPWSTR CAsyncWntCallbackImpl::_GetHREF (CQXML* pqxml,
WCHAR wszDavPrefix[])
{
// locals
LPWSTR pwszTagName = NULL;
HRESULT hres = S_OK;
WCHAR wszPath[MAX_PATH];
LPWSTR pwszResult = NULL;
// check params
if (pqxml == NULL || wszDavPrefix == NULL)
{
hres = E_INVALIDARG;
}
else
{
// code
pwszTagName = _XMLNSExtend ( wszDavPrefix, L"href");
if (pwszTagName != NULL)
{
hres = pqxml->GetText(NULL, pwszTagName, wszPath, MAX_PATH);
}
}
// release stuff
if (pwszTagName != NULL)
{
free(pwszTagName);
}
// return value
if (SUCCEEDED(hres))
{
pwszResult = DuplicateStringW(wszPath);
}
else
{
pwszResult = NULL;
}
return pwszResult;
}
////////////////////////
STDMETHODIMP CAsyncWntCallbackImpl::_RespondHandlePROPFINDHelper(DAVRESPONSE* pdavResponse,
LPWSTR pwszPath,
LPWSTR pwszDavPrefix,
CQXML* pqxml)
{
// locals
HRESULT hres = S_OK;
CQXML* pqxml2 = NULL;
CQXML* pqxml3 = NULL;
CQXMLEnum* pqxmlEnum2 = NULL;
IPropFindRequest* pfindreq;
UINT cProperties;
UINT cPropertiesRequested;
DAVPROPVAL* prgPropValTemp = NULL;
DAVPROPID dpi;
LPWSTR pwszTag = NULL;
LPWSTR pwszTagName = NULL;
LPWSTR pwszNamespace = NULL;
LPWSTR pwszNamespaceAlias = NULL;
LPWSTR pwszValue = NULL;
URL_COMPONENTS urlComponents = {0};
// code
// first, if this is noRoot, then stop if this is the root
urlComponents.dwStructSize = sizeof(URL_COMPONENTS);
urlComponents.dwUrlPathLength = 1;
if (!InternetCrackUrl(pwszPath, 0, 0, &urlComponents))
{
hres = E_FAIL;
}
else if (_fNoRoot &&
(lstrlen(pdavResponse->rPropFind.pwszHref) == (INT)urlComponents.dwUrlPathLength) &&
(LStrCmpNI((LPWSTR)pdavResponse->rPropFind.pwszHref, urlComponents.lpszUrlPath, urlComponents.dwUrlPathLength) == 0))
{
hres = S_OK; // this is the root, ignore it
}
else
{
// get the properties of the item found
pwszTagName = _XMLNSExtend ( pwszDavPrefix, L"propstat");
if (pwszTagName == NULL)
{
hres = E_OUTOFMEMORY;
}
else
{
hres = pqxml->GetQXML(NULL, pwszTagName, &pqxml2);
if (SUCCEEDED(hres) && hres != S_FALSE)
{
pwszTagName = _XMLNSExtend ( pwszDavPrefix, L"prop");
if (pwszTagName == NULL)
{
hres = E_OUTOFMEMORY;
}
else
{
hres = pqxml2->GetQXMLEnum(pwszTagName, L"*", &pqxmlEnum2);
if (SUCCEEDED(hres) && hres != S_FALSE)
{
free(pwszTagName);
pwszTagName = NULL;
// this is the count of properties for this entry
hres = pqxmlEnum2->GetCount((long*)&cProperties);
if (SUCCEEDED(hres) && hres != S_FALSE)
{
// this is the number of properties requested by the user
pfindreq = (IPropFindRequest*)_dwParam;
assert(pfindreq != NULL);
hres = pfindreq->GetPropCount(&cPropertiesRequested);
if (SUCCEEDED(hres))
{
// is this an allprop?
if (cPropertiesRequested > 0)
{
// if not an allprop, we only need entries for the things we requested
prgPropValTemp = (DAVPROPVAL*)malloc(sizeof(DAVPROPVAL) * cPropertiesRequested);
pdavResponse->rPropFind.cPropVal = 0;
}
else
{
// if cProperties == 0, then this is an allprop, so we need double the total number of entries
prgPropValTemp = (DAVPROPVAL*)malloc(sizeof(DAVPROPVAL) * cProperties * 2);
pdavResponse->rPropFind.cPropVal = (WORD)(cProperties * 2);
}
if (prgPropValTemp == NULL)
{
hres = E_OUTOFMEMORY;
}
else
{
for (DWORD j = 0; j < cProperties; j++)
{
hres = (pqxmlEnum2->NextQXML(&pqxml3));
if (SUCCEEDED(hres))
{
hres = pqxml3->GetRootTagNameNoBuf(&pwszTag);
if (SUCCEEDED(hres) && hres != S_FALSE)
{
hres = pqxml3->GetRootNamespaceNameNoBuf(&pwszNamespace);
if (SUCCEEDED(hres))
{
hres = pqxml3->GetRootNamespaceAliasNoBuf(&pwszNamespaceAlias);
if (SUCCEEDED(hres) && hres != S_FALSE)
{
hres = pqxml3->GetTextNoBuf(NULL, NULL, &pwszValue);
if (SUCCEEDED(hres) && hres != S_FALSE)
{
if (cPropertiesRequested > 0)
{
// not a find: allprop, only return if one of the requested props
if (pfindreq->GetPropInfo(pwszNamespace,
//pwszNamespaceAlias,
pwszTag,
&dpi))
{
prgPropValTemp[(pdavResponse->rPropFind.cPropVal)].dwId = dpi.dwId;
prgPropValTemp[(pdavResponse->rPropFind.cPropVal)].dpt = DPT_LPWSTR;
prgPropValTemp[(pdavResponse->rPropFind.cPropVal)].pwszVal = DuplicateStringW(pwszValue);
pdavResponse->rPropFind.cPropVal = (WORD)(pdavResponse->rPropFind.cPropVal + 1);
}
}
else
{
// a find: allprop, return prop no matter what it is
// we need to return two properties: the tag name (w/ namespace) and the value, since on an allprop we
// don't know what IDs to bind things to
prgPropValTemp[j*2].dwId = j*2;
prgPropValTemp[j*2].dpt = DPT_LPWSTR;
if (pwszNamespace != NULL)
{
prgPropValTemp[j*2].pwszVal = (LPWSTR)malloc(sizeof(WCHAR) * (lstrlen(pwszTag) + lstrlen(pwszNamespace) + 1));
lstrcpy(prgPropValTemp[j*2].pwszVal, pwszNamespace);
lstrcat(prgPropValTemp[j*2].pwszVal, pwszTag);
}
else
{
prgPropValTemp[j*2].pwszVal = DuplicateStringW(pwszTag);
}
prgPropValTemp[j*2 + 1].dwId = j*2 + 1;
prgPropValTemp[j*2 + 1].dpt = DPT_LPWSTR;
prgPropValTemp[j*2 + 1].pwszVal = DuplicateStringW(pwszValue);
}
pqxml3->ReleaseBuf(pwszTag);
if (pwszNamespace != NULL)
{
pqxml3->ReleaseBuf(pwszNamespace);
assert(pwszNamespaceAlias != NULL); // namespace and alias go together
pqxml3->ReleaseBuf(pwszNamespaceAlias);
}
pqxml3->ReleaseBuf(pwszValue);
delete pqxml3;
}
}
}
}
}
}
}
}
delete pqxmlEnum2;
}
}
}
delete pqxml2;
}
}
if (pdavResponse->rPropFind.cPropVal > 0)
{
// only call the callback if we actually found anything
pdavResponse->rPropFind.rgPropVal = (DAVPROPVAL*)malloc(sizeof(DAVPROPVAL) * pdavResponse->rPropFind.cPropVal);
if (pdavResponse->rPropFind.rgPropVal == NULL)
{
hres = E_OUTOFMEMORY;
}
else
{
CopyMemory(pdavResponse->rPropFind.rgPropVal, prgPropValTemp, sizeof(DAVPROPVAL) * pdavResponse->rPropFind.cPropVal);
// call the callback once for each entity returned by the server
hres = _pcallback->OnResponse(pdavResponse);
free(pdavResponse->rPropFind.rgPropVal);
}
}
// release stuff
if (prgPropValTemp != NULL)
{
free(prgPropValTemp);
}
}
// return value
return hres;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CAsyncWntCallbackImpl::_RespondHandlePROPPATCHHelper(DAVRESPONSE* pdavResponse,
LPWSTR UNREF_PARAM(pwszPath),
LPWSTR pwszDavPrefix,
CQXML* pqxml)
{
// locals
HRESULT hres;
LPWSTR pwszTagName = NULL;
CQXMLEnum* pqxmlEnum2 = NULL;
CQXML* pqxml2 = NULL;
UINT cValidResponses = 0;
LPWSTR pwszTag = NULL;
LPWSTR pwszNamespace = NULL;
LPWSTR pwszNamespaceAlias = NULL;
IPropPatchRequest* ppatchreq;
DAVPROPID dpi;
// code
// get the properties of the item found
pwszTagName = _XMLNSExtend ( pwszDavPrefix, L"propstat/prop");
if (pwszTagName == NULL)
{
hres = E_OUTOFMEMORY;
}
else
{
hres = pqxml->GetQXMLEnum(pwszTagName, L"*", &pqxmlEnum2);
if (SUCCEEDED(hres) && hres != S_FALSE)
{
free(pwszTagName);
pwszTagName = NULL;
// this is the count of properties returned by the proppatch
hres = pqxmlEnum2->GetCount((long*)&(pdavResponse->rPropFind.cPropVal));
if (SUCCEEDED(hres) && hres != S_FALSE)
{
if (pdavResponse->rPropFind.cPropVal == 0)
{
pdavResponse->rPropFind.rgPropVal = NULL;
}
else
{
pdavResponse->rPropFind.rgPropVal = (DAVPROPVAL*)malloc(sizeof(DAVPROPVAL) * pdavResponse->rPropFind.cPropVal);
if (FAILED(pdavResponse->rPropFind.rgPropVal))
{
hres = E_OUTOFMEMORY;
}
else
{
for (DWORD j = 0; j < pdavResponse->rPropFind.cPropVal; j++)
{
hres = pqxmlEnum2->NextQXML(&pqxml2);
if (SUCCEEDED(hres))
{
hres = pqxml2->GetRootTagNameNoBuf(&pwszTag);
if (SUCCEEDED(hres) && hres != S_FALSE)
{
hres = pqxml2->GetRootNamespaceNameNoBuf(&pwszNamespace); // namespace can be NULL
if (SUCCEEDED(hres))
{
hres = pqxml2->GetRootNamespaceAliasNoBuf(&pwszNamespaceAlias); // namespace can be NULL
if (SUCCEEDED(hres))
{
ppatchreq = (IPropPatchRequest*)_dwParam;
assert(ppatchreq != NULL);
if (ppatchreq->GetPropInfo(pwszNamespace,
pwszTag,
&dpi))
{
pdavResponse->rPropFind.rgPropVal[cValidResponses].dwId = dpi.dwId;
pdavResponse->rPropFind.rgPropVal[cValidResponses].dpt = dpi.dpt;
cValidResponses++;
}
}
if (pwszNamespace != NULL)
{
pqxml2->ReleaseBuf(pwszNamespace);
assert(pwszNamespaceAlias != NULL);
pqxml2->ReleaseBuf(pwszNamespaceAlias);
}
}
pqxml2->ReleaseBuf(pwszTag);
}
delete pqxml2;
}
}
// call the callback once for each entity returned by the server
pdavResponse->rPropFind.cPropVal = (WORD)cValidResponses;
hres = _pcallback->OnResponse(pdavResponse);
free(pdavResponse->rPropFind.rgPropVal);
}
}
}
delete pqxmlEnum2;
}
}
// return value
return hres;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CAsyncWntCallbackImpl::_RespondHandlePROPFINDPATCH(DAVRESPONSE* pdavResponse,
LPWSTR UNREF_PARAM(pwszVerb),
LPWSTR pwszPath,
DWORD cchHeaders,
LPWSTR UNREF_RETAIL_PARAM(pwszHeaders),
DWORD UNREF_PARAM(dwStatusCode),
LPWSTR UNREF_PARAM(pwszStatusCode),
LPWSTR UNREF_PARAM(pwszContentType),
DWORD UNREF_PARAM(cbSent),
LPBYTE pbResponse,
DWORD cbResponse,
BOOL fFind) // find: true, patch: false
{
// locals
HRESULT hres = S_OK;
LPWSTR pwszTagName = NULL;
LONG cResponses;
WCHAR wszDavPrefix[100]; // how long can a namespace name be?
CQXML qxml;
CQXML* pqxml = NULL;
CQXML* pqxml2 = NULL;
CQXMLEnum* pqxmlEnum = NULL;
CQXMLEnum* pqxmlEnum2 = NULL;
// check params
assert(pdavResponse != NULL);
assert(pwszVerb != NULL);
assert(pwszPath != NULL);
if (cbResponse > 0)
{
assert(pbResponse != NULL);
}
if (cchHeaders > 0)
{
assert(pwszHeaders != NULL);
}
assert(pbResponse != NULL);
// code
if (fFind)
{
pdavResponse->command = DAV_PROPFIND;
}
else
{
pdavResponse->command = DAV_PROPPATCH;
}
if (SUCCEEDED(pdavResponse->hrResult))
{
hres = this->_InitQXMLFromMessyBuffer(&qxml, (LPSTR)pbResponse, wszDavPrefix);
if (SUCCEEDED(hres))
{
pwszTagName = _XMLNSExtend ( wszDavPrefix, L"response");
if (pwszTagName == NULL)
{
hres = E_OUTOFMEMORY;
}
else
{
hres = qxml.GetQXMLEnum(NULL, pwszTagName, &pqxmlEnum);
if (SUCCEEDED(hres) && hres != S_FALSE)
{
hres = pqxmlEnum->GetCount(&cResponses);
if (SUCCEEDED(hres) && hres != S_FALSE)
{
// iterate over all the responses returned by the server
for (LONG i = 0; i < cResponses; i++)
{
hres = pqxmlEnum->NextQXML(&pqxml);
if (FAILED(hres) || hres == S_FALSE)
{
break;
}
else
{
// get the href of each item found
pdavResponse->rPropFind.pwszHref = this->_GetHREF(pqxml, wszDavPrefix);
if (pdavResponse->rPropFind.pwszHref != NULL)
{
if (fFind)
{
hres = this->_RespondHandlePROPFINDHelper(pdavResponse, pwszPath, wszDavPrefix, pqxml);
}
else
{
hres = this->_RespondHandlePROPPATCHHelper(pdavResponse, pwszPath, wszDavPrefix, pqxml);
}
}
delete pqxml;
pqxml = NULL;
}
}
}
delete pqxmlEnum;
}
}
}
}
// release stuff
if (pwszTagName != NULL)
{
free(pwszTagName);
}
return hres;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CAsyncWntCallbackImpl::_RespondHandlePROPFIND(DAVRESPONSE* pdavResponse,
LPWSTR pwszVerb,
LPWSTR pwszPath,
DWORD cchHeaders,
LPWSTR pwszHeaders,
DWORD dwStatusCode,
LPWSTR pwszStatusCode,
LPWSTR pwszContentType,
DWORD cbSent,
LPBYTE pbResponse,
DWORD cbResponse)
{
return this->_RespondHandlePROPFINDPATCH(pdavResponse,
pwszVerb,
pwszPath,
cchHeaders,
pwszHeaders,
dwStatusCode,
pwszStatusCode,
pwszContentType,
cbSent,
pbResponse,
cbResponse,
TRUE); // PROPFIND version
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CAsyncWntCallbackImpl::_RespondHandlePROPPATCH(DAVRESPONSE* pdavResponse,
LPWSTR pwszVerb,
LPWSTR pwszPath,
DWORD cchHeaders,
LPWSTR pwszHeaders,
DWORD dwStatusCode,
LPWSTR pwszStatusCode,
LPWSTR pwszContentType,
DWORD cbSent,
LPBYTE pbResponse,
DWORD cbResponse)
{
return this->_RespondHandlePROPFINDPATCH(pdavResponse,
pwszVerb,
pwszPath,
cchHeaders,
pwszHeaders,
dwStatusCode,
pwszStatusCode,
pwszContentType,
cbSent,
pbResponse,
cbResponse,
FALSE); // PROPPATCH version
}