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
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
|
|
}
|