Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2884 lines
68 KiB

/*
* F S M E T A . C P P
*
* Sources file system implementation of DAV-Meta
*
* Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
*/
#include "_davfs.h"
// CFSFind -------------------------------------------------------------------
//
SCODE
CFSFind::ScAddProp (LPCWSTR, LPCWSTR pwszProp, BOOL)
{
enum { cProps = 8 };
// If this is our first time in, we will need to allocate
// space for all of the properties we are expecting to get
// added over the coarse of this operation.
//
if (m_ft == FIND_NONE)
{
// Note that we have started requesting specific properties
//
m_ft = FIND_SPECIFIC;
}
else if (m_ft != FIND_SPECIFIC)
{
// If we are not finding specfic properties and somebody asked
// for one, then bts (by the spec) this should consititute an
// error.
//
return E_DAV_PROPFIND_TYPE_UNEXPECTED;
}
// See if there is room at the in...
//
if (m_cMaxProps == m_cProps)
{
UINT cb;
// Allocate enough space for the next block of properties
//
m_cMaxProps = m_cProps + cProps;
cb = m_cMaxProps * sizeof(PROPVARIANT);
m_rgwszProps.realloc (cb);
}
// If this is the getcontenttype property, then we need to remember
// its location for use when providing default values...
//
if (!wcscmp (pwszProp, gc_wszProp_iana_getcontenttype))
m_ip_getcontenttype = m_cProps;
// Set the property up as one to process.
//
Assert (m_cProps < m_cMaxProps);
m_rgwszProps[m_cProps++] = AppendChainedSz (m_csb, pwszProp);
return S_OK;
}
SCODE
CFSFind::ScFind (CXMLEmitter& msr,
IMethUtil * pmu,
CFSProp& fpt)
{
SCODE scFind;
SCODE sc = S_OK;
// Setup the emitting of the response. This will construct
// an XML node that looks like:
//
// <multistatus>
// <response>
// <href>http:/www....</>
//
//
CEmitterNode enItem;
CEmitterNode en;
sc = msr.ScSetRoot (gc_wszMultiResponse);
if (FAILED (sc))
goto ret;
sc = enItem.ScConstructNode (msr, msr.PxnRoot(), gc_wszResponse);
if (FAILED (sc))
goto ret;
// If they havent asked for anything, then we should return an
// error
//
if (m_ft == FIND_NONE)
{
//$REVIEW: is it really correct to NOT add the HREF node here? --BeckyAn 6July1999
return E_DAV_EMPTY_FIND_REQUEST;
}
// If the request is an for a specific set of properties, then
// this is pretty easy...
//
else if (m_ft == FIND_SPECIFIC)
{
Assert (m_cProps);
Assert (m_rgwszProps);
sc = ScAddHref (enItem,
pmu,
fpt.PwszPath(),
fpt.FCollection(),
fpt.PcvrTranslation());
if (FAILED (sc))
goto ret;
// Get all the properties by name
//
scFind = fpt.ScGetSpecificProps (msr,
enItem,
m_cProps,
(LPCWSTR*)m_rgwszProps.get(),
m_ip_getcontenttype);
if (FAILED (scFind))
{
(void) ScAddStatus (&enItem, HscFromHresult(scFind));
goto ret;
}
}
// If the request is an for all properties or all names, then again,
// this is pretty easy...
//
else
{
Assert ((m_ft == FIND_ALL) || (m_ft == FIND_NAMES));
// Get all props or all names
//
scFind = fpt.ScGetAllProps (msr, enItem, m_ft == FIND_ALL);
if (FAILED (scFind) && (scFind != E_DAV_SMB_PROPERTY_ERROR))
{
(void) ScAddStatus (&enItem, HscFromHresult(scFind));
goto ret;
}
}
ret:
return sc;
}
// IPreloadNamespaces
//
SCODE
CFSFind::ScLoadNamespaces(CXMLEmitter * pmsr)
{
SCODE sc = S_OK;
UINT iProp;
// Load common namespaces
//
sc = pmsr->ScPreloadNamespace (gc_wszDav);
if (FAILED(sc))
goto ret;
sc = pmsr->ScPreloadNamespace (gc_wszLexType);
if (FAILED(sc))
goto ret;
sc = pmsr->ScPreloadNamespace (gc_wszXml_V);
if (FAILED(sc))
goto ret;
// Add more namespaces
switch (m_ft)
{
case FIND_SPECIFIC:
for (iProp = 0; iProp < m_cProps; iProp++)
{
sc = pmsr->ScPreloadNamespace (m_rgwszProps[iProp]);
if (FAILED(sc))
goto ret;
}
break;
case FIND_ALL:
case FIND_NAMES:
// Now that we don't have a way to predict what namespaces to
// be used.
// Per resource level namespaces will be added on <DAV:response>
// node later
break;
default:
AssertSz (FALSE, "Unknown propfind type");
// fall through
case FIND_NONE:
sc = E_DAV_EMPTY_FIND_REQUEST;
goto ret;
}
ret:
return sc;
}
// CFSPatch ------------------------------------------------------------------
//
SCODE
CFSPatch::ScDeleteProp (LPCWSTR, LPCWSTR pwszProp)
{
enum { cProps = 8 };
UINT irp;
// We cannot delete any reserved properties, so let's
// just shortcut this here and now...
//
if (CFSProp::FReservedProperty (pwszProp,
CFSProp::RESERVED_SET,
&irp))
{
// Take ownership of the bstr as well
//
return m_csn.ScAddErrorStatus (HSC_FORBIDDEN, pwszProp);
}
// Make sure there is room at the inn...
//
if (m_cMaxDeleteProps == m_cDeleteProps)
{
UINT cb;
// Allocate enough space for all the properties names
// we want to delete.
//
m_cMaxDeleteProps = m_cDeleteProps + cProps;
cb = m_cMaxDeleteProps * sizeof(BSTR);
m_rgwszDeleteProps.realloc (cb);
}
// Set the property up as one to process.
//
Assert (m_cDeleteProps < m_cMaxDeleteProps);
m_rgwszDeleteProps[m_cDeleteProps++] = AppendChainedSz(m_csb, pwszProp);
return S_OK;
}
SCODE
CFSPatch::ScSetProp (LPCWSTR,
LPCWSTR pwszProp,
auto_ref_ptr<CPropContext>& pPropCtx)
{
enum { cProps = 8 };
UINT irp;
// We cannot set any reserved properties, so let's
// just shortcut this here and now...
//
if (CFSProp::FReservedProperty (pwszProp,
CFSProp::RESERVED_SET,
&irp))
{
// Take ownership of the bstr as well
//
return m_csn.ScAddErrorStatus (HSC_FORBIDDEN, pwszProp);
}
// Make sure there is room at the inn...
//
if (m_cMaxSetProps == m_cSetProps)
{
UINT cb;
// Allocate enough space for all the properties we
// might want to set
//
m_cMaxSetProps = m_cSetProps + cProps;
cb = m_cMaxSetProps * sizeof(PROPVARIANT);
m_rgvSetProps.realloc (cb);
// Make sure the VARIANT are properly initialized
// (only initialize the newly added space).
//
ZeroMemory (&m_rgvSetProps[m_cSetProps],
sizeof(PROPVARIANT) * cProps);
// ... and their names.
//
cb = m_cMaxSetProps * sizeof(LPCWSTR);
m_rgwszSetProps.realloc (cb);
}
// Set the property up as one to process.
//
Assert (m_cSetProps < m_cMaxSetProps);
m_rgwszSetProps[m_cSetProps] = AppendChainedSz(m_csb, pwszProp);
pPropCtx = new CFSPropContext(&m_rgvSetProps[m_cSetProps]);
m_cSetProps++;
return S_OK;
}
SCODE
CFSPatch::ScPatch (CXMLEmitter& msr,
IMethUtil * pmu,
CFSProp& fpt)
{
SCODE sc = S_OK;
SCODE scSet = S_OK;
SCODE scDelete = S_OK;
CEmitterNode enItem;
// If there are no properties at all, reserved or otherwise,
// we want to fail the call with BAD_REQUEST
//
if ((m_cSetProps == 0) &&
(m_cDeleteProps == 0) &&
m_csn.FEmpty())
{
return E_DAV_EMPTY_PATCH_REQUEST;
}
// Setup the emitting of the response. This will construct
// an XML node that looks like:
//
// <multistatus>
// <response>
// <href>http:/www....</>
//
//
sc = msr.ScSetRoot (gc_wszMultiResponse);
if (FAILED (sc))
goto ret;
sc = enItem.ScConstructNode (msr, msr.PxnRoot(), gc_wszResponse);
if (FAILED (sc))
goto ret;
sc = ScAddHref (enItem,
pmu,
fpt.PwszPath(),
fpt.FCollection(),
fpt.PcvrTranslation());
if (FAILED (sc))
goto ret;
// If the client requested any of the reserved properties, we know
// that they will fail and we also know that everything else will fail
// as well, so we might as well handle that here...
//
if (!m_csn.FEmpty())
{
//$ REVIEW:
//
// If the possibly successful properties need to be
// marked as a failure as well (HSC_METHOD_FAILURE),
// then that would happen here.
//
//NT242086: Now that we've got a reponse node, we should
//added to the response.
//
sc = m_csn.ScEmitErrorStatus (enItem);
goto ret;
}
// If there are no reserved properties we have a pretty good bet
// at setting these props...
//
scSet = fpt.ScSetProps (m_csn,
m_cSetProps,
m_rgwszSetProps.get(),
m_rgvSetProps);
if (FAILED (scSet))
{
sc = scSet;
goto ret;
}
// ... and deleting these props.
//
scDelete = fpt.ScDeleteProps (m_csn,
m_cDeleteProps,
m_rgwszDeleteProps.get());
if (FAILED (scDelete))
{
sc = scDelete;
goto ret;
}
// If the possibly successful properties need to be
// marked as a failure as well (HSC_METHOD_FAILURE),
// then that would happen here. Either way, if there
// is a failure, then we do not want to commit the
// changes.
//
if ((scSet == S_FALSE) || (scDelete == S_FALSE))
goto ret;
// Commit the changes to the property container
//
sc = fpt.ScPersist();
if (FAILED (sc))
goto ret;
// Emit the response,
//
sc = m_csn.ScEmitErrorStatus (enItem);
if (FAILED(sc))
goto ret;
ret:
return sc;
}
CFSPatch::~CFSPatch()
{
// Make sure all the propvariants are cleaned up...
//
for (UINT i = 0; i < m_cSetProps; i++)
PropVariantClear (&m_rgvSetProps[i]);
}
SCODE
CFSPatch::ScLoadNamespaces (CXMLEmitter * pmsr)
{
SCODE sc = S_OK;
UINT iProp;
// Load common namespaces
//
sc = pmsr->ScPreloadNamespace (gc_wszDav);
if (FAILED(sc))
goto ret;
// Add namespaces for set props
//
for (iProp = 0; iProp < m_cSetProps; iProp++)
{
sc = pmsr->ScPreloadNamespace (m_rgwszSetProps[iProp]);
if (FAILED(sc))
goto ret;
}
// And delete props
//
for (iProp = 0; iProp < m_cDeleteProps; iProp++)
{
sc = pmsr->ScPreloadNamespace (m_rgwszDeleteProps[iProp]);
if (FAILED(sc))
goto ret;
}
ret:
return sc;
}
// CFSProp -------------------------------------------------------------------
//
SCODE
CFSProp::ScGetPropsInternal (ULONG cProps,
LPCWSTR* rgwszPropNames,
PROPVARIANT* rgvar,
LONG ip_getcontenttype)
{
SCODE sc = S_OK;
// There really should only be one scenario where this could happen
// -- and it is a cheap test, so it is worth doing. The case where
// we might see an invalid pbag is when the document extisted, but
// there was no existing property set to impose the pbag on. Other
// than that, OLE is always giving us a property bag, regardless of
// whether the target drive can support it.
//
if (FInvalidPbag())
return sc;
// We better be good to go...
//
sc = m_pbag->ReadMultiple (cProps,
rgwszPropNames,
rgvar,
NULL);
// If we succeeded, and the getcontenttype property was requested,
// we may need to do some special processing
//
if (SUCCEEDED (sc) && (ip_getcontenttype != -1))
{
// We want to make sure that getcontenttype gets filled in
//
if (rgvar[ip_getcontenttype].vt == VT_EMPTY)
{
CStackBuffer<WCHAR> pwszT;
LPWSTR pwszContentType;
UINT cch = 40;
// No content type was explicitly set in the props.
// Fetch the default based on the file extension
// (fetching from our metabase-content-type-cache).
//
do {
if (NULL == pwszT.resize(CbSizeWsz(cch)))
{
sc = E_OUTOFMEMORY;
goto ret;
}
} while (!m_pmu->FGetContentType (m_pwszURI, pwszT.get(), &cch));
// Return the mapped content type
//
rgvar[ip_getcontenttype].vt = VT_LPWSTR;
// Must use task memory, as it will be freed by PropVariantClear
//
pwszContentType = (LPWSTR) CoTaskMemAlloc (cch * sizeof(WCHAR));
if (NULL == pwszContentType)
{
MCDTrace ("Dav: MCD: CFSProp::ScGetPropsInternal() - CoTaskMemAlloc() failed to allocate %d bytes\n", cch * sizeof(WCHAR));
sc = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
goto ret;
}
rgvar[ip_getcontenttype].pwszVal = pwszContentType;
memcpy(pwszContentType, pwszT.get(), cch * sizeof(WCHAR));
// In the case where this was the only property requested, make
// sure that our return code it correct.
//
if (cProps == 1)
{
Assert (ip_getcontenttype == 0);
Assert (sc == S_FALSE);
sc = S_OK;
}
}
}
else
{
// This is the common path for when we are trying to access
// something over an SMB, but the host cannot support the
// request (it is not an NT5 NTFS machine).
//
if ((sc == STG_E_INVALIDNAME) || !FIsVolumeNTFS())
sc = E_DAV_SMB_PROPERTY_ERROR;
}
ret:
return sc;
}
BOOL
CFSProp::FReservedProperty (LPCWSTR pwszProp, RESERVED_TYPE rt, UINT* prp)
{
UINT irp;
CRCWsz wsz(pwszProp);
// Search for the property in the list of local
// properties.
//
Assert (CElems(sc_rp) == sc_crp_set_reserved);
for (irp = 0; irp < sc_crp_set_reserved; irp++)
{
// If the crc and the strings match...
//
if ((wsz.m_dwCRC == sc_rp[irp].dwCRC) &&
!wcscmp (wsz.m_pwsz, sc_rp[irp].pwsz))
{
break;
}
}
// Setup the return
//
Assert (sc_crp_set_reserved != iana_rp_content_type);
*prp = irp;
return (irp < static_cast<UINT>((rt == RESERVED_GET) ? sc_crp_get_reserved : sc_crp_set_reserved));
}
SCODE
CFSProp::ScGetReservedProp (CXMLEmitter& xml,
CEmitterNode& enParent,
UINT irp,
BOOL fGetValues)
{
CEmitterNode en;
CStackBuffer<WCHAR> wszBuf;
LARGE_INTEGER li;
LPCWSTR pwszType = NULL;
LPWSTR pwsz = NULL;
SCODE sc = S_OK;
SYSTEMTIME st;
Assert (irp <= sc_crp_get_reserved);
Assert (sc_crp_get_reserved == iana_rp_content_type);
Assert (CElems(sc_rp) == sc_crp_set_reserved);
// Only generate values if the caller wants them
//
if (fGetValues)
{
// Switch across the reserved properties generating
// a value for the property
//
switch (irp)
{
case iana_rp_etag:
Assert(m_pmu);
if (FETagFromFiletime (m_cri.PftLastModified(), wszBuf.get(), m_pmu->GetEcb()))
{
pwsz = wszBuf.get();
}
break;
case iana_rp_displayname:
// The filename/displayname is simply the name of the file
// and we should be able to pick it off from the path with
// little and/or no trouble at all. However, we will use
// the URI instead. We do this such that the displayname
// for a vroot is the name of the vroot and not the name of
// the physical disk directory.
//
pwsz = wcsrchr (m_pwszURI, L'/');
if (NULL == pwsz)
{
// Arrgh. If there was no path separator in the filename
// I don't know that we can really give a reasonable value
// for this file.
//
TrapSz ("resource path has no slashes....");
return S_FALSE;
}
// One more check. If this is a directory path,
// they might have a trailing slash. If that is what we're
// pointing to right now (next char is NULL), back up to the
// next delimiter to get the real item name.
//
if (L'\0' == pwsz[1])
{
// This better be a collection. Although it may not
// be if the client mis-terminated his/her url
//
// There is a special case that we need to check for
// here. It is possible that the URI was strictly "/"
// which means that if we continue this processing, the
// displayname and/or the filename would be empty or
// non-existant. In this case only, return "/" as the
// display name.
//
if (m_pwszURI == pwsz)
{
pwsz = L"/";
}
else
{
// Now we have to copy the string, to rip off that
// trailing slash we found in the step above.
// We want to remove the final slash because this is the
// displayname, not a URI.
//
LPCWSTR pwszEnd;
UINT cchNew;
for (pwszEnd = pwsz--; pwsz > m_pwszURI; pwsz--)
if (L'/' == *pwsz)
break;
if (L'/' != *pwsz)
{
// Arrgh. If there was no path separator in the
// filename I don't know that we can really give
// a reasonable value for this file.
//
TrapSz ("resource path has no slashes (redux)....");
return S_FALSE;
}
// At this point, the segment defined by (pwsz + 1, pwszEnd)
// names the resource.
//
cchNew = static_cast<UINT>(pwszEnd - ++pwsz);
if (NULL == wszBuf.resize(CbSizeWsz(cchNew)))
{
sc = E_OUTOFMEMORY;
goto ret;
}
memcpy(wszBuf.get(), pwsz, cchNew * sizeof(WCHAR));
wszBuf[cchNew] = L'\0';
pwsz = wszBuf.get();
}
}
else
{
// At this point, the segment defined by (pwsz + 1, '\0'] names
// the resource.
//
pwsz++;
}
break;
case iana_rp_resourcetype:
// Create the element to pass back
//
sc = en.ScConstructNode (xml, enParent.Pxn(), sc_rp[irp].pwsz);
if (FAILED (sc))
goto ret;
if (m_cri.FCollection())
{
CEmitterNode enSub;
sc = en.ScAddNode (gc_wszCollection, enSub);
if (FAILED (sc))
goto ret;
}
goto ret;
case iana_rp_content_length:
m_cri.FileSize(li);
pwszType = gc_wszDavType_Int;
//$ REVIEW: negative values of _int64 seem to have problems in
// the __i64tow() API. Handle those cases ourselves.
//
// In this instance, we shouldn't have to worry about it because
// the content-length *shouldn't* ever be negative. We'll assert
// that this is the case.
//
Assert (li.QuadPart >= 0);
_i64tow (li.QuadPart, wszBuf.get(), 10);
pwsz = wszBuf.get();
break;
case iana_rp_creation_date:
FileTimeToSystemTime (m_cri.PftCreation(), &st);
if (FGetDateIso8601FromSystime (&st, wszBuf.get(), wszBuf.size()))
{
pwszType = gc_wszDavType_Date_ISO8601;
pwsz = wszBuf.get();
}
break;
case iana_rp_last_modified:
FileTimeToSystemTime (m_cri.PftLastModified(), &st);
if (FGetDateRfc1123FromSystime (&st, wszBuf.get(), wszBuf.size()))
{
pwszType = gc_wszDavType_Date_Rfc1123;
pwsz = wszBuf.get();
}
break;
case iana_rp_supportedlock:
case iana_rp_lockdiscovery:
// Get the prop from the lock cache (and related subsystem calls).
//
sc = HrGetLockProp (m_pmu,
sc_rp[irp].pwsz,
m_pwszPath,
m_cri.FCollection() ? RT_COLLECTION : RT_DOCUMENT,
xml,
enParent);
// Regardless of error or success, we are done here. If we
// succeeded, then the pel has already been constructed and
// is ready to pass back. Otherwise, we just want to report
// the error.
//
goto ret;
case iana_rp_ishidden:
pwszType = gc_wszDavType_Boolean;
_itow (!!m_cri.FHidden(), wszBuf.get(), 10);
pwsz = wszBuf.get();
break;
case iana_rp_iscollection:
pwszType = gc_wszDavType_Boolean;
_itow (!!m_cri.FCollection(), wszBuf.get(), 10);
pwsz = wszBuf.get();
break;
// Special case: getcontenttype should really be stored, but there
// are some cases where the file may live in such a place as there
// would be no property stream available to store the value in.
//
case iana_rp_content_type:
// Get the content-type if it was not stored in the property
// stream.
//
for (UINT cch = wszBuf.celems();;)
{
if (NULL == wszBuf.resize(CbSizeWsz(cch)))
{
sc = E_OUTOFMEMORY;
goto ret;
}
if (m_pmu->FGetContentType(m_pwszURI, wszBuf.get(), &cch))
break;
}
}
}
// Create the element to pass back
//
sc = en.ScConstructNode (xml, enParent.Pxn(), sc_rp[irp].pwsz, pwsz, pwszType);
if (FAILED (sc))
goto ret;
ret:
return sc;
}
SCODE
CFSProp::ScGetSpecificProps (CXMLEmitter& msr,
CEmitterNode& enItem,
ULONG cProps,
LPCWSTR* rgwszPropNames,
LONG ip_getcontenttype)
{
// safe_propvariant_array ----------------------------------------------------
//
// Used to make sure the array of VARIANT can always be safely freed
//
class safe_propvariant_array
{
PROPVARIANT * m_rgv;
ULONG m_cv;
public:
safe_propvariant_array (PROPVARIANT* rgv, ULONG cv)
: m_rgv(rgv),
m_cv(cv)
{
memset (rgv, 0, sizeof(PROPVARIANT) * cv);
}
~safe_propvariant_array ()
{
ULONG i;
for (i = 0; i < m_cv; i++)
PropVariantClear(&m_rgv[i]);
}
};
SCODE sc = S_OK;
CStackBuffer<PROPVARIANT> rgv;
UINT iv;
CStatusCache csn;
CEmitterNode enPropStat;
CEmitterNode enPropOK;
// allocate space to hold an array of variants and stuff it into
// a safe_variant_array to ensure cleanup
//
rgv.resize(sizeof(PROPVARIANT) * cProps);
safe_propvariant_array sva(rgv.get(), cProps);
sc = csn.ScInit();
if (FAILED(sc))
goto ret;
// Get the properties
//
sc = ScGetPropsInternal (cProps, rgwszPropNames, rgv.get(), ip_getcontenttype);
if (FAILED(sc))
{
// When getting properties, it is perfectly OK to ignore SMB errors
// and treat the file as if it were hosted on a FAT drive
//
if (sc == E_DAV_SMB_PROPERTY_ERROR)
sc = S_OK;
// What this means is that the default not-found processing should
// kick in.
//
}
// Rip through the returned properties, adding to the response as we go
//
for (iv = 0; iv < cProps; iv++)
{
// If there is a value to the property, write the variant as
// an XML element and add it to the response
//
if (rgv[iv].vt != VT_EMPTY)
{
if (!enPropOK.Pxn())
{
// Get the insert point for props
//
sc = ScGetPropNode (enItem, HSC_OK, enPropStat, enPropOK);
if (FAILED(sc))
goto ret;
}
// Write the variant as an XML element
//
sc = ScEmitFromVariant (msr,
enPropOK,
rgwszPropNames[iv],
rgv[iv]);
if (FAILED (sc))
goto ret;
}
else
{
UINT irp;
// Check if it's a reserved property
//
if (FReservedProperty (rgwszPropNames[iv], RESERVED_GET, &irp) ||
(irp == iana_rp_content_type))
{
if (!enPropOK.Pxn())
{
// Get the insert point for props
//
sc = ScGetPropNode (enItem, HSC_OK, enPropStat, enPropOK);
if (FAILED(sc))
goto ret;
}
// If the property was reserved, then extract it from
// the property class directly
//
sc = ScGetReservedProp (msr, enPropOK, irp);
if (FAILED (sc))
goto ret;
continue;
}
// Now, if we got here, then for CFSProp, the property
// must not have existed.
//
sc = csn.ScAddErrorStatus (HSC_NOT_FOUND, rgwszPropNames[iv]);
if (FAILED(sc))
goto ret;
}
}
// Need to close the previous prop stat before more status node to be emitted
//
if (!csn.FEmpty())
{
// The order is important, inner node must be closed first
//
sc = enPropOK.ScDone();
if (FAILED(sc))
goto ret;
sc = enPropStat.ScDone();
if (FAILED(sc))
goto ret;
sc = csn.ScEmitErrorStatus (enItem);
if (FAILED(sc))
goto ret;
}
ret:
return sc;
}
SCODE
CFSProp::ScGetAllProps (CXMLEmitter& msr,
CEmitterNode& enItem,
BOOL fFindValues)
{
auto_com_ptr<IEnumSTATPROPBAG> penum;
BOOL fContentType = FALSE;
BOOL fHrefAdded = FALSE;
SCODE sc = S_OK;
UINT irp;
CEmitterNode enPropStat;
CEmitterNode enProp;
// There really should only be one scenario where this could happen
// -- and it is a cheap test, so it is worth doing. The case where
// we might see an invalid pbag is when the document extisted, but
// there was no existing property set to impose the pbag on. Other
// than that, OLE is always giving us a property bag, regardless of
// whether the target drive can support it.
//
if (!FInvalidPbag())
{
sc = m_pbag->Enum (NULL, 0, &penum);
if (FAILED(sc))
{
// AddHref was delayed to be done after local namespace is loaded
// but in this case, we know there'll be no local namespaces at all.
// so add href now
//
(void) ScAddHref (enItem,
m_pmu,
PwszPath(),
FCollection(),
PcvrTranslation());
if ((sc == STG_E_INVALIDNAME) || !FIsVolumeNTFS())
{
// This is the common path for when we are trying to access
// something over an SMB, but the host cannot support the
// request (it is not an NT5 NTFS machine). We want to treat
// this as if the operation was against a FAT drive
//
sc = E_DAV_SMB_PROPERTY_ERROR;
goto get_reserved;
}
goto ret;
}
// We must preload all the potential namespaces in the <response> node,
// Note that the namespace for all reserved properties is "DAV:", which
// has been added already in CFSFind::ScLoadNamespace()
//
do
{
safe_statpropbag ssp[PROP_CHUNK_SIZE];
ULONG csp = 0;
UINT isp;
// Get next chunk of props
//
sc = penum->Next (PROP_CHUNK_SIZE, ssp[0].load(), &csp);
if (FAILED(sc))
goto ret;
// At this point, we either want to call the underlying
// property container to retrieve all the property data
// or we just want to emit the names.
//
for (isp = 0; isp < csp; isp++)
{
Assert (ssp[isp].get().lpwstrName);
sc = msr.ScPreloadLocalNamespace (enItem.Pxn(), ssp[isp].get().lpwstrName);
if (FAILED(sc))
goto ret;
}
} while (sc != S_FALSE);
// Addhref must be done after all the local nmespaces has been emitted
//
sc = ScAddHref (enItem,
m_pmu,
PwszPath(),
FCollection(),
PcvrTranslation());
if (FAILED (sc))
goto ret;
fHrefAdded = TRUE;
// Reset the enumerator back to the beginning
//
sc = penum->Reset();
if (FAILED(sc))
goto ret;
// Get the insert point for props
//
sc = ScGetPropNode (enItem, HSC_OK, enPropStat, enProp);
if (FAILED(sc))
goto ret;
// Enumerate the props and emit
//
do
{
safe_statpropbag ssp[PROP_CHUNK_SIZE];
safe_propvariant propvar[PROP_CHUNK_SIZE];
LPWSTR rglpwstr[PROP_CHUNK_SIZE] = {0};
ULONG csp = 0;
UINT isp;
// Get next chunk of props
//
sc = penum->Next (PROP_CHUNK_SIZE, ssp[0].load(), &csp);
if (FAILED(sc))
goto ret;
// At this point, we either want to call the underlying
// property container to retrieve all the property data
// or we just want to emit the names.
//
for (isp = 0; isp < csp; isp++)
{
Assert (ssp[isp].get().lpwstrName);
// We need to track whether or not the getcontenttype
// property was actually stored or not. If it wasn't,
// then, we will want to default it at a later time.
//
if (!fContentType)
{
if (!wcscmp (ssp[isp].get().lpwstrName,
gc_wszProp_iana_getcontenttype))
{
// Note that content-type is included
//
fContentType = TRUE;
}
}
// If we are just asking for names, then add the
// name to the list now...
//
if (!fFindValues)
{
CEmitterNode en;
// Add the result to the response
//
sc = enProp.ScAddNode (ssp[isp].get().lpwstrName, en);
if (FAILED (sc))
goto ret;
}
else
rglpwstr[isp] = ssp[isp].get().lpwstrName;
}
// If we are just asking about names, then we really
// are done with this group of properties, otherwise
// we need to generate the values and emit them.
//
if (!fFindValues)
continue;
// Read properties in chunk
//
if (csp)
{
sc = m_pbag->ReadMultiple (csp,
rglpwstr,
propvar[0].addressof(),
NULL);
if (FAILED (sc))
goto ret;
}
// Emit properties
//
for (isp = 0; isp < csp; isp++)
{
// Contstruct the pel from the variant
//
sc = ScEmitFromVariant (msr,
enProp,
ssp[isp].get().lpwstrName,
const_cast<PROPVARIANT&>(propvar[isp].get()));
if (FAILED (sc))
goto ret;
}
} while (sc != S_FALSE);
}
get_reserved:
// Render all the reserved properties, this relies on the fact that
// the first non-GET reserved property is "DAV:getcontenttype".
//
Assert (iana_rp_content_type == sc_crp_get_reserved);
if (!fHrefAdded)
{
// Need to build the HREF node because it wasn't built above.
// This can happen when we don't have a pbag (like on FAT16).
//
sc = ScAddHref (enItem,
m_pmu,
PwszPath(),
FCollection(),
PcvrTranslation());
if (FAILED (sc))
goto ret;
}
if (!enProp.Pxn())
{
// Get the insert point for props
//
sc = ScGetPropNode (enItem, HSC_OK, enPropStat, enProp);
if (FAILED(sc))
goto ret;
}
for (irp = 0; irp <= sc_crp_get_reserved; irp++)
{
// If the content-type has already been processed, then
// don't do it here.
//
if ((irp == sc_crp_get_reserved) && fContentType)
break;
// Construct the pel from the reserved property
//
sc = ScGetReservedProp (msr, enProp, irp, fFindValues);
if (FAILED (sc))
goto ret;
}
// We are done with all the local namespaces
//
msr.DoneWithLocalNamespace();
ret:
return sc;
}
SCODE
CFSProp::ScSetProps (CStatusCache& csn,
ULONG cProps,
LPCWSTR* rgwszProps,
PROPVARIANT* rgvProps)
{
UINT ip;
SCODE sc = S_OK;
ULONG hsc;
// Zero props is a no-op
//
if (!cProps)
return S_OK;
Assert (!FInvalidPbag());
sc = m_pbag->WriteMultiple (cProps, rgwszProps, rgvProps);
if (FAILED(sc))
{
// This is the common path for when we are trying to access
// something over an SMB, but the host cannot support the
// request (it is not an NT5 NTFS machine).
//
if ((sc == STG_E_INVALIDNAME) || !FIsVolumeNTFS())
return E_DAV_SMB_PROPERTY_ERROR;
}
// we don't know exactly which prop failed,
// return same error for all props
//
hsc = HscFromHresult(sc);
for (ip = 0; ip < cProps; ip++)
{
sc = csn.ScAddErrorStatus (hsc, rgwszProps[ip]);
if (FAILED(sc))
goto ret;
}
ret:
return FAILED(sc) ? S_FALSE : S_OK;
}
SCODE
CFSProp::ScDeleteProps (CStatusCache& csn,
ULONG cProps,
LPCWSTR* rgwszProps)
{
UINT ip;
SCODE sc = S_OK;
ULONG hsc;
// Zero props is a no-op
//
if (!cProps)
return S_OK;
Assert (!FInvalidPbag());
sc = m_pbag->DeleteMultiple (cProps, rgwszProps, 0);
if (FAILED(sc))
{
// This is the common path for when we are trying to access
// something over an SMB, but the host cannot support the
// request (it is not an NT5 NTFS machine).
//
if ((sc == STG_E_INVALIDNAME) || !FIsVolumeNTFS())
return E_DAV_SMB_PROPERTY_ERROR;
}
// we don't know exactly which prop failed,
// return same error for all props
//
hsc = HscFromHresult(sc);
for (ip = 0; ip < cProps; ip++)
{
sc = csn.ScAddErrorStatus (hsc, rgwszProps[ip]);
if (FAILED(sc))
goto ret;
}
ret:
return FAILED(sc) ? S_FALSE : S_OK;
}
SCODE
CFSProp::ScPersist ()
{
// We are not transacted now, just
//
return S_OK;
}
// Content properties --------------------------------------------------------
//
SCODE
ScSetContentProperties (IMethUtil * pmu, LPCWSTR pwszPath, HANDLE hFile)
{
LPCWSTR pwszContentType;
LPCWSTR pwszContentLanguage;
LPCWSTR pwszContentEncoding;
SCODE sc = S_OK;
// Figure out which content properties we have
//
pwszContentType = pmu->LpwszGetRequestHeader (gc_szContent_Type, FALSE);
pwszContentLanguage = pmu->LpwszGetRequestHeader (gc_szContent_Language, FALSE);
pwszContentEncoding = pmu->LpwszGetRequestHeader (gc_szContent_Encoding, FALSE);
// Content-Type is special -- it is always set in the metabase.
// It should be set *before* setting any properties in the property bag
// since it's OK for the property bag stuff to fail.
//
if (NULL != pwszContentType)
{
// Setting the content-type will not work if the metabase is read only,
// which is exactly what is happening in .NET server. So we need to
// ignore the error -- if any.
//
(void) pmu->ScSetContentType (pmu->LpwszRequestUrl(), pwszContentType);
}
// Set any content properties we have in the property bag
//
if (pwszContentLanguage || pwszContentEncoding)
{
auto_com_ptr<IPropertyBagEx> pbe;
CStackBuffer<WCHAR> pwsz;
// Try to open the property bag. If this fails because we're not
// on an NTFS filesystem, that's OK. We just won't set the properties
// there.
//
// We need to open propertybag by handle as it the main stream might
// be locked.
//
sc = ScGetPropertyBag (pwszPath,
STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
&pbe,
FALSE, // not a collection
hFile);
if (FAILED(sc))
{
//$ REVIEW:
//
// We did our best here. For DocFiles this will fail because
// of how we have to open the files. What this means is that we
// could potentially lose the content-encoding and content-language
// which would put us on par with IIS (they don't store these either).
//
sc = S_OK;
goto ret;
}
CResourceInfo cri;
CFSProp xpt(pmu,
pbe,
pmu->LpwszRequestUrl(),
pwszPath,
NULL,
cri);
// Content-Type
//
if (NULL != pwszContentType)
{
sc = xpt.ScSetStringProp (sc_rp[iana_rp_content_type].pwsz, pwszContentType);
if (FAILED (sc))
goto ret;
}
// Content-Language
//
if (NULL != pwszContentLanguage)
{
sc = xpt.ScSetStringProp (sc_rp[iana_rp_content_language].pwsz, pwszContentLanguage);
if (FAILED (sc))
goto ret;
}
// Content-Encoding
//
if (NULL != pwszContentEncoding)
{
sc = xpt.ScSetStringProp (sc_rp[iana_rp_content_encoding].pwsz, pwszContentEncoding);
if (FAILED (sc))
goto ret;
}
// Persist the changes
//
sc = xpt.ScPersist();
if (FAILED(sc))
goto ret;
}
ret:
// It is perfectly OK to ignore SMB errors when setting content properties.
//
if (sc == E_DAV_SMB_PROPERTY_ERROR)
sc = S_OK;
return sc;
}
// ScFindFileProps -----------------------------------------------------------
//
SCODE
ScFindFileProps (IMethUtil* pmu,
CFSFind& cfc,
CXMLEmitter& msr,
LPCWSTR pwszUri,
LPCWSTR pwszPath,
CVRoot* pcvrTranslation,
CResourceInfo& cri,
BOOL fEmbedErrorsInResponse)
{
auto_com_ptr<IPropertyBagEx> pbag;
CFSProp fsp(pmu, pbag, pwszUri, pwszPath, pcvrTranslation, cri);
SCODE sc = S_OK;
// Check access permission
//
sc = pmu->ScCheckMoveCopyDeleteAccess (pwszUri,
pcvrTranslation,
cri.FCollection(),
FALSE, // do not check against scriptmaps
MD_ACCESS_READ);
if (FAILED (sc))
{
// No permission to read, we certainly do not want
// to try and traverse down into the directory (if
// it was one), we do this by returning S_FALSE.
//
if (fEmbedErrorsInResponse)
{
sc = cfc.ScErrorAllProps (msr,
pmu,
pwszPath,
cri.FCollection(),
pcvrTranslation,
sc);
if (FAILED (sc))
goto ret;
// Pass back S_FALSE so that no further traversal of
// this resource is performed
//
sc = S_FALSE;
}
if (S_OK != sc)
goto ret;
}
// Don't get pbag for remote files. This would cause the files to
// be recalled, etc.
//
if (!cri.FRemote())
{
// Get the IPropertyBagEx interface
//
// Before call into this function, we've checked we have read access to
// this file. so we should always be able to read the proerties however,
// if the file is write locked, there may be some problems from the OLE
// properties code.
//
sc = ScGetPropertyBag (pwszPath,
STGM_READ | STGM_SHARE_DENY_WRITE,
&pbag,
cri.FCollection());
if (FAILED (sc))
{
// We need to check the volume of the file we are trying
// to read.
//
if (VOLTYPE_NTFS == VolumeType (pwszPath, pmu->HitUser()))
{
// Report the errors for this file and come on back...
//
if (fEmbedErrorsInResponse)
{
sc = cfc.ScErrorAllProps (msr,
pmu,
pwszPath,
cri.FCollection(),
pcvrTranslation,
sc);
}
goto ret;
}
}
}
// Find the properties
//
sc = cfc.ScFind (msr, pmu, fsp);
if (FAILED (sc))
goto ret;
ret:
return sc;
}
SCODE
ScFindFilePropsDeep (IMethUtil* pmu,
CFSFind& cfc,
CXMLEmitter& msr,
LPCWSTR pwszUri,
LPCWSTR pwszPath,
CVRoot* pcvrTranslation,
LONG lDepth)
{
BOOL fSubDirectoryAccess = TRUE;
SCODE sc = S_OK;
// Query subdirs when do deep query
//
Assert ((lDepth == DEPTH_ONE) ||
(lDepth == DEPTH_ONE_NOROOT) ||
(lDepth == DEPTH_INFINITY));
CDirIter di(pwszUri,
pwszPath,
NULL, // no destination url
NULL, // no destination path
NULL, // no destination translation
lDepth == DEPTH_INFINITY);
while (S_OK == (sc = di.ScGetNext (fSubDirectoryAccess)))
{
CResourceInfo cri;
// If we found another directory, then iterate on it
//
fSubDirectoryAccess = FALSE;
if (di.FDirectory())
{
auto_ref_ptr<CVRoot> arp;
// Skip the special and/or hidden directories
//
if (di.FSpecial())
continue;
// If we happen to traverse into a directory
// that happens to be a vroot (as identified
// by url), then there is another entry in
// the list of child vroots that will refer
// to this directory. Let that processing
// handle this directory instead of the
// doing it here.
//
// This means that the file hierarchy is not
// strictly preserved, but I think that this
// is OK.
//
if (pmu->FFindVRootFromUrl (di.PwszUri(), arp))
continue;
// Check the directory browsing bit and see
// if it is enabled. And only progess down
// if it is set.
//
{
auto_ref_ptr<IMDData> pMDData;
if (SUCCEEDED(pmu->HrMDGetData (di.PwszUri(), pMDData.load())) &&
(pMDData->DwDirBrowsing() & MD_DIRBROW_ENABLED))
{
// Prepare to go into the subdir
//
fSubDirectoryAccess = TRUE;
}
}
}
// Find the properties for the resource
//
*cri.PfdLoad() = di.FindData();
sc = ScFindFileProps (pmu,
cfc,
msr,
di.PwszUri(),
di.PwszSource(),
pcvrTranslation,
cri,
TRUE /*fEmbedErrorsInResponse*/);
if (FAILED (sc))
goto ret;
// S_FALSE is a special return code that
// means we did not have access to read the
// resource...
//
if (sc == S_FALSE)
{
// ... and since we really didn't have access,
// we don't want to delve into the children of
// the resource.
//
fSubDirectoryAccess = FALSE;
}
}
ret:
return sc;
}
// ScCopyProps ---------------------------------------------------------------
//
/*
* ScCopyProps()
*
* Purpose:
*
* Copies the properties from one resource to another. This is
* really only useful for copying full directories. Standard file
* copies do the dirty work for us, but for directories, we need to
* do it ourselves.
* If we don't find any propstream on the source, we DELETE
* any propstream on the destination.
*/
SCODE
ScCopyProps (IMethUtil* pmu, LPCWSTR pwszSrc, LPCWSTR pwszDst,
BOOL fCollection, HANDLE hSource, HANDLE hDest)
{
enum { CHUNK_SIZE = 16 };
auto_com_ptr<IPropertyBagEx> pbeSrc;
auto_com_ptr<IPropertyBagEx> pbeDst;
auto_com_ptr<IEnumSTATPROPBAG> penumSrc;
auto_com_ptr<IEnumSTATPROPBAG> penumDst;
SCODE sc;
SCODE scEnum;
ULONG cProp;
MCDTrace ("Dav: MCD: copying props manually: %ws -> %ws\n", pwszSrc, pwszDst);
// Get the IPropertyBagEx on the source
//
sc = ScGetPropertyBag (pwszSrc,
STGM_READ | STGM_SHARE_DENY_WRITE,
&pbeSrc,
fCollection,
hSource);
if (sc != S_OK)
goto ret;
MCDTrace ("Dav: MCD: opened source property bag: %ws\n", pwszSrc);
// Get the IPropertyBagEx on the destination
//
sc = ScGetPropertyBag (pwszDst,
STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
&pbeDst,
fCollection,
hDest);
if (FAILED(sc))
goto ret;
MCDTrace ("Dav: MCD: opened destination property bag: %ws\n", pwszDst);
// Get the IEnumSTATPROPBAG interface on source
//
sc = pbeSrc->Enum (NULL, 0, &penumSrc);
if (FAILED(sc))
goto ret;
// Get the IEnumSTATPROPBAG interface on destination
//
sc = pbeDst->Enum (NULL, 0, &penumDst);
if (FAILED(sc))
goto ret;
// Delete all props from destination if there's any
//$ COME BACK
//$ Instead of delete props one by one, we can just delete the
// prop stream.
//
for (;;)
{
safe_statpropbag ssp[CHUNK_SIZE];
safe_propvariant propvar[CHUNK_SIZE];
ULONG csp = 0;
// Get next chunk of props
//
Assert (sizeof(safe_statpropbag) == sizeof(STATPROPBAG));
scEnum = penumDst->Next(CHUNK_SIZE,
reinterpret_cast<STATPROPBAG *>(&ssp[0]),
&csp);
if (FAILED(scEnum))
{
sc = scEnum;
goto ret;
}
MCDTrace ("Dav: MCD: copying %ld props\n", csp);
// Delete one by one
//
for (cProp = 0; cProp < csp; cProp++)
{
Assert (ssp[cProp].get().lpwstrName);
// Write to the destination
//
LPCWSTR pwsz = ssp[cProp].get().lpwstrName;
sc = pbeDst->DeleteMultiple (1, &pwsz, 0);
if (FAILED(sc))
goto ret;
}
if (scEnum == S_FALSE)
break;
}
// Enumerate the props and emit
//
for (;;)
{
safe_statpropbag ssp[CHUNK_SIZE];
safe_propvariant propvar[CHUNK_SIZE];
LPWSTR rglpwstr[CHUNK_SIZE] = {0};
ULONG csp = 0;
// Get next chunk of props
//
Assert (sizeof(safe_statpropbag) == sizeof(STATPROPBAG));
scEnum = penumSrc->Next (CHUNK_SIZE,
reinterpret_cast<STATPROPBAG *>(&ssp[0]),
&csp);
if (FAILED(scEnum))
{
sc = scEnum;
goto ret;
}
// Prepare to call read multiple props
//
for (cProp=0; cProp<csp; cProp++)
{
Assert (ssp[cProp].get().lpwstrName);
rglpwstr[cProp] = ssp[cProp].get().lpwstrName;
}
if (csp)
{
// Read properties in chunk from source
//
sc = pbeSrc->ReadMultiple (csp, rglpwstr, &propvar[0], NULL);
if (FAILED(sc))
goto ret;
// Write to the destination
//
sc = pbeDst->WriteMultiple (csp, rglpwstr, propvar[0].addressof());
if (FAILED(sc))
goto ret;
}
if (scEnum == S_FALSE)
break;
}
ret:
// Copying properties is a harmless failure that
// we should feel free to ignore if we are not on
// an NFTS volume
//
if (FAILED(sc))
{
if ((sc == STG_E_INVALIDNAME) ||
VOLTYPE_NTFS != VolumeType (pwszSrc, pmu->HitUser()) ||
VOLTYPE_NTFS != VolumeType (pwszDst, pmu->HitUser()))
{
// This is the common path for when we are trying to access
// something over an SMB, but the host cannot support the
// request (it is not an NT5 NTFS machine).
//
sc = S_OK;
}
}
return sc;
}
// OLE 32 IPropertyBagEx Access ----------------------------------------------
//
// StgOpenStorageOnHandle() and StgCreateStorageOnHandle() are implemented
// in OLE32.DLL but not exported. We must load the library and get the proc
// instances ourselves. We wrap the calls to these functions with this small
// wrapper such that we can catch when the API changes.
//
STDAPI
StgOpenStorageOnHandle (
IN HANDLE hStream,
IN DWORD grfMode,
IN void *reserved1,
IN void *reserved2,
IN REFIID riid,
OUT void **ppObjectOpen )
{
Assert (g_pfnStgOpenStorageOnHandle);
// Yes, we've asserted.
// However, if it does happen, we don't want to fail and we can
// just treat this like we are on a FAT. (i.e. no property support)
//
if (!g_pfnStgOpenStorageOnHandle)
return E_DAV_SMB_PROPERTY_ERROR;
return (*g_pfnStgOpenStorageOnHandle) (hStream,
grfMode,
reserved1,
reserved2,
riid,
ppObjectOpen);
}
// ScGetPropertyBag() --------------------------------------------------------
//
// Helper function used to get IPropertyBagEx interface. The important
// thing to know about this function is that there are three interesting
// return values:
//
// S_OK means everything was OK, and there should be a
// propertybag associated with the file in the out param.
//
// S_FALSE means that the file did not exist. There will
// not be an associated property bag in that scenario.
//
// FAILED(sc) means that there was a failure of some sort,
// not all of which are fatal. In many cases, we will simply
// treat the file as if it was hosted on a FAT file system.
//
SCODE
ScGetPropertyBag (LPCWSTR pwszPath,
DWORD dwAccessDesired,
IPropertyBagEx** ppbe,
BOOL fCollection,
HANDLE hLockFile)
{
SCODE sc = S_OK;
auto_handle<HANDLE> hAlt;
// READ!!
//
// The storage of property bag is different between docfile and flat file,
// In a flat file, the property bag is stored in an alternative file stream,
// (currently, ":Docf_\005Bagaaqy23kudbhchAaq5u2chNd"), in a docfile, the
// property bag is stored as a substream under the root storage.
//
// We should not be concerned with where the pbag is stored. The API with
// which we implement our IPropertyBagEx access is designed to have the
// behavior of...
//
// We pass in a handle to the file that we want to get a property bag
// on. If the file is a docfile, then OLE32 will dup the file handle
// and impose a IPropertyBagEx on the appropriate substorage. If the
// file is a flat file -- directories included -- then OLE32 opens up
// a handle on the alternate file stream relative to the handle given
// in the call.
//
// These are the only two combinations allowed, and we rely on this in the
// following flag checks.
//
Assert ((dwAccessDesired == (STGM_READWRITE | STGM_SHARE_EXCLUSIVE)) ||
(dwAccessDesired == (STGM_READ | STGM_SHARE_DENY_WRITE)));
if (hLockFile == INVALID_HANDLE_VALUE)
{
ULONG dwShare = 0;
ULONG dwAccess;
ULONG dwOpen;
ULONG dwFile;
//$ REVIEW: Directories are special critters and we need to
// open the directory with special access as not to conflict
// with IIS and/or ASP and their directory change notification
// stuff
//
if (fCollection)
{
dwAccess = 1; // FILE_LIST_DIRECTORY
dwShare = FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE;
dwOpen = OPEN_EXISTING;
// The FILE_FLAG_BACKUP_SEMANTICS is used to open a directory handle
//
dwFile = FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED;
}
else
{
// Adjust access/open mode based on desired operation
//
dwAccess = GENERIC_READ;
dwFile = FILE_ATTRIBUTE_NORMAL;
if (dwAccessDesired & STGM_READWRITE)
{
dwAccess |= GENERIC_WRITE;
dwOpen = OPEN_ALWAYS;
}
else
dwOpen = OPEN_EXISTING;
// Adjust the sharing modes as well
//
if (dwAccessDesired & STGM_SHARE_DENY_WRITE)
dwShare = FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE;
}
hAlt = DavCreateFile (pwszPath,
dwAccess,
dwShare,
NULL,
dwOpen,
dwFile,
NULL);
if (INVALID_HANDLE_VALUE == hAlt.get())
{
DWORD dwErr = GetLastError();
// When open the property bag for read (PROPFIND), ERROR_FILE/PATH_NOT_FOUND
// could be returned if the file does not exists.
//
// When open the property bag for write (PROPPATCH), ERROR_FILE/PATH_NOT_FOUND
// Could still be returned if the parent of the path does not exists
// (Such as c:\x\y\z and \x\y does not exist).
//
// We need to differenciate the above two cases, when reading a file,
// it's not a fatal error as you could still try to read reserved
// properties, when write, we should treat this as a fatal error.
//
if ((dwErr == ERROR_FILE_NOT_FOUND) || (dwErr == ERROR_PATH_NOT_FOUND))
{
// It's not a fatal error when read
//
if (dwAccessDesired == (STGM_READ|STGM_SHARE_DENY_WRITE))
sc = S_FALSE;
else
{
// This is consistent with Mkcol, it will be mapped to 409
//
Assert (dwAccessDesired == (STGM_READWRITE|STGM_SHARE_EXCLUSIVE));
sc = E_DAV_NONEXISTING_PARENT;
}
}
else
sc = HRESULT_FROM_WIN32 (dwErr);
goto ret;
}
// Setup the handle to use
//
hLockFile = hAlt.get();
}
// Try to open the propertybag.
//
Assert (hLockFile != 0);
Assert (hLockFile != INVALID_HANDLE_VALUE);
sc = StgOpenStorageOnHandle (hLockFile,
dwAccessDesired,
NULL,
NULL,
IID_IPropertyBagEx,
reinterpret_cast<LPVOID *>(ppbe));
if (FAILED (sc))
{
goto ret;
}
//$ WARNING
//
// Argh! The current implementation of OLE32 returns a non-failure
// with a NULL property bag!
//
if (*ppbe == NULL)
{
DebugTrace ("WARNING! OLE32 returned success w/NULL object!\n");
sc = E_DAV_SMB_PROPERTY_ERROR;
}
ret:
return sc;
}
// DAV-Properties Implementation ---------------------------------------------------
//
// CPropFindRequest ----------------------------------------------------------------
//
class CPropFindRequest :
public CMTRefCounted,
private IAsyncIStreamObserver
{
//
// Reference to the CMethUtil
//
auto_ref_ptr<CMethUtil> m_pmu;
//
// Translated URI path
//
LPCWSTR m_pwszPath;
// Resource info
//
CResourceInfo m_cri;
// Depth
//
LONG m_lDepth;
// Contexts
//
CFSFind m_cfc;
auto_ref_ptr<CNFFind> m_pcpf;
// Request body as an IStream. This stream is async -- it can
// return E_PENDING from Read() calls.
//
auto_ref_ptr<IStream> m_pstmRequest;
// The XML parser used to parse the request body using
// the node factory above.
//
auto_ref_ptr<IXMLParser> m_pxprs;
// IAsyncIStreamObserver
//
VOID AsyncIOComplete();
// State functions
//
VOID ParseBody();
VOID DoFind();
VOID SendResponse( SCODE sc );
// NOT IMPLEMENTED
//
CPropFindRequest (const CPropFindRequest&);
CPropFindRequest& operator= (const CPropFindRequest&);
public:
// CREATORS
//
CPropFindRequest(LPMETHUTIL pmu) :
m_pmu(pmu),
m_pwszPath(m_pmu->LpwszPathTranslated()),
m_lDepth(DEPTH_INFINITY)
{
}
// MANIPULATORS
//
VOID Execute();
};
VOID
CPropFindRequest::Execute()
{
auto_ref_handle hf;
LPCWSTR pwsz;
SCODE sc = S_OK;
//
// First off, tell the pmu that we want to defer the response.
// Even if we send it synchronously (i.e. due to an error in
// this function), we still want to use the same mechanism that
// we would use for async.
//
m_pmu->DeferResponse();
// Do ISAPI application and IIS access bits checking
//
sc = m_pmu->ScIISCheck (m_pmu->LpwszRequestUrl(), MD_ACCESS_READ);
if (FAILED(sc))
{
// Either the request has been forwarded, or some bad error occurred.
// In either case, quit here and map the error!
//
SendResponse(sc);
return;
}
// For PropFind, content-length is required
//
//
if (NULL == m_pmu->LpwszGetRequestHeader (gc_szContent_Length, FALSE))
{
pwsz = m_pmu->LpwszGetRequestHeader (gc_szTransfer_Encoding, FALSE);
if (!pwsz || _wcsicmp (pwsz, gc_wszChunked))
{
DavTrace ("Dav: PUT: missing content-length in request\n");
SendResponse(E_DAV_MISSING_LENGTH);
return;
}
}
// Ensure the resource exists
//
sc = m_cri.ScGetResourceInfo (m_pwszPath);
if (FAILED (sc))
{
SendResponse(sc);
return;
}
// Depth header only applies on directories
//
if (m_cri.FCollection())
{
// Check the depth header only on collections
//
if (!FGetDepth (m_pmu.get(), &m_lDepth))
{
// If Depth header is wrong, fail the operation
//
SendResponse(E_INVALIDARG);
return;
}
}
// This method is gated by If-xxx headers
//
sc = ScCheckIfHeaders (m_pmu.get(), m_cri.PftLastModified(), FALSE);
if (FAILED (sc))
{
SendResponse(sc);
return;
}
// Ensure the URI and resource match
//
(void) ScCheckForLocationCorrectness (m_pmu.get(), m_cri, NO_REDIRECT);
// Check state headers here.
//
// For PROPFIND, when we check the state headers,
// we want to treat the request as if it were a
// GET-type request.
//
sc = HrCheckStateHeaders (m_pmu.get(), m_pwszPath, TRUE);
if (FAILED (sc))
{
DebugTrace ("DavFS: If-State checking failed.\n");
SendResponse(sc);
return;
}
// Handle locktokens and check for locks on this resource.
// Our locks don't lock the "secondary file stream" where we keep
// the properties, so we have to check manually before we do anything else.
//$REVIEW: Joels, will this change when we switch to NT5 properties?
//$REVIEW: If so, we need to change this code!
//
// If we have a locktoken, try to get the lock handle from the cache.
// If this fails, fall through and do the normal processing.
//
pwsz = m_pmu->LpwszGetRequestHeader (gc_szLockToken, TRUE);
if (!pwsz || !FGetLockHandle (m_pmu.get(), m_pwszPath, GENERIC_READ, pwsz, &hf))
{
// Manually check for locks on this resource.
// (see if someone ELSE has it locked...)
// If a read lock exists, tell the caller that it's locked.
//
if (FLockViolation (m_pmu.get(), ERROR_SHARING_VIOLATION, m_pwszPath, GENERIC_READ))
{
SendResponse(E_DAV_LOCKED);
return;
}
}
// If there was no request body, we want to get all props
//
if (!m_pmu->FExistsRequestBody())
{
sc = m_cfc.ScGetAllProps (m_pwszPath);
if (FAILED (sc))
{
SendResponse(sc);
return;
}
DoFind();
return;
}
else
{
// If there's a body, there must be a content-type header
// and the value must be text/xml
//
sc = ScIsContentTypeXML (m_pmu.get());
if (FAILED(sc))
{
DebugTrace ("Dav: PROPFIND specific fails without specifying a text/xml contenttype\n");
SendResponse(sc);
return;
}
}
// Instantiate the XML parser
//
m_pcpf.take_ownership(new CNFFind(m_cfc));
m_pstmRequest.take_ownership(m_pmu->GetRequestBodyIStream(*this));
sc = ScNewXMLParser( m_pcpf.get(),
m_pstmRequest.get(),
m_pxprs.load() );
if (FAILED(sc))
{
DebugTrace( "CPropFindRequest::Execute() - ScNewXMLParser() failed (0x%08lX)\n", sc );
SendResponse(sc);
return;
}
// Parse the body
//
ParseBody();
}
VOID
CPropFindRequest::ParseBody()
{
SCODE sc;
Assert( m_pxprs.get() );
Assert( m_pcpf.get() );
Assert( m_pstmRequest.get() );
// Parse XML from the request body stream.
//
// Add a ref for the following async operation.
// Use auto_ref_ptr rather than AddRef() for exception safety.
//
auto_ref_ptr<CPropFindRequest> pRef(this);
sc = ScParseXML (m_pxprs.get(), m_pcpf.get());
if ( SUCCEEDED(sc) )
{
Assert( S_OK == sc || S_FALSE == sc );
DoFind();
}
else if ( E_PENDING == sc )
{
//
// The operation is pending -- AsyncIOComplete() will take ownership
// ownership of the reference when it is called.
//
pRef.relinquish();
}
else
{
DebugTrace( "CPropFindRequest::ParseBody() - ScParseXML() failed (0x%08lX)\n", sc );
SendResponse(sc);
}
}
VOID
CPropFindRequest::AsyncIOComplete()
{
// Take ownership of the reference added for the async operation.
//
auto_ref_ptr<CPropFindRequest> pRef;
pRef.take_ownership(this);
ParseBody();
}
VOID
CPropFindRequest::DoFind()
{
LPCWSTR pwszUrl = m_pmu->LpwszRequestUrl();
SCODE sc;
// At this point, make sure that they support text/xml
//
sc = ScIsAcceptable (m_pmu.get(), gc_wszText_XML);
if (FAILED (sc))
{
SendResponse(sc);
return;
}
// All header must be emitted before chunked XML emitting starts
//
m_pmu->SetResponseHeader (gc_szContent_Type, gc_szText_XML);
// Set the response code and go
//
m_pmu->SetResponseCode( HscFromHresult(W_DAV_PARTIAL_SUCCESS),
NULL,
0,
CSEFromHresult(W_DAV_PARTIAL_SUCCESS) );
// Find the properties...
//
auto_ref_ptr<CXMLEmitter> pmsr;
auto_ref_ptr<CXMLBody> pxb;
pxb.take_ownership (new CXMLBody(m_pmu.get()));
pmsr.take_ownership (new CXMLEmitter(pxb.get(), &m_cfc));
if (DEPTH_ONE_NOROOT != m_lDepth)
{
// Get properties for root if it is not a noroot case
// Depth infinity,noroot is a bad request, that is why
// check above is valid.
//
sc = ScFindFileProps (m_pmu.get(),
m_cfc,
*pmsr,
pwszUrl,
m_pwszPath,
NULL,
m_cri,
FALSE /*fEmbeddErrorsInResponse*/);
if (FAILED (sc))
{
SendResponse(sc);
return;
}
}
// ScFindFilePropsDeep initializes the emitter root only
// when it sees there's an entry to emit. so we crash
// in the noroot empty response case, when we try to emit
// the response, as we have no entry to emit and the
// root is still NULL.
// so we here manually initialize the root,
//
sc = pmsr->ScSetRoot (gc_wszMultiResponse);
if (FAILED (sc))
{
SendResponse(sc);
return;
}
// And then, if apropriate, go deep...
//
if (m_cri.FCollection() &&
(m_lDepth != DEPTH_ZERO) &&
(m_pmu->MetaData().DwDirBrowsing() & MD_DIRBROW_ENABLED))
{
ChainedStringBuffer<WCHAR> sb;
CVRList vrl;
// Apply the property request across all the physical children
//
sc = ScFindFilePropsDeep (m_pmu.get(),
m_cfc,
*pmsr,
pwszUrl,
m_pwszPath,
NULL,
m_lDepth);
if (FAILED (sc))
{
SendResponse(sc);
return;
}
// Enumerate the child vroots and perform the
// deletion of those directories as well
//
m_pmu->ScFindChildVRoots (pwszUrl, sb, vrl);
for ( ; (!FAILED (sc) && !vrl.empty()); vrl.pop_front())
{
auto_ref_ptr<CVRoot> cvr;
LPCWSTR pwszChildUrl;
LPCWSTR pwszChildPath;
if (m_pmu->FGetChildVRoot (vrl.front().m_pwsz, cvr))
{
// Put the url into a multibyte string
//
cvr->CchGetVRoot (&pwszChildUrl);
// Only process the sub-vroot if we are
// truely are going deep or if the sub-vroot
// is the immediate child of the request URI
//
if ((m_lDepth == DEPTH_INFINITY) ||
FIsImmediateParentUrl (pwszUrl, pwszChildUrl))
{
CResourceInfo criSub;
// Crack the vroot and go...
//
cvr->CchGetVRPath (&pwszChildPath);
sc = criSub.ScGetResourceInfo (pwszChildPath);
if (!FAILED (sc))
{
// Find the properties on the vroot root
//
sc = ScFindFileProps (m_pmu.get(),
m_cfc,
*pmsr,
pwszChildUrl,
pwszChildPath,
cvr.get(),
criSub,
TRUE /*fEmbedErrorsInResponse*/);
}
if (FAILED (sc))
{
SendResponse(sc);
return;
}
else if (S_FALSE == sc)
continue;
// Find the properties on the vroot kids
//
if (m_lDepth == DEPTH_INFINITY)
{
auto_ref_ptr<IMDData> pMDData;
// See if we have directory browsing...
//
if (SUCCEEDED(m_pmu->HrMDGetData (pwszChildUrl, pMDData.load())) &&
(pMDData->DwDirBrowsing() & MD_DIRBROW_ENABLED))
{
sc = ScFindFilePropsDeep (m_pmu.get(),
m_cfc,
*pmsr,
pwszChildUrl,
pwszChildPath,
cvr.get(),
m_lDepth);
if (FAILED (sc))
{
SendResponse(sc);
return;
}
}
}
}
}
}
}
// Done with the reponse
//
pmsr->Done();
m_pmu->SendCompleteResponse();
}
VOID
CPropFindRequest::SendResponse( SCODE sc )
{
//
// Set the response code and go
//
m_pmu->SetResponseCode( HscFromHresult(sc), NULL, 0, CSEFromHresult(sc) );
m_pmu->SendCompleteResponse();
}
/*
* DAVPropFind()
*
* Purpose:
*
* Win32 file system implementation of the DAV PROPGET method. The
* PROPGET method responds with a fully constructed XML that provides
* the values of the resources property/properties.
*
* Parameters:
*
* pmu [in] pointer to the method utility object
*/
void
DAVPropFind (LPMETHUTIL pmu)
{
auto_ref_ptr<CPropFindRequest> pRequest(new CPropFindRequest(pmu));
pRequest->Execute();
}
// CPropPatchRequest ----------------------------------------------------------------
//
class CPropPatchRequest :
public CMTRefCounted,
private IAsyncIStreamObserver
{
//
// Reference to the CMethUtil
//
auto_ref_ptr<CMethUtil> m_pmu;
//
// Translated URI path
//
LPCWSTR m_pwszPath;
// Holds a handle owned by the lock cache.
//
auto_ref_handle m_hf;
// Resource info
//
CResourceInfo m_cri;
// Contexts
//
CFSPatch m_cpc;
auto_ref_ptr<CNFPatch> m_pnfp;
// Request body as an IStream. This stream is async -- it can
// return E_PENDING from Read() calls.
//
auto_ref_ptr<IStream> m_pstmRequest;
// The XML parser used to parse the request body using
// the node factory above.
//
auto_ref_ptr<IXMLParser> m_pxprs;
// IAsyncIStreamObserver
//
VOID AsyncIOComplete();
// State functions
//
VOID ParseBody();
VOID DoPatch();
VOID SendResponse( SCODE sc );
// NOT IMPLEMENTED
//
CPropPatchRequest (const CPropPatchRequest&);
CPropPatchRequest& operator= (const CPropPatchRequest&);
public:
// CREATORS
//
CPropPatchRequest(LPMETHUTIL pmu) :
m_pmu(pmu),
m_pwszPath(m_pmu->LpwszPathTranslated())
{
}
SCODE ScInit() { return m_cpc.ScInit(); }
// MANIPULATORS
//
VOID Execute();
};
VOID
CPropPatchRequest::Execute()
{
LPCWSTR pwsz;
SCODE sc = S_OK;
//
// First off, tell the pmu that we want to defer the response.
// Even if we send it synchronously (i.e. due to an error in
// this function), we still want to use the same mechanism that
// we would use for async.
//
m_pmu->DeferResponse();
// Do ISAPI application and IIS access bits checking
//
sc = m_pmu->ScIISCheck (m_pmu->LpwszRequestUrl(), MD_ACCESS_WRITE);
if (FAILED(sc))
{
// Either the request has been forwarded, or some bad error occurred.
// In either case, quit here and map the error!
//
SendResponse(sc);
return;
}
// PropPatch must have a content-type header and the value must be text/xml
//
sc = ScIsContentTypeXML (m_pmu.get());
if (FAILED(sc))
{
DebugTrace ("Dav: PROPPATCH fails without specifying a text/xml contenttype\n");
SendResponse(sc);
return;
}
// Look to see the Content-length - required for this operation
// to continue.
//
if (NULL == m_pmu->LpwszGetRequestHeader (gc_szContent_Length, FALSE))
{
DebugTrace ("Dav: PROPPATCH fails without content\n");
SendResponse(E_DAV_MISSING_LENGTH);
return;
}
if (!m_pmu->FExistsRequestBody())
{
DebugTrace ("Dav: PROPPATCH fails without content\n");
SendResponse(E_INVALIDARG);
return;
}
// This method is gated by If-xxx headers
//
if (!FAILED (m_cri.ScGetResourceInfo (m_pwszPath)))
{
// Ensure the URI and resource match...
//
(void) ScCheckForLocationCorrectness (m_pmu.get(), m_cri, NO_REDIRECT);
// ... then check the headers
//
sc = ScCheckIfHeaders (m_pmu.get(), m_cri.PftLastModified(), FALSE);
}
else
sc = ScCheckIfHeaders (m_pmu.get(), m_pwszPath, FALSE);
if (FAILED (sc))
{
SendResponse(sc);
return;
}
// Check state headers here.
//
sc = HrCheckStateHeaders (m_pmu.get(), m_pwszPath, FALSE);
if (FAILED (sc))
{
DebugTrace ("DavFS: If-State checking failed.\n");
SendResponse(sc);
return;
}
// Handle locktokens and check for locks on this resource.
// Our locks don't lock the "secondary file stream" where we keep
// the properties, so we have to check manually before we do anything else.
//$REVIEW: Joels, will this change when we switch to NT5 properties?
//$REVIEW: If so, we need to change this code!
//
// If we have a locktoken, try to get the lock handle from the cache.
// If this fails, fall through and do the normal processing.
//
pwsz = m_pmu->LpwszGetRequestHeader (gc_szLockToken, TRUE);
if (!pwsz || !FGetLockHandle (m_pmu.get(), m_pwszPath, GENERIC_WRITE, pwsz, &m_hf))
{
// Manually check for any write locks on this resource.
// If a write lock exists, don't process the request.
//
if (FLockViolation (m_pmu.get(), ERROR_SHARING_VIOLATION, m_pwszPath, GENERIC_WRITE))
{
SendResponse(E_DAV_LOCKED);
return;
}
}
// Instantiate the XML parser
//
m_pnfp.take_ownership(new CNFPatch(m_cpc));
m_pstmRequest.take_ownership(m_pmu->GetRequestBodyIStream(*this));
sc = ScNewXMLParser( m_pnfp.get(),
m_pstmRequest.get(),
m_pxprs.load() );
if (FAILED(sc))
{
DebugTrace( "CPropPatchRequest::Execute() - ScNewXMLParser() failed (0x%08lX)\n", sc );
SendResponse(sc);
return;
}
// Start parsing it into the context
//
ParseBody();
}
VOID
CPropPatchRequest::ParseBody()
{
Assert( m_pxprs.get() );
Assert( m_pnfp.get() );
Assert( m_pstmRequest.get() );
// Parse XML from the request body stream.
//
// Add a ref for the following async operation.
// Use auto_ref_ptr rather than AddRef() for exception safety.
//
auto_ref_ptr<CPropPatchRequest> pRef(this);
SCODE sc = ScParseXML (m_pxprs.get(), m_pnfp.get());
if ( SUCCEEDED(sc) )
{
Assert( S_OK == sc || S_FALSE == sc );
DoPatch();
}
else if ( E_PENDING == sc )
{
//
// The operation is pending -- AsyncIOComplete() will take ownership
// ownership of the reference when it is called.
//
pRef.relinquish();
}
else
{
DebugTrace( "CPropPatchRequest::ParseBody() - ScParseXML() failed (0x%08lX)\n", sc );
SendResponse(sc);
}
}
VOID
CPropPatchRequest::AsyncIOComplete()
{
// Take ownership of the reference added for the async operation.
//
auto_ref_ptr<CPropPatchRequest> pRef;
pRef.take_ownership(this);
ParseBody();
}
VOID
CPropPatchRequest::DoPatch()
{
SCODE sc;
// At this point, make sure that they support text/xml
//
sc = ScIsAcceptable (m_pmu.get(), gc_wszText_XML);
if (FAILED (sc))
{
SendResponse(sc);
return;
}
// Get the IPropertyBagEx on the resource
// If the file is locked, we must use its handle to
// get the inteface. otherwise, access will be denied
//
auto_com_ptr<IPropertyBagEx> pbag;
sc = ScGetPropertyBag (m_pwszPath,
STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
&pbag,
m_cri.FLoaded() ? m_cri.FCollection() : FALSE,
m_hf.get() ? m_hf.get() : INVALID_HANDLE_VALUE);
if (FAILED (sc))
{
// You can't set properties without a property bag...
//
if (VOLTYPE_NTFS != VolumeType (m_pwszPath, m_pmu->HitUser()))
sc = E_DAV_VOLUME_NOT_NTFS;
SendResponse(sc);
return;
}
// All header must be emitted before chunked XML emitting starts
//
m_pmu->SetResponseHeader (gc_szContent_Type, gc_szText_XML);
// Set the response code and go
//
m_pmu->SetResponseCode( HscFromHresult(W_DAV_PARTIAL_SUCCESS),
NULL,
0,
CSEFromHresult(W_DAV_PARTIAL_SUCCESS) );
// Apply the request
//
auto_ref_ptr<CXMLEmitter> pmsr;
auto_ref_ptr<CXMLBody> pxb;
pxb.take_ownership (new CXMLBody(m_pmu.get()));
pmsr.take_ownership (new CXMLEmitter(pxb.get(), &m_cpc));
CFSProp fsp(m_pmu.get(),
pbag,
m_pmu->LpwszRequestUrl(),
m_pwszPath,
NULL,
m_cri);
sc = m_cpc.ScPatch (*pmsr, m_pmu.get(), fsp);
// Make sure we close the file before sending any response
//
pbag.clear();
if (FAILED (sc))
{
SendResponse(sc);
return;
}
// Done with the reponse
//
pmsr->Done();
m_pmu->SendCompleteResponse();
}
VOID
CPropPatchRequest::SendResponse( SCODE sc )
{
//
// Set the response code and go
//
m_pmu->SetResponseCode( HscFromHresult(sc), NULL, 0, CSEFromHresult(sc) );
m_pmu->SendCompleteResponse();
}
/*
* DAVPropPatch()
*
* Purpose:
*
* Win32 file system implementation of the DAV PROPPATCH method. The
* PROPPATCH method responds with a fully constructed XML that identifies
* the success of each prop request.
*
* Parameters:
*
* pmu [in] pointer to the method utility object
*/
void
DAVPropPatch (LPMETHUTIL pmu)
{
SCODE sc;
auto_ref_ptr<CPropPatchRequest> pRequest(new CPropPatchRequest(pmu));
sc = pRequest->ScInit();
if (FAILED(sc))
{
pmu->SetResponseCode( HscFromHresult(sc), NULL, 0, CSEFromHresult(sc) );
pmu->SendCompleteResponse();
}
pRequest->Execute();
}