/* * 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 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(wcslen(pwszUrl)), pwszUrl, pszUrlEscaped); if (FAILED (sc)) goto ret; // Add the url value // cchUrl = static_cast(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(&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(&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 sb; SCODE sc = S_OK; SCODE scItem = S_OK; std::list > 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 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 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 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 pwszContentType; CMDObjectHandle mdohDest(*m_pecb); CStackBuffer pwsz; DWORD cb = sizeof(prgchContentType); LPBYTE pbValue = reinterpret_cast(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(g_heap.Alloc(cb)); pbValue = reinterpret_cast(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(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 pcvrDestination(pcvrTranslateDst); ChainedStringBuffer sb; LPCWSTR pwszDestinationRedirect = NULL; SCODE sc = S_OK; SCODE scItem = S_OK; std::list > 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 sb; CVRList vrl; UINT cchUrl = static_cast(wcslen (pwszUrl)); UINT cchDstUrl = static_cast(wcslen (pwszUrlDst)); UINT cchDstPath = static_cast(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 pcvrDst; auto_ref_ptr pcvrSrc; CResourceInfo cri; CStackBuffer 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 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 plth; auto_ref_ptr pxb; auto_ref_ptr 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 pwszMBPathDst; CStackBuffer 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 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 }