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.
 
 
 
 
 
 

2367 lines
63 KiB

/*
* F S M V C P Y . C P P
*
* Sources for directory ineration object
*
* Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
*/
#include "_davfs.h"
#include "_fsmvcpy.h"
#include "_shlkmgr.h"
// ScAddMultiUrl
// Helper function for XML emitting
//
SCODE
ScAddMultiFromUrl (
/* [in] */ CXMLEmitter& emitter,
/* [in] */ IMethUtil * pmu,
/* [in] */ LPCWSTR pwszUrl,
/* [in] */ ULONG hsc,
/* [in] */ BOOL fCollection,
/* [in] */ BOOL fMove = FALSE)
{
SCODE sc = S_OK;
// NT#403615 -- Rosebud in Office9 or in NT5 re-issues a MOVE if they
// see a 401 inside the 207 Multi-Status response, which can result
// in data loss.
// WORKAROUND: If we are doing a MOVE, and the User-Agent string shows
// that this is Rosebud from Office9 or from NT5, change all 401s to 403s
// to avoid the problem -- Rosebud will not re-issue the MOVE, so the
// data (now sitting in the destination dir) will not be wiped.
// This is the minimal code needed to work around the problem.
//
if (fMove &&
HSC_UNAUTHORIZED == hsc &&
(pmu->FIsOffice9Request() || pmu->FIsRosebudNT5Request()))
{
hsc = HSC_FORBIDDEN;
}
// Supress omitting of "Method Failure" node as it is a "SHOULD NOT"
// item in the DAV drafts.
// It's possible pszUrl passed in as NULL, in these cases, simply skip
// the emitting, do nothing
//
if ((hsc != HSC_METHOD_FAILURE) && pwszUrl)
{
auto_heap_ptr<CHAR> pszUrlEscaped;
CEmitterNode enRes;
UINT cchUrl;
//$REVIEW: This is important, we should not start a xml document
//$REVIEW: unless we have to. Otherwise, we may end up XML body
//$REVIEW: when not necessary.
//$REVIEW: So it's necessary to call ScSetRoot() to make sure
//$REVIEW: that the xml document is intialized bofore continue
//$REVIEW:
//$REVIEW: This model works because in fsmvcpy.cpp, all the calls to
//$REVIEW: XML emitter are through ScAddMultiFromUrl and ScAddMulti
//$REVIEW:
sc = emitter.ScSetRoot (gc_wszMultiResponse);
if (FAILED (sc))
goto ret;
// Construct the response
//
sc = enRes.ScConstructNode (emitter, emitter.PxnRoot(), gc_wszResponse);
if (FAILED (sc))
goto ret;
// Construct the href node
//
{
CEmitterNode en;
sc = enRes.ScAddNode (gc_wszXML__Href, en);
if (FAILED (sc))
goto ret;
// Set the value of the href node. If the url is absolute,
// but not fully qualified, qualify it...
//
if (L'/' == *pwszUrl)
{
LPCSTR psz;
UINT cch;
// Add the prefix
//
cch = pmu->CchUrlPrefix (&psz);
sc = en.Pxn()->ScSetUTF8Value (psz, cch);
if (FAILED (sc))
goto ret;
//$ REVIEW: Does the host name need escaping?
//
// Add the server
//
cch = pmu->CchServerName (&psz);
sc = en.Pxn()->ScSetValue (psz, cch);
if (FAILED (sc))
goto ret;
}
// Make the url wire safe
//
sc = ScWireUrlFromWideLocalUrl (static_cast<UINT>(wcslen(pwszUrl)),
pwszUrl,
pszUrlEscaped);
if (FAILED (sc))
goto ret;
// Add the url value
//
cchUrl = static_cast<UINT>(strlen(pszUrlEscaped.get()));
sc = en.Pxn()->ScSetUTF8Value (pszUrlEscaped.get(), cchUrl);
if (FAILED (sc))
goto ret;
// If this is a collection, and the last char is not a
// trailing slash, add one....
//
if (fCollection && ('/' != pszUrlEscaped.get()[cchUrl-1]))
{
sc = en.Pxn()->ScSetUTF8Value ("/", 1);
if (FAILED (sc))
goto ret;
}
}
// Add the status/error string
//
sc = ScAddStatus (&enRes, hsc);
if (FAILED (sc))
goto ret;
}
ret:
return sc;
}
SCODE
ScAddMulti (
/* [in] */ CXMLEmitter& emitter,
/* [in] */ IMethUtil * pmu,
/* [in] */ LPCWSTR pwszPath,
/* [in] */ LPCWSTR pwszErr,
/* [in] */ ULONG hsc,
/* [in] */ BOOL fCollection,
/* [in] */ CVRoot* pcvrTrans)
{
SCODE sc = S_OK;
// Supress omitting of "Method Failure" node as it is a "SHOULD NOT"
// item in the DAV drafts.
//
if (hsc != HSC_METHOD_FAILURE)
{
CEmitterNode enRes;
//$REVIEW: This is important, we should not start a xml document
//$REVIEW: unless we have to. Otherwise, we may end up XML body
//$REVIEW: when not necessary.
//$REVIEW: So it's necessary to call ScSetRoot() to make sure
//$REVIEW: that the xml document is intialized bofore continue
//$REVIEW:
//$REVIEW: This model works because in fsmvcpy.cpp, all the calls to
//$REVIEW: XML emitter are through ScAddMultiFromUrl and ScAddMulti
//$REVIEW:
sc = emitter.ScSetRoot (gc_wszMultiResponse);
if (FAILED (sc))
goto ret;
sc = enRes.ScConstructNode (emitter, emitter.PxnRoot(), gc_wszResponse);
if (FAILED (sc))
goto ret;
sc = ScAddHref (enRes, pmu, pwszPath, fCollection, pcvrTrans);
if (FAILED (sc))
goto ret;
sc = ScAddStatus (&enRes, hsc);
if (FAILED (sc))
goto ret;
if (pwszErr)
{
sc = ScAddError (&enRes, pwszErr);
if (FAILED (sc))
goto ret;
}
}
ret:
return sc;
}
// class CAccessMetaOp -------------------------------------------------------
//
SCODE __fastcall
CAccessMetaOp::ScOp(LPCWSTR pwszMbPath, UINT cch)
{
SCODE sc;
METADATA_RECORD mdrec;
Assert (MD_ACCESS_PERM == m_dwId);
Assert (DWORD_METADATA == m_dwType);
// Get the value from the metabase and don't inherit
//
DWORD dwAcc = 0;
DWORD cb = sizeof(DWORD);
mdrec.dwMDIdentifier = m_dwId;
mdrec.dwMDAttributes = METADATA_NO_ATTRIBUTES;
mdrec.dwMDUserType = 0;
mdrec.dwMDDataType = m_dwType;
mdrec.dwMDDataLen = cb;
mdrec.pbMDData = reinterpret_cast<LPBYTE>(&dwAcc);
sc = m_mdoh.HrGetMetaData( pwszMbPath,
&mdrec,
&cb );
if (FAILED(sc))
{
MCDTrace ("CAccessMetaOp::ScOp() - CMDObjectHandle::HrGetMetaData() failed 0x%08lX\n", sc);
// We will ignore any NOT_FOUND type errors
//
if ((HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == sc) ||
(MD_ERROR_DATA_NOT_FOUND == sc))
sc = S_OK;
goto ret;
}
// Hey, we got a value, so let's do our quick check..
//
if (m_dwAcc == (dwAcc & m_dwAcc))
{
// We have full required access to this node, so
// we can proceed.
//
Assert (S_OK == sc);
}
else
{
// We do not have access to operate on this item and
// it's inherited children.
//
MCDTrace ("CAccessMetaOp::ScOp() - no access to '%S'\n", pwszMbPath);
m_fAccessBlocked = TRUE;
// We know enough...
//
sc = S_FALSE;
}
ret:
return sc;
}
// class CAuthMetaOp -------------------------------------------------------
//
SCODE __fastcall
CAuthMetaOp::ScOp(LPCWSTR pwszMbPath, UINT cch)
{
SCODE sc;
METADATA_RECORD mdrec;
Assert (MD_AUTHORIZATION == m_dwId);
Assert (DWORD_METADATA == m_dwType);
// Get the value from the metabase and don't inherit
//
DWORD dwAuth = 0;
DWORD cb = sizeof(DWORD);
mdrec.dwMDIdentifier = m_dwId;
mdrec.dwMDAttributes = METADATA_NO_ATTRIBUTES;
mdrec.dwMDUserType = 0;
mdrec.dwMDDataType = m_dwType;
mdrec.dwMDDataLen = cb;
mdrec.pbMDData = reinterpret_cast<LPBYTE>(&dwAuth);
sc = m_mdoh.HrGetMetaData( pwszMbPath,
&mdrec,
&cb );
if (FAILED(sc))
{
MCDTrace ("CAuthMetaOp::ScOp() - CMDObjectHandle::HrGetMetaData() failed 0x%08lX\n", sc);
// We will ignore any NOT_FOUND type errors
//
if ((HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == sc) ||
(MD_ERROR_DATA_NOT_FOUND == sc))
sc = S_OK;
goto ret;
}
// Hey, we got a value, so let's do our quick check..
//
if (m_dwAuth == dwAuth)
{
Assert(S_OK == sc);
}
else
{
// We do not have access to operate on this item and
// it's inherited children.
//
MCDTrace ("CAuthMetaOp::ScOp() - authorization differs, no access to '%S'\n", pwszMbPath);
m_fAccessBlocked = TRUE;
// We know enough...
//
sc = S_FALSE;
}
ret:
return sc;
}
// class CIPRestrictionMetaOp ------------------------------------------------
//
SCODE __fastcall
CIPRestrictionMetaOp::ScOp(LPCWSTR pwszMbPath, UINT cch)
{
SCODE sc;
METADATA_RECORD mdrec;
Assert (MD_IP_SEC == m_dwId);
Assert (BINARY_METADATA == m_dwType);
// Get the value from the metabase and don't inherit
//
DWORD cb = 0;
mdrec.dwMDIdentifier = m_dwId;
mdrec.dwMDAttributes = METADATA_NO_ATTRIBUTES;
mdrec.dwMDUserType = 0;
mdrec.dwMDDataType = m_dwType;
mdrec.dwMDDataLen = cb;
mdrec.pbMDData = NULL;
sc = m_mdoh.HrGetMetaData( pwszMbPath,
&mdrec,
&cb );
if (FAILED(sc) && (0 == cb))
{
MCDTrace ("CIPRestrictionMetaOp::ScOp() - CMDObjectHandle::HrGetMetaData() failed 0x%08lX, but that means success in this path\n", sc);
// We will ignore any NOT_FOUND type errors
//
if ((HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == sc) ||
(MD_ERROR_DATA_NOT_FOUND == sc))
sc = S_OK;
}
else
{
Assert (S_OK == sc ||
HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) == sc);
// Hey, we got a value, and we don't want to check here so
// we are going to be pessimistic about this one....
//
MCDTrace ("CIPRestrictionMetaOp::ScOp() - IPRestriction exists in tree '%S'\n", pwszMbPath);
m_fAccessBlocked = TRUE;
// We know enough...
//
sc = S_FALSE;
}
return sc;
}
// ScCheckMoveCopyDeleteAccess() ---------------------------------------------
//
SCODE __fastcall
ScCheckMoveCopyDeleteAccess (
/* [in] */ IMethUtil* pmu,
/* [in] */ LPCWSTR pwszUrl,
/* [in] */ CVRoot* pcvr,
/* [in] */ BOOL fDirectory,
/* [in] */ BOOL fCheckScriptmaps,
/* [in] */ DWORD dwAccess,
/* [out] */ SCODE* pscItem,
/* [in] */ CXMLEmitter& msr)
{
SCODE sc = S_OK;
// Check with the CMethUtil on whether or not we have access
//
sc = pmu->ScCheckMoveCopyDeleteAccess (pwszUrl,
pcvr,
fDirectory,
fCheckScriptmaps,
dwAccess);
// Pass back the results...
//
*pscItem = sc;
// ... and if the call cannot proceed, then add the item to the
// multi-status response.
//
if (FAILED (sc))
{
// Add to the reponse XML
//
sc = ScAddMultiFromUrl (msr,
pmu,
pwszUrl,
HscFromHresult(sc),
fDirectory);
if (!FAILED (sc))
sc = W_DAV_PARTIAL_SUCCESS;
}
return sc;
}
// Directory deletes ---------------------------------------------------------
//
/*
* ScDeleteDirectory()
*
* Purpose:
*
* Helper function used to iterate through a directory
* and delete all its contents as well as the directory
* itself.
*
* Notes:
*
* BIG FAT NOTE ABOUT LOCKING.
*
* The Lock-Token header may contain locks that we have to use in
* this operation.
* The following code was written with these assumptions:
* o Directory locks are NOT SUPPORTED on davfs.
* o Locks only affect the ability to WRITE to a resource.
* (The only currently supported locktype on davfs is WRITE.)
* o This function may be called from DELETE or other methods.
* (If called from DELETE, we want to DROP the locks listed.)
* Because of these two assumptions, we only check the passed-in
* locktokens when we have a write-error (destination).
*
* Locking uses the final two parameters. plth is a
* pointer to a locktoken header parser object. If we have a plth,
* then we must check it for provided locktokens when we hit a lock
* conflict. If a locktoken is provided, the failed delete operation
* should NOT be reported as an error, but instead skipped here
* (to be handled by the calling routine later in the operation) OR
* the lock should be dropped here and the delete attempted again.
* The fDeleteLocks variable tells whether to drop locks (TRUE),
* or to skip the deleteion of locked items that have locktokens.
*
* Basic logic:
* Try to delete.
* If LOCKING failure (ERROR_SHARING_VIOLATION), check the plth.
* If the plth has a locktoken for this path, check fDeleteLocks.
* If fDeleteLocks == TRUE, drop the lock and try the delte again.
* If fDeleteLocks == FALSE, skip this file and move on.
*/
SCODE
ScDeleteDirectory (
/* [in] */ IMethUtil* pmu,
/* [in] */ LPCWSTR pwszUrl,
/* [in] */ LPCWSTR pwszDir,
/* [in] */ BOOL fCheckAccess,
/* [in] */ DWORD dwAcc,
/* [in] */ LONG lDepth,
/* [in] */ CVRoot* pcvrTranslate,
/* [in] */ CXMLEmitter& msr,
/* [out] */ BOOL* pfDeleted,
/* [in] */ CParseLockTokenHeader* plth, // Usually NULL -- no locktokens to worry about
/* [in] */ BOOL fDeleteLocks) // Normally FALSE -- don't drop locks
{
BOOL fOneSkipped = FALSE;
ChainedStringBuffer<WCHAR> sb;
SCODE sc = S_OK;
SCODE scItem = S_OK;
std::list<LPCWSTR, heap_allocator<LPCWSTR> > lst;
CDirIter di(pwszUrl,
pwszDir,
NULL, // no-destination for deletes
NULL, // no-destination for deletes
NULL, // no-destination for deletes
TRUE); // recurse into sub-driectories
Assert (pfDeleted);
*pfDeleted = TRUE;
// A SMALL FAT NOTE ABOUT ACCESS
//
// The caller of this method is required to sniff the tree
// prior to this call. If there is any access block in the
// tree, then each item will be checked for access before the
// operation proceeds.
//
const DWORD dwDirectory = MD_ACCESS_READ | MD_ACCESS_WRITE;
const DWORD dwFile = MD_ACCESS_WRITE;
if (fCheckAccess & (0 == (dwAcc & MD_ACCESS_READ)))
{
DebugTrace ("Dav: MCD: no permissions for deleting\n");
sc = E_DAV_NO_IIS_READ_ACCESS;
*pfDeleted = FALSE;
goto ret;
}
// Push the current path
//
//$ REVIEW: if "depth: infinity,no-root" ever needs to be supported,
// it really is as simple as not pushing the current path.
//
if (DEPTH_INFINITY_NOROOT != lDepth)
{
Assert (DEPTH_INFINITY == lDepth);
lst.push_back(pwszDir);
}
// Iterate through the directories. Deleting files and pushing
// directory names as we go.
//
// We really only want to push into the child directories if the
// operation on the parent succeeds.
//
while (S_OK == di.ScGetNext(!FAILED (scItem)))
{
// Check our access rights and only push down into the directory
// if we have access to delete its contents.
//
// Note that we need read and write access to enum and delete
// a directory, but we need only write access in order to delete
// a file.
//
if (fCheckAccess)
{
sc = ScCheckMoveCopyDeleteAccess (pmu,
di.PwszUri(),
pcvrTranslate,
di.FDirectory(),
FALSE, // don't check scriptmaps
di.FDirectory() ? dwDirectory : dwFile,
&scItem,
msr);
if (FAILED (sc))
goto ret;
// If things were not 100%, don't process this resource
//
if (S_OK != sc)
continue;
}
// Process the file
//
if (di.FDirectory())
{
auto_ref_ptr<CVRoot> pcvr;
if (di.FSpecial())
continue;
// Child virtual root scriptmappings have been
// handled via ScCheckMoveCopyDeleteAccess(),
// and the physical deleting happens after this
// call completes!
//
// So there is no need to do any special processing
// other than to push the directory and move on...
//
lst.push_back (AppendChainedSz (sb, di.PwszSource()));
scItem = S_OK;
}
else
{
// Delete the file
//
// NOTE: We've already checked that we have write access.
// Also keep in mind that the ordering in which the directory
// traversals occur allows us to still key off of scItem to
// determine if we should push down into subdirectories.
//
// This is because the directory entry is processed BEFORE
// any children are processed. So the iteration on the dir
// will reset the scItem with the appropriate scode for access.
//
MCDTrace ("Dav: MCD: deleting '%ws'\n", di.PwszSource());
if (!DavDeleteFile (di.PwszSource()))
{
DWORD dwLastError = GetLastError();
ULONG hsc = HscFromLastError(dwLastError);
DebugTrace ("Dav: MCD: failed to delete file (%d)\n", dwLastError);
// If it's a sharing (lock) violation, AND we have a
// locktoken for this path (lth.GetToken(pwsz))
// skip this path.
//
if ((ERROR_SHARING_VIOLATION == dwLastError) && plth)
{
LARGE_INTEGER liLockID;
// If we have a locktoken for this path, skip it.
//
scItem = plth->HrGetLockIdForPath(di.PwszSource(),
GENERIC_WRITE,
&liLockID);
if (SUCCEEDED (scItem))
{
// Should we try to delete locks?
//
if (!fDeleteLocks)
{
// Don't delete locks. Just skip this item.
// Remember that we skipped it, tho, so we don't
// complain about deleting the parent dir below.
//
fOneSkipped = TRUE;
continue;
}
else
{
// Drop the lock & try again.
//
scItem = CSharedLockMgr::Instance().HrDeleteLock(pmu->HitUser(),
liLockID);
if (SUCCEEDED(scItem))
{
if (DavDeleteFile(di.PwszSource()))
{
// We're done with this item. Move along.
//
continue;
}
// Else, record the error in our XML
//
hsc = HscFromLastError(GetLastError());
}
else
{
hsc = HscFromHresult(scItem);
}
}
}
// else, record the error in our XML.
//
}
// Add to the reponse XML
//
sc = ScAddMultiFromUrl (msr,
pmu,
di.PwszUri(),
hsc,
di.FDirectory());
if (FAILED (sc))
goto ret;
sc = W_DAV_PARTIAL_SUCCESS;
}
}
}
// Now that all the files are deleted, we can start deleting
// the directories.
//
while (!lst.empty())
{
MCDTrace ("Dav: MCD: removing '%ws'\n", lst.back());
// Try to delete the dir. If it doesn't delete, check our
// "skipped because of a lock above" flag before complaining.
//
//$ LATER: Fix this to actually lookup the dir path in the
// lockcache (using "fPathLookup").
//
if (!DavRemoveDirectory (lst.back()) && !fOneSkipped)
{
DWORD dw = GetLastError();
DebugTrace ("Dav: MCD: failed to delete directory: %ld\n", dw);
// Add to the reponse XML
//
sc = ScAddMulti (msr,
pmu,
lst.back(),
NULL,
HscFromLastError(dw),
TRUE, // We know it's a directory
pcvrTranslate);
if (FAILED (sc))
goto ret;
sc = W_DAV_PARTIAL_SUCCESS;
*pfDeleted = FALSE;
}
lst.pop_back();
}
ret:
return sc;
}
SCODE
ScDeleteDirectoryAndChildren (
/* [in] */ IMethUtil* pmu,
/* [in] */ LPCWSTR pwszUrl,
/* [in] */ LPCWSTR pwszPath,
/* [in] */ BOOL fCheckAccess,
/* [in] */ DWORD dwAcc,
/* [in] */ LONG lDepth,
/* [in] */ CXMLEmitter& msr,
/* [in] */ CVRoot* pcvrTranslate,
/* [out] */ BOOL* pfDeleted,
/* [in] */ CParseLockTokenHeader* plth, // Usually NULL -- no locktokens to worry about
/* [in] */ BOOL fDeleteLocks) // Normally FALSE -- don't drop locks
{
BOOL fPartial = FALSE;
SCODE sc = S_OK;
// Delete the main body first
//
MCDTrace ("Dav: MCD: deleting '%ws'\n", pwszPath);
sc = ScDeleteDirectory (pmu,
pwszUrl,
pwszPath,
fCheckAccess,
dwAcc,
lDepth,
pcvrTranslate, // translations are pmu based
msr,
pfDeleted,
plth,
fDeleteLocks);
if (!FAILED (sc))
{
// Enumerate the child vroots and perform the
// deletion of those directories as well
//
ChainedStringBuffer<WCHAR> sb;
CVRList vrl;
// Cleanup the list such that our namespaces are in
// a reasonable order.
//
(void) pmu->ScFindChildVRoots (pwszUrl, sb, vrl);
vrl.sort();
for ( ; !FAILED(sc) && !vrl.empty(); vrl.pop_front())
{
auto_ref_ptr<CVRoot> pcvr;
CResourceInfo cri;
LPCWSTR pwszChildUrl;
LPCWSTR pwszChildPath;
SCODE scItem;
// Remember any partial returns
//
if (W_DAV_PARTIAL_SUCCESS == sc)
fPartial = TRUE;
if (pmu->FGetChildVRoot (vrl.front().m_pwsz, pcvr))
{
// Note, only check access if required
//
Assert (fCheckAccess);
pcvr->CchGetVRoot (&pwszChildUrl);
pcvr->CchGetVRPath (&pwszChildPath);
sc = ScCheckMoveCopyDeleteAccess (pmu,
pwszChildUrl,
pcvr.get(),
TRUE, // Directory
FALSE, // dont check scriptmaps
MD_ACCESS_READ|MD_ACCESS_WRITE,
&scItem,
msr);
if (FAILED (sc))
goto ret;
// If things were not 100%, don't process this resource
//
if (S_OK != sc)
continue;
// Delete the sub-virtual roots files and and continue on
//
sc = ScDeleteDirectory (pmu,
pwszChildUrl,
pwszChildPath,
fCheckAccess,
dwAcc,
DEPTH_INFINITY,
pcvr.get(),
msr,
pfDeleted,
plth,
fDeleteLocks);
if (FAILED (sc))
{
sc = ScAddMultiFromUrl (msr,
pmu,
pwszChildUrl,
HscFromHresult(sc),
TRUE); // We know it's a directory
if (FAILED (sc))
goto ret;
sc = W_DAV_PARTIAL_SUCCESS;
*pfDeleted = FALSE;
}
}
}
}
ret:
return ((S_OK == sc) && fPartial) ? W_DAV_PARTIAL_SUCCESS : sc;
}
// class CContentTypeMetaOp --------------------------------------------------
//
SCODE __fastcall
CContentTypeMetaOp::ScOp(LPCWSTR pwszMbPath, UINT cchSrc)
{
Assert (MD_MIME_MAP == m_dwId);
Assert (MULTISZ_METADATA == m_dwType);
// Ok, we are going to get the data and copy it across
// if there is a destination path.
//
if (NULL != m_pwszDestPath)
{
WCHAR prgchContentType[MAX_PATH];
auto_heap_ptr<WCHAR> pwszContentType;
CMDObjectHandle mdohDest(*m_pecb);
CStackBuffer<WCHAR,128> pwsz;
DWORD cb = sizeof(prgchContentType);
LPBYTE pbValue = reinterpret_cast<LPBYTE>(prgchContentType);
LPWSTR pwszLowest;
METADATA_RECORD mdrec;
SCODE sc = S_OK;
UINT cchBase;
MCDTrace ("CContentTypeMetaOp::ScOp() - content-type: copying for '%S%S'...\n",
m_pwszMetaPath,
pwszMbPath);
mdrec.dwMDIdentifier = m_dwId;
mdrec.dwMDAttributes = METADATA_NO_ATTRIBUTES;
mdrec.dwMDUserType = 0;
mdrec.dwMDDataType = m_dwType;
mdrec.dwMDDataLen = cb;
mdrec.pbMDData = pbValue;
sc = m_mdoh.HrGetMetaData( pwszMbPath,
&mdrec,
&cb );
if (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) == sc)
{
pwszContentType = static_cast<LPWSTR>(g_heap.Alloc(cb));
pbValue = reinterpret_cast<LPBYTE>(pwszContentType.get());
mdrec.dwMDIdentifier = m_dwId;
mdrec.dwMDAttributes = METADATA_NO_ATTRIBUTES;
mdrec.dwMDUserType = 0;
mdrec.dwMDDataType = m_dwType;
mdrec.dwMDDataLen = cb;
mdrec.pbMDData = pbValue;
sc = m_mdoh.HrGetMetaData( pwszMbPath,
&mdrec,
&cb );
}
if (FAILED(sc))
{
//$ REVIEW: should failure to copy a content-type
// fail the call? So we will ignore any NOT_FOUND
// type errors
//
if ((HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == sc) ||
(MD_ERROR_DATA_NOT_FOUND == sc))
sc = S_OK;
goto ret;
}
// We sucesfully read some metadata. Remember the size.
//
cb = mdrec.dwMDDataLen;
m_mdoh.Close();
// The destination path is the comprised of the stored
// destination path and the tail of the original source
// path.
//
MCDTrace ("CContentTypeMetaOp::ScOp() - content-type: ...to '%S%S'\n",
m_pwszDestPath,
pwszMbPath);
// Construct an metabase path for lowest node construction
//
cchBase = static_cast<UINT>(wcslen(m_pwszDestPath));
if (NULL == pwsz.resize(CbSizeWsz(cchBase + cchSrc)))
{
sc = E_OUTOFMEMORY;
goto ret;
}
// Before we construct anything, make sure that we are not
// doing something stupid and creating two '//' in a row.
//
if ((L'/' == m_pwszDestPath[cchBase - 1]) &&
(L'/' == *pwszMbPath))
{
cchBase -= 1;
}
memcpy (pwsz.get(), m_pwszDestPath, cchBase * sizeof(WCHAR));
memcpy (pwsz.get() + cchBase, pwszMbPath, (cchSrc + 1) * sizeof(WCHAR));
// Release our hold on the metabase. We need to do this
// because it is possible that the common root between
// the two nodes, may be in the same hierarchy.
//
// NOTE: this should only really happen one time per
// move/copy operation. The reason being that the lowest
// node will be established outside of the scope of the
// source on the first call.
//
sc = HrMDOpenLowestNodeMetaObject(pwsz.get(),
METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE,
&pwszLowest,
&mdohDest);
if (SUCCEEDED(sc))
{
mdrec.dwMDIdentifier = m_dwId;
mdrec.dwMDAttributes = METADATA_INHERIT;
mdrec.dwMDUserType = IIS_MD_UT_FILE;
mdrec.dwMDDataType = m_dwType;
mdrec.dwMDDataLen = cb;
mdrec.pbMDData = pbValue;
(void) mdohDest.HrSetMetaData(pwszLowest, &mdrec);
mdohDest.Close();
}
// Reaquire our hold on the metabase
//
sc = HrMDOpenMetaObject( m_pwszMetaPath,
m_fWrite ? METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE : METADATA_PERMISSION_READ,
5000,
&m_mdoh);
if (FAILED (sc))
goto ret;
}
// If this is a move, then delete the source
//
if (m_fDelete)
{
MCDTrace ("Dav: MCD: content-type: deleting from '%S'\n", pwszMbPath);
(void) m_mdoh.HrDeleteMetaData( pwszMbPath,
m_dwId,
m_dwType);
}
ret:
return S_OK;
}
// Directory moves/copies ----------------------------------------------------
//
/*
* ScMoveCopyDirectory()
*
* Purpose:
*
* Helper function used to iterate through a directory
* and copy all its contents to a destination directory.
*
* Notes:
*
* BIG FAT NOTE ABOUT LOCKING.
*
* The Lock-Token header may contain locks that we have to use in
* this operation.
* The following code was written with these assumptions:
* o Directory locks are NOT SUPPORTED on davfs.
* o Locks only affect the ability to WRITE to a resource.
* (The only currently supported locktype on davfs is WRITE.)
* Because of these two assumptions, we only check the passed-in
* locktokens when we have a write-error (destination).
*/
SCODE
ScMoveCopyDirectory (
/* [in] */ IMethUtil* pmu,
/* [in] */ LPCWSTR pwszUrl,
/* [in] */ LPCWSTR pwszSrc,
/* [in] */ LPCWSTR pwszUrlDst,
/* [in] */ LPCWSTR pwszDst,
/* [in] */ BOOL fMove,
/* [in] */ DWORD dwReplace,
/* [in] */ BOOL fCheckAccess,
/* [in] */ BOOL fCheckDestinationAccess,
/* [in] */ DWORD dwAcc,
/* [in] */ CVRoot* pcvrTranslateSrc,
/* [in] */ CVRoot* pcvrTranslateDst,
/* [in] */ CXMLEmitter& msr,
/* [in] */ LONG lDepth,
/* [in] */ CParseLockTokenHeader* plth) // Usually NULL -- no locktokens to worry about
{
auto_ref_ptr<CVRoot> pcvrDestination(pcvrTranslateDst);
ChainedStringBuffer<WCHAR> sb;
LPCWSTR pwszDestinationRedirect = NULL;
SCODE sc = S_OK;
SCODE scItem = S_OK;
std::list<LPCWSTR, heap_allocator<LPCWSTR> > lst;
CDirIter di(pwszUrl,
pwszSrc,
pwszUrlDst,
pwszDst,
pcvrTranslateDst,
TRUE); // Traverse into sub-directories
// See if there is a path conflict
//
if (FPathConflict (pwszSrc, pwszDst))
{
DebugTrace ("Dav: source and dest are in conflict\n");
sc = E_DAV_CONFLICTING_PATHS;
goto ret;
}
// Ok, for MOVE requests where there is no blocked
// access along the way we can do the whole thing
// in one big shot.
//
// Otherwise we are going to try and do this piece-wise.
//
//$ REVIEW:
//
// Normally, we would do something like the following
//
// if (!fMove ||
// fCheckAccess ||
// fCheckDestinationAccess ||
// !DavMoveFile (pwszSrc, pwszDst, dwReplace))
//
// However, if the above code is used, IIS holds a lock
// on the moved source directory. This prevents further
// access to that physical path. NtCreateFile() reports
// a status of "DELETE PENDING" on the locked directory
// Win32 API's report "ACCESS DENIED"
//
// If we do the degenerate case of always copying the root
// by hand, it reduces the likely hood of the lock out.
//
// When this code gets checked in, a bug should be filed
// against IIS over this lock issue. If and only if they
// do not fix this, will we fall back to the degenerate
// code.
//
if (!fMove ||
fCheckAccess ||
fCheckDestinationAccess ||
!DavMoveFile (pwszSrc, pwszDst, dwReplace))
//
//$ REVIEW: end
{
// Create the destination directory
//
if (!DavCreateDirectory (pwszDst, NULL))
{
// If we have locks, and the dir is already there, it's okay.
// Otherwise, return an error.
//
//$ LATER: Fix this to actually lookup the dir path in the
// lockcache (using "fPathLookup").
//
if (!plth)
{
DWORD dw = GetLastError();
if ((dw == ERROR_FILE_EXISTS) || (dw == ERROR_ALREADY_EXISTS))
sc = E_DAV_OVERWRITE_REQUIRED;
else
sc = HRESULT_FROM_WIN32(dw);
DebugTrace ("Dav: MCD: failed to create destination\n");
goto ret;
}
}
// Slurp the properties over for the root directory
// We need to copy the properties first,
// Note, Currently, this call must be ahead of FInstantiate,
// as FInstantiate will keep the src dir open, and we won't be able to
// get a IPropertyBagEx with STGM_SHARE_EXCLUSIVE, which is
// required by current nt5 impl. as STGM_SHARE_SHARE_WRITE
// does not work with IPropertyBagEx::Enum.
//
sc = ScCopyProps (pmu, pwszSrc, pwszDst, TRUE);
if (FAILED(sc))
{
// Do our best to remove the turd directory
//
DavRemoveDirectory (pwszDst);
goto ret;
}
// For MOVE's, push the current path
//
if (fMove)
lst.push_back (pwszSrc);
}
else // !fMove || fCheckAccess || fCheckDestinationAccess || !MoveFileEx()
{
Assert (DEPTH_INFINITY == lDepth);
// Ok, this is the cool bit. If this succeeded,
// there there is no more processing required.
//
goto ret;
}
// If we are not asked to copy internal members,
// then we are done
//
if (DEPTH_INFINITY != lDepth)
{
Assert (!fMove);
goto ret;
}
// Iterate through the directories -- copying as we go
//
while (S_OK == di.ScGetNext(!FAILED (scItem),
pwszDestinationRedirect,
pcvrDestination.get()))
{
//$ REVIEW:
//
// We have a very nasty case that we need to be
// able to handle...
//
// If a virtual root already exists along the path of
// the destination, we need to redirect out destination
// path to the vrpath of that virtual root.
//
// Reset the destination redirection
//
pwszDestinationRedirect = di.PwszDestination();
pcvrDestination = di.PvrDestination();
//
//$ REVIEW: end
// Before anything, if this is one of the specials,
// do nothing...
//
if (di.FSpecial())
continue;
if (fCheckAccess)
{
// Check our access rights and only push down
// into the directory if and only if we have access.
//
sc = ScCheckMoveCopyDeleteAccess (pmu,
di.PwszUri(),
pcvrTranslateSrc,
di.FDirectory(),
TRUE, // check scriptmaps
dwAcc,
&scItem,
msr);
if (FAILED (sc))
goto ret;
// If things were not 100%, don't process this resource
//
if (S_OK != sc)
continue;
}
if (fCheckDestinationAccess)
{
//$ REVIEW:
//
// We have a very nasty case that we need to be
// able to handle...
//
// If a virtual root already exists along the path of
// the destination, we need to redirect out destination
// path to the vrpath of that virtual root.
//
// Look for a virtual root matching the destination url,
// and set the redirect path if need be.
//
if (pmu->FFindVRootFromUrl(di.PwszUriDestination(), pcvrDestination))
{
MCDTrace ("Dav: MCD: destination url maps to virtual root\n");
// All access checking, including scriptmap honors are handled
// by ScCheckMoveCopyDeleteAccess()
//
// Redirect the destination
//
pcvrDestination->CchGetVRPath (&pwszDestinationRedirect);
}
//
//$ REVIEW: end
// Same kind of deal -- check our access rights and
// only push down into the directory if and only if
// we have access.
//
sc = ScCheckMoveCopyDeleteAccess (pmu,
di.PwszUriDestination(),
pcvrDestination.get(),
di.FDirectory(),
TRUE, // check scriptmap on dest
MD_ACCESS_WRITE,
&scItem,
msr);
if (FAILED (sc))
goto ret;
// If things were not 100%, don't process this resource
//
if (S_OK != sc)
continue;
}
MCDTrace ("Dav: MCD: moving/copying '%S' to '%S'\n",
di.PwszSource(),
pwszDestinationRedirect);
// If we are moving, then just try the generic MoveFileW(),
// and if it fails, then do it piecewise...
//
if (!fMove ||
fCheckAccess ||
fCheckDestinationAccess ||
!DavMoveFile (di.PwszSource(),
pwszDestinationRedirect,
dwReplace))
{
scItem = S_OK;
// If we found another directory, then iterate on it
//
if (di.FDirectory())
{
// We need to create the sister directory in
// the destination directory
//
if (DavCreateDirectory (pwszDestinationRedirect, NULL) || plth)
{
scItem = ScCopyProps (pmu,
di.PwszSource(),
pwszDestinationRedirect,
TRUE);
if (FAILED (scItem))
{
// Do our best to remove the turd directory
//
DavRemoveDirectory (pwszDestinationRedirect);
}
// For all MOVEs push the directory
//
if (!FAILED (scItem) && fMove)
{
lst.push_back (AppendChainedSz(sb, di.PwszSource()));
}
}
else
{
DebugTrace ("Dav: MCD: failed to create directory\n");
scItem = HRESULT_FROM_WIN32(GetLastError());
}
if (FAILED (scItem))
{
// Add to the reponse XML
//
sc = ScAddMultiFromUrl (msr,
pmu,
di.PwszUri(),
HscFromHresult(scItem),
di.FDirectory());
if (FAILED (sc))
goto ret;
sc = W_DAV_PARTIAL_SUCCESS;
}
}
else
{
// Copy the file
//
if (!DavCopyFile (di.PwszSource(),
pwszDestinationRedirect,
0 != dwReplace))
{
DWORD dw = GetLastError();
scItem = HRESULT_FROM_WIN32(dw);
// If it's a sharing (lock) violation, AND we have a
// locktoken parser (plth) AND it has a locktoken for
// this path (lth.GetToken(pwsz)), copy it manually.
//
if (plth &&
(ERROR_SHARING_VIOLATION == dw || ERROR_FILE_EXISTS == dw))
{
scItem = ScDoLockedCopy (pmu,
plth,
di.PwszSource(),
pwszDestinationRedirect);
}
}
// In the case of a move, handle the potentially
// locked source.
//
if (!FAILED (scItem) && fMove)
{
LARGE_INTEGER liLockID;
// If we have a locktoken for this path, then we really
// want to try and release the lock for the source and
// delete the file.
//
if (plth)
{
// Find the lock...
//
scItem = plth->HrGetLockIdForPath (di.PwszSource(),
GENERIC_WRITE,
&liLockID);
if (SUCCEEDED(scItem))
{
// ... and drop it on the floor...
//
scItem = CSharedLockMgr::Instance().HrDeleteLock(pmu->HitUser(),
liLockID);
if (SUCCEEDED(scItem))
{
// ... and try to delete the source again.
//
if (!DavDeleteFile (di.PwszSource()))
{
scItem = HRESULT_FROM_WIN32(GetLastError());
}
}
}
}
else
{
// ... and try to delete the source again.
//
if (!DavDeleteFile (di.PwszSource()))
{
scItem = HRESULT_FROM_WIN32(GetLastError());
}
}
}
if (FAILED (scItem))
{
// If the file that failed to be copied was a hidden
// and/or system file, then it was more than likely a
// trigger file, but even as such, if the file has the
// hidden attribute, we don't want to partial report
// the failure.
//
if (!di.FHidden())
{
sc = ScAddMultiFromUrl (msr,
pmu,
di.PwszUri(),
HscFromHresult(scItem),
di.FDirectory(),
fMove);
if (FAILED (sc))
goto ret;
sc = W_DAV_PARTIAL_SUCCESS;
}
}
}
}
else // !fMove || fCheckAccess || fCheckDestinationAccess || !MoveFileEx()
{
// Again, this is the cool bit. If we got here, then
// there is no need to delve down into this particular
// branch of the tree.
//
// To accomplish this, we are going to lie in a gentle
// way. By setting scItem to a failure condition we are
// preventing the extra work.
//
scItem = HRESULT_FROM_WIN32(ERROR_FILE_EXISTS);
}
}
// Now that all the files are moved or copied, the pushed directories
// can be removed.
//
while (!lst.empty())
{
Assert (fMove);
MCDTrace ("Dav: MCD: removing '%S'\n", lst.back());
// Try to delete the dir. If it doesn't delete, check our
// "skipped because of a lock above" flag before complaining.
//
//$ LATER: Fix this to actually lookup the dir path in the
// lockcache (using "fPathLookup").
//
if (!DavRemoveDirectory (lst.back()))
{
DebugTrace ("Dav: MCD: failed to delete directory\n");
// Add to the reponse XML
//
sc = ScAddMulti (msr,
pmu,
lst.back(),
NULL,
HscFromLastError(GetLastError()),
TRUE, // We know it's a directory
pcvrTranslateSrc);
if (FAILED (sc))
goto ret;
sc = W_DAV_PARTIAL_SUCCESS;
}
lst.pop_back();
}
ret:
return sc;
}
SCODE
ScMoveCopyDirectoryAndChildren (
/* [in] */ IMethUtil* pmu,
/* [in] */ LPCWSTR pwszUrl,
/* [in] */ LPCWSTR pwszSrc,
/* [in] */ LPCWSTR pwszUrlDst,
/* [in] */ LPCWSTR pwszDst,
/* [in] */ BOOL fMove,
/* [in] */ DWORD dwReplace,
/* [in] */ BOOL fCheckAccess,
/* [in] */ BOOL fCheckDestinationAccess,
/* [in] */ CVRoot* pcvrTranslateDestination,
/* [in] */ DWORD dwAcc,
/* [in] */ CXMLEmitter& msr,
/* [in] */ LONG lDepth,
/* [in] */ CParseLockTokenHeader* plth) // Usually NULL -- no locktokens to worry about
{
BOOL fPartial = FALSE;
SCODE sc = S_OK;
// Move/Copy the main body first
//
MCDTrace ("Dav: copying '%S' to '%S'\n", pwszSrc, pwszDst);
sc = ScMoveCopyDirectory (pmu,
pwszUrl,
pwszSrc,
pwszUrlDst,
pwszDst,
fMove,
dwReplace,
fCheckAccess,
fCheckDestinationAccess,
dwAcc,
NULL, // translations are pmu based
pcvrTranslateDestination,
msr,
lDepth,
plth);
if (!FAILED (sc) && (lDepth != DEPTH_ZERO))
{
Assert (lDepth == DEPTH_INFINITY);
// Enumerate the child vroots and perform the
// deletion of those directories as well
//
ChainedStringBuffer<WCHAR> sb;
CVRList vrl;
UINT cchUrl = static_cast<UINT>(wcslen (pwszUrl));
UINT cchDstUrl = static_cast<UINT>(wcslen (pwszUrlDst));
UINT cchDstPath = static_cast<UINT>(wcslen (pwszDst));
// Cleanup the list such that our namespaces are in
// a reasonable order.
//
(void) pmu->ScFindChildVRoots (pwszUrl, sb, vrl);
vrl.sort();
for ( ; !FAILED(sc) && !vrl.empty(); vrl.pop_front())
{
auto_ref_ptr<CVRoot> pcvrDst;
auto_ref_ptr<CVRoot> pcvrSrc;
CResourceInfo cri;
CStackBuffer<WCHAR,128> pwszChildDstT;
LPCWSTR pwszChildDst;
LPCWSTR pwszChildPath;
LPCWSTR pwszChildUrl;
SCODE scItem;
UINT cchVRoot;
// Remember any partial returns
//
if (W_DAV_PARTIAL_SUCCESS == sc)
fPartial = TRUE;
if (pmu->FGetChildVRoot (vrl.front().m_pwsz, pcvrSrc))
{
Assert (fCheckAccess);
cchVRoot = pcvrSrc->CchGetVRoot (&pwszChildUrl);
sc = ScCheckMoveCopyDeleteAccess (pmu,
pwszChildUrl,
pcvrSrc.get(),
TRUE, // directory
TRUE, // check scriptmaps
dwAcc,
&scItem,
msr);
if (FAILED (sc))
goto ret;
// If things were not 100%, don't process this resource
//
if (S_OK != sc)
continue;
// We now have to figure out how we can really do this!
//
// The source path and url bits are easy. The destination
// path, on the other hand, is a pain. It is the original
// destination root with the delta between the source root
// and the child's url path. Huh?
//
// Ok, here is an example:
//
// Source url: /misc
// Source root: c:\inetpub\wwwroot\misc
// Dest. root: c:\inetpub\wwwroot\copy
//
// Child url: /misc/blah
//
// In this example the childs, destination path would need to
// be:
//
// Child dest.: c:\inetpub\wwwroot\copy\blah
//
//$ REVIEW:
//
// And the real pain here is that the child path could already
// exist, but not match the namespace path. I am not too sure
// how to handle that eventuality at this point.
//
Assert (cchUrl < cchVRoot);
//
// Construct the new destination url
//
UINT cchDest = cchVRoot - cchUrl + cchDstUrl + 1;
CStackBuffer<WCHAR,128> pwszChildUrlDst;
if (NULL == pwszChildUrlDst.resize(CbSizeWsz(cchDest)))
{
sc = E_OUTOFMEMORY;
goto ret;
}
memcpy (pwszChildUrlDst.get(), pwszUrlDst, cchDstUrl * sizeof(WCHAR));
memcpy (pwszChildUrlDst.get() + cchDstUrl, pwszChildUrl + cchUrl, (1 + cchDest - cchDstUrl) * sizeof(WCHAR));
if (fCheckDestinationAccess)
{
sc = ScCheckMoveCopyDeleteAccess (pmu,
pwszChildUrlDst.get(),
pcvrSrc.get(),
TRUE, // directory
TRUE, // check scriptmap on dest
MD_ACCESS_WRITE,
&scItem,
msr);
if (FAILED (sc))
goto ret;
// If things were not 100%, don't process this resource
//
if (S_OK != sc)
continue;
}
// And now that we have perfomed the forbidden dance... we
// have to go back and see if the destination url actually
// refers to a new child virtual root as well.
//
if (pmu->FFindVRootFromUrl (pwszChildUrlDst.get(), pcvrDst))
{
MCDTrace ("Dav: MCD: destination url maps to virtual root\n");
// Access checking, as always is handled in ScCheckM/C/DAccess()
// So all we need to do here is setup the destination path
//
pcvrDst->CchGetVRPath (&pwszChildDst);
}
else
{
// We actually need to construct a physical path from
// the url and current destination path.
//
cchDest = cchDstPath + cchVRoot - cchUrl + 1;
if (NULL == pwszChildDstT.resize(CbSizeWsz(cchDest)))
{
sc = E_OUTOFMEMORY;
goto ret;
}
memcpy (pwszChildDstT.get(), pwszDst, cchDstPath * sizeof(WCHAR));
memcpy (pwszChildDstT.get() + cchDstPath, pwszChildUrl + cchUrl, (cchVRoot - cchUrl) * sizeof(WCHAR));
pwszChildDstT[cchDstPath + cchVRoot - cchUrl] = L'\0';
// We also now need to rip through the trailing part of the
// path one more time, translating all '/' to '\\' as we go.
//
for (WCHAR* pwch = pwszChildDstT.get() + cchDstPath;
NULL != (pwch = wcschr (pwch, L'/'));
)
{
*pwch++ = L'\\';
}
pwszChildDst = pwszChildDstT.get();
}
// Well, now we should be able to continue the MOVE/COPY
//
pcvrSrc->CchGetVRPath (&pwszChildPath);
sc = ScMoveCopyDirectory (pmu,
pwszChildUrl,
pwszChildPath,
pwszChildUrlDst.get(),
pwszChildDst,
fMove,
dwReplace,
fCheckAccess,
fCheckDestinationAccess,
dwAcc,
pcvrSrc.get(),
pcvrDst.get(),
msr,
DEPTH_INFINITY,
plth);
if (FAILED (sc))
{
sc = ScAddMultiFromUrl (msr,
pmu,
pwszChildUrl,
HscFromHresult(sc),
TRUE); // We know it's a directory
if (FAILED (sc))
goto ret;
sc = W_DAV_PARTIAL_SUCCESS;
}
}
}
}
ret:
return ((S_OK == sc) && fPartial) ? W_DAV_PARTIAL_SUCCESS : sc;
}
// Move/Copy -----------------------------------------------------------------
//
void
MoveCopyResource (LPMETHUTIL pmu, DWORD dwAccRequired, BOOL fDeleteSrc)
{
auto_ptr<CParseLockTokenHeader> plth;
auto_ref_ptr<CXMLBody> pxb;
auto_ref_ptr<CXMLEmitter> pxml;
BOOL fCheckDestination = FALSE;
BOOL fCheckSource = FALSE;
BOOL fCreateNew = TRUE;
BOOL fDestinationExists = TRUE; // IMPORTANT: assume exists for location header processing
CResourceInfo criDst;
CResourceInfo criSrc;
CStackBuffer<WCHAR> pwszMBPathDst;
CStackBuffer<WCHAR> pwszMBPathSrc;
CVRoot* pcvrDestination;
DWORD dwAccDest = MD_ACCESS_WRITE;
DWORD dwReplace = 0;
LONG lDepth;
LPCWSTR pwsz;
LPCWSTR pwszDst = NULL;
LPCWSTR pwszDstUrl = NULL;
LPCWSTR pwszSrc = pmu->LpwszPathTranslated();
LPCWSTR pwszSrcUrl = pmu->LpwszRequestUrl();
SCODE sc = S_OK;
SCODE scDest = S_OK;
UINT cch;
UINT uiErrorDetail = 0;
// We don't know if we'll have chunked XML response, defer response anyway
//
pmu->DeferResponse();
// Create an XML doc, NOT chunked
//
pxb.take_ownership (new CXMLBody(pmu));
pxml.take_ownership (new CXMLEmitter(pxb.get()));
// Must set all headers before XML emitting starts
//
pmu->SetResponseHeader (gc_szContent_Type, gc_szText_XML);
pmu->SetResponseCode (HscFromHresult(W_DAV_PARTIAL_SUCCESS),
NULL,
0,
CSEFromHresult(W_DAV_PARTIAL_SUCCESS));
// Do ISAPI application and IIS access bits checking on source
//
sc = pmu->ScIISCheck (pmu->LpwszRequestUrl(), dwAccRequired);
if (FAILED(sc))
{
// Either the request has been forwarded, or some bad error occurred.
// In either case, quit here and map the error!
//
MCDTrace ("Dav: Move/Copy: insufficient access\n");
goto ret;
}
// If there's no valid destination header, it's a bad request.
//
// NOTE: we are asking for the translated url's virtual root if
// it exists. The ECB holds the reference for us, so we do not
// add one or release the one we have!
//
sc = pmu->ScGetDestination (&pwszDstUrl, &pwszDst, &cch, &pcvrDestination);
if (FAILED (sc))
{
MCDTrace ("Dav: Move/Copy: no and/or bad destination header\n");
if (sc != E_DAV_NO_DESTINATION)
{
Assert (pwszDstUrl);
sc = ScAddMultiFromUrl (*pxml,
pmu,
pwszDstUrl,
HscFromHresult(sc),
FALSE); // do not check for trailing slash
if (!FAILED (sc))
sc = W_DAV_PARTIAL_SUCCESS;
}
goto ret;
}
// Get the file attributes for the passed in URI. If it aint there, then
// don't do jack!
//
sc = criSrc.ScGetResourceInfo (pwszSrc);
if (FAILED (sc))
goto ret;
// Get the metabase for the source and destination for later use
//
if ((NULL == pwszMBPathSrc.resize(pmu->CbMDPathW(pwszSrcUrl))) ||
(NULL == pwszMBPathDst.resize(pmu->CbMDPathW(pwszDstUrl))))
{
sc = E_OUTOFMEMORY;
goto ret;
}
pmu->MDPathFromUrlW (pwszSrcUrl, pwszMBPathSrc.get());
pmu->MDPathFromUrlW (pwszDstUrl, pwszMBPathDst.get());
// Get the resource info for the destination up front
//
sc = criDst.ScGetResourceInfo (pwszDst);
if (FAILED (sc))
{
MCDTrace ("Dav: Move/Copy: destination probably did not exist prior to op\n");
// The destination may or may not exist. We will just act like
// it doesn't. However, if we don't have access, then we want to
// stick the error into a 207 body.
//
fDestinationExists = FALSE;
if ((HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) == sc))
{
sc = ScAddMultiFromUrl (*pxml,
pmu,
pwszDstUrl,
HscFromHresult(sc),
FALSE); // do not check for trailing slash
if (!FAILED (sc))
sc = W_DAV_PARTIAL_SUCCESS;
goto ret;
}
}
// Again, emit all the headers before XML chunking starts
//
if (!fDestinationExists)
{
Assert (pxml->PxnRoot() == NULL);
//$NOTE At this time, we have only the destination URL, the destination
//$NOTE is not created yet, but we do know whether it will be created
//$NOTE as a collection by looking at the source
//
pmu->EmitLocation (gc_szLocation, pwszDstUrl, criSrc.FCollection());
}
//$ SECURITY:
//
// Check to see if the destination is really a short
// filename.
//
sc = ScCheckIfShortFileName (pwszDst, pmu->HitUser());
if (FAILED (sc))
{
DebugTrace ("Dav: MCD: destination is short-filename\n");
sc = ScAddMultiFromUrl (*pxml,
pmu,
pwszDstUrl,
HscFromHresult(sc),
FALSE); // do not check for trailing slash
if (!FAILED (sc))
sc = W_DAV_PARTIAL_SUCCESS;
goto ret;
}
//
//$ SECURITY: end.
//$ SECURITY:
//
// Check to see if the destination is really the default
// data stream via alternate file access.
//
sc = ScCheckForAltFileStream (pwszDst);
if (FAILED (sc))
{
DebugTrace ("Dav: MCD: destination is possible alternate filestream\n");
sc = ScAddMultiFromUrl (*pxml,
pmu,
pwszDstUrl,
HscFromHresult(sc),
FALSE); // do not check for trailing slash
if (!FAILED (sc))
sc = W_DAV_PARTIAL_SUCCESS;
goto ret;
}
//
//$ SECURITY: end.
// See if we have move/copy access at destination
//
if (fDestinationExists && criDst.FCollection())
dwAccDest |= MD_ACCESS_READ;
sc = ScCheckMoveCopyDeleteAccess (pmu,
pwszDstUrl,
pcvrDestination,
fDestinationExists
? criDst.FCollection()
: criSrc.FCollection(),
TRUE, // check scriptmaps on dest.
dwAccDest,
&scDest,
*pxml);
if (sc != S_OK)
goto ret;
// The client must not submit a depth header with any value
// but Infinity
//
lDepth = pmu->LDepth (DEPTH_INFINITY);
if (DEPTH_INFINITY != lDepth)
{
if (fDeleteSrc || (DEPTH_ZERO != lDepth))
{
MCDTrace ("Dav: only 'Depth: inifinity' is allowed for MOVE\n"
"- 'Depth: inifinity' and 'Depth: 0' are allowed for COPY\n");
sc = E_DAV_INVALID_HEADER;
goto ret;
}
}
// See if there is a path conflict
//
if (FPathConflict (pwszSrc, pwszDst))
{
DebugTrace ("Dav: source and dest are in conflict\n");
sc = E_DAV_CONFLICTING_PATHS;
goto ret;
}
// If we were to check either URI for correctness, the only
// real result would be to possibly emit a content-location
// header that would only be invalidated in the case of a
// successful move
//
if (!fDeleteSrc)
{
sc = ScCheckForLocationCorrectness (pmu, criSrc, NO_REDIRECT);
if (FAILED (sc))
goto ret;
}
// This method is gated by If-xxx headers
//
sc = ScCheckIfHeaders (pmu, criSrc.PftLastModified(), FALSE);
if (FAILED (sc))
goto ret;
// Check state headers
//
sc = HrCheckStateHeaders (pmu, pwszSrc, FALSE);
if (FAILED (sc))
{
DebugTrace ("DavFS: If-State checking failed.\n");
goto ret;
}
// If there are locktokens, feed them to a parser object.
//
pwsz = pmu->LpwszGetRequestHeader (gc_szLockToken, TRUE);
if (pwsz)
{
plth = new CParseLockTokenHeader (pmu, pwsz);
Assert(plth.get());
plth->SetPaths (pwszSrc, pwszDst);
}
// Check for deep access issues
//
//$ REVIEW: we wanted to be able to not have to check
// access at each level. However, because of the semantics of
// MOVE/COPY, we have to check each source file for scriptmap
// access. We cannot copy a file that has a scriptmap if the
// they do not have source access.
//
// So we must always check the source of the MOVE/COPY operation.
//
fCheckSource = TRUE;
//
//$ REVIEW: end.
//
// However, we still can try and be optimistic for the destination
//
if (NULL == pwszMBPathDst.resize(pmu->CbMDPathW(pwszDstUrl)))
{
sc = E_OUTOFMEMORY;
goto ret;
}
pmu->MDPathFromUrlW (pwszDstUrl, pwszMBPathDst.get());
if (fDestinationExists || (DEPTH_ONE != pmu->LDepth(DEPTH_INFINITY)))
{
CAccessMetaOp moAccess(pmu, pwszMBPathDst.get(), dwAccDest);
CAuthMetaOp moAuth(pmu, pwszMBPathDst.get(), pmu->MetaData().DwAuthorization());
CIPRestrictionMetaOp moIPRestriction(pmu, pwszMBPathDst.get());
ChainedStringBuffer<WCHAR> sb;
CVRList vrl;
// If we do not have access to COPY/MOVE or
// DELETE anything in the destination, then
// we really shouldn't blindly proceed.
//
sc = moAccess.ScMetaOp();
if (FAILED (sc))
goto ret;
fCheckDestination |= moAccess.FAccessBlocked();
if (!fCheckDestination)
{
// If we do not have the same authorization anywhere along
// the destination as we do for the request url, then we
// really shouldn't blindly proceed.
//
sc = moAuth.ScMetaOp();
if (FAILED (sc))
goto ret;
fCheckDestination |= moAuth.FAccessBlocked();
}
if (!fCheckDestination)
{
// If we do not have the same authorization anywhere along
// the destination as we do for the request url, then we
// really shouldn't blindly proceed.
//
sc = moIPRestriction.ScMetaOp();
if (FAILED (sc))
goto ret;
fCheckDestination |= moAuth.FAccessBlocked();
}
if (!fCheckDestination)
{
// If there are any child virtual roots along
// the destination tree, there is some redirection
// that may need to happen as well.
//
(void) pmu->ScFindChildVRoots (pwszDstUrl, sb, vrl);
fCheckDestination |= !vrl.empty();
}
}
// Determine if we are destructive or not.
//
if (pmu->LOverwrite() & OVERWRITE_YES)
{
dwReplace |= MOVEFILE_REPLACE_EXISTING;
// MoveFileEx does not seem to want to replace existing
// directories.. It returns E_ACCESS_DENIED so we delete
// the existing directory ourselves.
//
if (fDestinationExists)
{
BOOL fDeletedDestination;
// The destination exists already
//
fCreateNew = FALSE;
// If the destination is a directory, delete it.
//
if (criDst.FCollection())
{
// Otherwise, go ahead and delete the directory currently at dest.
//
sc = ScDeleteDirectoryAndChildren (pmu,
pwszDstUrl,
pwszDst,
fCheckDestination,
dwAccDest,
DEPTH_INFINITY,
*pxml,
pcvrDestination,
&fDeletedDestination,
plth.get(), // DO use locktokens, if any exist.
FALSE); // Do NOT drop locks. Just skip them.
if (sc != S_OK)
{
DebugTrace("DavFS: MOVE failed to pre-delete destination directory.\n");
goto ret;
}
}
else
{
// If the destination is locked (ERROR_SHARING_VIOLATION),
// DO NOT catch it here. We'll handle it below....
//
if (!DavDeleteFile (pwszDst))
{
DWORD dw = GetLastError();
if (ERROR_ACCESS_DENIED == dw)
{
sc = HRESULT_FROM_WIN32(dw);
goto ret;
}
}
}
}
}
// Do the move/copy. If the operation is either a move, or the source
// is a collection, then call out to do the diry work.
//
MCDTrace ("DavFS: MCD: moving copying '%S' to '%S'\n", pwszSrc, pwszDst);
if (criSrc.FCollection())
{
sc = ScMoveCopyDirectoryAndChildren (pmu,
pwszSrcUrl,
pwszSrc,
pwszDstUrl,
pwszDst,
fDeleteSrc,
dwReplace,
fCheckSource,
fCheckDestination,
pcvrDestination,
dwAccRequired,
*pxml,
lDepth,
plth.get());
if (FAILED (sc))
goto ret;
}
else
{
// Well this should be the move/copy of a single file
//
if (!fDeleteSrc || !DavMoveFile (pwszSrc, pwszDst, dwReplace))
{
if (!DavCopyFile (pwszSrc, pwszDst, (0 == dwReplace)))
{
DWORD dw = GetLastError();
DebugTrace ("Dav: failed to copy file\n");
// If it's a sharing violation (lock-caused error),
// AND we have a locktoken parser (plth), handle the copy.
//
if ((ERROR_SHARING_VIOLATION == dw) && plth.get())
{
// Check if any locktokens apply to these file,
// and try the copy using the locks from the cache.
//
sc = ScDoLockedCopy (pmu, plth.get(), pwszSrc, pwszDst);
}
else
{
if ((dw == ERROR_FILE_EXISTS) ||
(dw == ERROR_ALREADY_EXISTS))
{
sc = E_DAV_OVERWRITE_REQUIRED;
}
else
sc = HRESULT_FROM_WIN32(dw);
}
// If the file-manual-move failed, we'll hit here.
//
if (FAILED (sc))
{
DebugTrace("Dav: MCD: move/copy failed. Looking for lock conflicts.\n");
// Special work for '423 Locked' responses -- fetch the
// comment & set that as the response body.
//
if (FLockViolation (pmu, sc, pwszSrc,
GENERIC_READ | GENERIC_WRITE))
{
sc = E_DAV_LOCKED;
goto ret;
}
else
{
// Test the destination too.
// However, if the dest is locked, do NOT add
// the lockinfo as the body -- we have to list the dest
// URI as the problem, so we need to have a multi-status
// body, and we put a plain 423 Locked node under there.
// (NOTE: Yes, this means we can't use FLockViolation.
// Instead, we have to check "by hand".)
//
if (CSharedLockMgr::Instance().FGetLockOnError (
pmu,
pwszDst,
GENERIC_READ | GENERIC_WRITE))
{
sc = ScAddMultiFromUrl (*pxml,
pmu,
pwszDstUrl,
HscFromHresult(E_DAV_LOCKED),
FALSE); // We know it's not a directory
if (!FAILED (sc))
sc = W_DAV_PARTIAL_SUCCESS;
goto ret;
}
}
}
} // end !DavCopyFile
if (SUCCEEDED(sc) && fDeleteSrc)
{
// Delete the source file by hand.
// (fDeleteSrc means this is a MOVE, not a COPY.)
//
// Move the content-types only if the source is
// deleted, otherwise treat it as a copy the of
// the content-type
//
if (!DavDeleteFile (pwszSrc))
{
DWORD dwLastError = GetLastError();
DebugTrace ("Dav: failed to delete file (%d)\n", dwLastError);
// If it's a sharing (lock) violation, AND we have a
// locktoken for this path (lth.GetToken(pwsz))
// skip this path.
//
if ((ERROR_SHARING_VIOLATION == dwLastError) && plth)
{
LARGE_INTEGER liLockID;
// If we have a locktoken for this path, drop
// the lock and try to delete the source again.
//
if (SUCCEEDED(plth->HrGetLockIdForPath (pwszSrc,
GENERIC_WRITE,
&liLockID)))
{
// This item is locked in our cache.
// We are doing a MOVE, so DO delete the lock
// and try again.
//
if (SUCCEEDED(CSharedLockMgr::Instance().HrDeleteLock(pmu->HitUser(),
liLockID)))
{
// Try the delete again, and set/clear our error
// code for testing below.
// This error code will control whether we
// add this error to our XML.
//
if (DavDeleteFile(pwszSrc))
{
dwLastError = ERROR_SUCCESS;
}
else
{
dwLastError = GetLastError();
}
}
}
// else, record the error in our XML.
//
}
if (ERROR_SUCCESS != dwLastError)
{
// We could not work around all the errors.
// Add this failure to the XML.
//
sc = ScAddMultiFromUrl (*pxml,
pmu,
pwszSrcUrl,
HscFromLastError(dwLastError),
FALSE); // We know it's not a directory
if (FAILED (sc))
goto ret;
// It is partial sucess if we are here. And do not fail out
// yet as we still need to take care of content types.
//
sc = W_DAV_PARTIAL_SUCCESS;
}
}
}
}
}
// Now that we're done mucking around in the filesystem,
// muck around in the metabase.
// (Delete any destination content-types, then copy/move
// the source content-types on over.)
//
// Delete the content-types for the destination
//
{
Assert (pwszMBPathDst.get());
CContentTypeMetaOp amoContent(pmu, pwszMBPathDst.get(), NULL, TRUE);
(void) amoContent.ScMetaOp();
}
// Move/copy the content-type
//
//$ REVIEW: I am not so sure what can be done when this fails
//
{
Assert (pwszMBPathDst.get());
// Only delete the source content-types if everything has been 100%
// successfull up to this point.
//
CContentTypeMetaOp amoContent(pmu,
pwszMBPathSrc.get(),
pwszMBPathDst.get(),
(fDeleteSrc && (S_OK == sc)));
(void) amoContent.ScMetaOp ();
}
//
//$ REVIEW: end.
ret:
if (pxml.get() && pxml->PxnRoot())
{
pxml->Done();
// No more header can be sent after XML chunking started
}
else
{
if (SUCCEEDED (sc))
sc = fCreateNew ? W_DAV_CREATED : W_DAV_NO_CONTENT;
pmu->SetResponseCode (HscFromHresult(sc), NULL, uiErrorDetail, CSEFromHresult(sc));
}
pmu->SendCompleteResponse();
}
/*
* DAVMove()
*
* Purpose:
*
* Win32 file system implementation of the DAV MOVE method. The
* MOVE method results in the moving of a resource from one location
* to another. The response is used to indicate the success of the
* call.
*
* Parameters:
*
* pmu [in] pointer to the method utility object
*
* Notes:
*
* In the file system implementation, the MOVE method maps directly
* to the Win32 RenameFile() method.
*/
void
DAVMove (LPMETHUTIL pmu)
{
MoveCopyResource (pmu,
MD_ACCESS_READ|MD_ACCESS_WRITE, // src access required
TRUE); // fDeleteSource
}
/*
* DAVCopy()
*
* Purpose:
*
* Win32 file system implementation of the DAV COPY method. The
* COPY method results in the copying of a resource from one location
* to another. The response is used to indicate the success of the
* call.
*
* Parameters:
*
* pmu [in] pointer to the method utility object
*
* Notes:
*
* In the file system implementation, the COPY method maps directly
* to the Win32 CopyFile() API for a single file. Directory copies
* are done via a custom process.
*/
void
DAVCopy (LPMETHUTIL pmu)
{
MoveCopyResource (pmu,
MD_ACCESS_READ, // src access required
FALSE); // fDeleteSource
}