//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
/*--
Module Name: davmain.cpp
Abstract: WebDAV main verb handeling routines
--*/

#include "httpd.h"

const DWORD g_fWebDavModule = TRUE;

//
// Global initialization and deinitialization routines
// 

BOOL CGlobalVariables::InitWebDav(CReg *pWebsite) 
{
    // WebDAV is enabled by default, but can be disabled via registry.
    if (!pWebsite->ValueDW(RV_WEBDAV,TRUE))
    {
        return FALSE;
    }

    if (m_fRootSite && !g_pFileLockManager)
    {
        g_pFileLockManager = new CWebDavFileLockManager;
    }

    if (! (g_pFileLockManager && g_pFileLockManager->IsInitialized()))
    {
        return FALSE;
    }

    m_dwDavDefaultLockTimeout = pWebsite->ValueDW(RV_DEFAULT_DAV_LOCK,DEFAULT_DAV_LOCK_TIMEOUT);
    m_dwDavDefaultLockTimeout = m_dwDavDefaultLockTimeout / 1000; // store number in seconds.
    return TRUE;
}

void CGlobalVariables::DeInitWebDav(void) 
{
    DEBUGCHK(m_fRootSite);
    DEBUGCHK(g_fState == SERVICE_STATE_UNLOADING);

    if (g_pFileLockManager)
    {
        delete g_pFileLockManager;
    }

    return;
}

//
// Map a verb request into appropriate DAV handler
//
typedef BOOL (WINAPI *PFN_DAVEXECUTE)(CWebDav *pThis);

BOOL DavUnsupported(CWebDav *pThis) 
{ 
    return pThis->DavUnsupported(); 
}
BOOL DavOptions(CWebDav *pThis) 
{ 
    return pThis->DavOptions() ; 
}
BOOL DavPut(CWebDav *pThis) 
{ 
    return pThis->DavPut() ; 
}
BOOL DavMove(CWebDav *pThis) 
{ 
    return pThis->DavMove() ; 
}
BOOL DavCopy(CWebDav *pThis) 
{ 
    return pThis->DavCopy() ; 
}
BOOL DavDelete(CWebDav *pThis) 
{ 
    return pThis->DavDelete() ; 
}
BOOL DavMkcol(CWebDav *pThis) 
{ 
    return pThis->DavMkcol() ; 
}
BOOL DavPropfind(CWebDav *pThis) 
{ 
    return pThis->DavPropfind() ; 
}
BOOL DavProppatch(CWebDav *pThis) 
{ 
    return pThis->DavProppatch() ; 
}
BOOL DavLock(CWebDav *pThis) 
{ 
    return pThis->DavLock() ; 
}
BOOL DavUnlock(CWebDav *pThis) 
{ 
    return pThis->DavUnlock() ; 
}

#if 0
BOOL DavSearch(CWebDav *pThis) 
{ 
    return pThis->DavSearch() ; 
}
BOOL DavSubscribe(CWebDav *pThis) 
{ 
    return pThis->DavUnsubscribe() ; 
}
BOOL DavUnsubscribe(CWebDav *pThis) 
{ 
    return pThis->DavUnsubscribe() ; 
}
BOOL DavPoll(CWebDav *pThis) 
{ 
    return pThis->DavPoll() ; 
}
BOOL DavBDelete(CWebDav *pThis) 
{ 
    return pThis->DavBDelete() ; 
}
BOOL DavBCopy(CWebDav *pThis) 
{ 
    return pThis->DavBCopy() ; 
}
BOOL DavBMove(CWebDav *pThis) 
{ 
    return pThis->DavBMove() ; 
}
BOOL DavBProppatch(CWebDav *pThis) 
{ 
    return pThis->DavBProppatch() ; 
}
BOOL DavBPropfind(CWebDav *pThis) 
{ 
    return pThis->DavBPropfind() ; 
}
BOOL DavX_MS_Enumatts(CWebDav *pThis) 
{ 
    return pThis->DavX_MS_Enumatts() ; 
}
#endif // 0

// NOTE: this follows same order as "enum VERB" in request.h
PFN_DAVEXECUTE davTable[] = 
{
    DavUnsupported,
    DavUnsupported,
    DavUnsupported,
    DavUnsupported,
    DavOptions,
    DavPut,
    DavMove,
    DavCopy,
    DavDelete,
    DavMkcol,
    DavPropfind,
    DavProppatch,
//    DavSearch,  // SharePoint + IIS Specific
    DavLock,
    DavUnlock,
#if 0 // unsupported DAV vebs
    DavSubscribe,
    DavUnsubscribe,
    DavPoll,
    DavBDelete,
    DavBCopy,
    DavBMove,
    DavBProppatch,
    DavBPropfind,
    DavX_MS_Enumatts,
#endif // 0 
};

// Webdav entry point web server calls into.
// Note that WebDav handles the vast majority of errors it hits with descriptive 
// XML tags and a "207 Multistatus" response.  HandleWebDav() only returns
// FALSE when it needs the web server to send a 4XX or 5XX error code to the client.
BOOL CHttpRequest::HandleWebDav(void) 
{
    CWebDav dav(this);

    DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: WebDAV called to handle HTTP verb <<%a>>\r\n",m_pszMethod));

    DEBUGCHK(m_idMethod <= VERB_LAST_VALID && m_idMethod != VERB_UNKNOWN);
    DEBUGCHK(m_idMethod <= ARRAYSIZEOF(davTable));
    DEBUGCHK(m_bufRespBody.m_iNextIn    == 0);
    DEBUGCHK(m_rs == STATUS_OK);

    if (dav.IsCollection() || dav.IsNull())
    {
        RemoveTrailingSlashToPhysicalPath();
    }

    if (m_fDisabledNagle == FALSE) 
    {
        // To increase performance of WebDAV, disable Nagling.
        BOOL fDisable = TRUE;
        int iErr;

        if (ERROR_SUCCESS != (iErr = setsockopt(m_socket,IPPROTO_TCP,TCP_NODELAY,(const char*)&fDisable,sizeof(BOOL)))) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: setsockopt(0x%08x,TCP_NODELAY) failed, err = 0x%08x\r\n",m_socket,iErr));
            DEBUGCHK(0);
        }
        m_fDisabledNagle = TRUE;
    }

    BOOL fRet = (davTable[m_idMethod])(&dav);
    if (dav.m_fXMLBody)
    {
        dav.FinalizeMultiStatusResponse();
    }
    
    dav.FinalConsistencyCheck(fRet);
    DEBUGCHK(! g_pFileLockManager->IsLocked());

    DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: WebDav processing completed.  Returning %d to HTTPD, status code = %d\r\n",fRet,rgStatus[m_rs].dwStatusNumber));
    return fRet;
}

// 
//  Handle HTTP verbs
// 

//
// DavOptions
// OPTIONS requests the capabilities of the server or the available operations on a given resource.
//

const CHAR cszDavCompliance[] = "DAV:";
const CHAR cszCompliance[]    = "1, 2";

const CHAR cszCache_Control[] = "Cache-Control:";
const CHAR cszCache_Control_Private[]    = "private";
// const CHAR cszCache_Control_NoCache[] = "no-cache";


BOOL CWebDav::DavOptions(void) 
{
    BOOL fRet = FALSE;
    BOOL fWriteAccess = (m_pRequest->GetPerms() & HSE_URL_FLAGS_WRITE);

    // alloc big enough buffer now so we don't have to check every AddHeader() call.
    if (!m_pRequest->m_bufRespHeaders.AllocMem(HEADERBUFSIZE)) 
    {
        SetStatus(STATUS_INTERNALERR);
        goto done;
    }

    // "*" requests options for the entire server
    if (0 == strcmp(m_pRequest->m_pszURL,"*") || 0 == strcmp(m_pRequest->m_pszURL,"/*")) 
    {
        m_pRequest->m_bufRespHeaders.AppendData(cszAllow,ccAllow);
        WritePublicOptions();

        m_pRequest->m_bufRespHeaders.AddHeader(cszAccept_Ranges,cszBytes);
        fRet = TRUE;
        goto done; // make sure common case headers are handled
    }

    if (m_pRequest->GetPerms() & HSE_URL_FLAGS_READ) 
    {
        if (! CheckIfHeaders()) 
        {
            SetStatus(STATUS_BADREQ);
            fRet = FALSE;
            goto done;
        }
    }

    // It doesn't make sense to check against LOCKs in this case, as OPTIONS doesn't modify resource in any way.

    // Print out "Allow: " header
    m_pRequest->m_bufRespHeaders.AppendData(cszAllow,ccAllow);
    m_pRequest->m_bufRespHeaders.AppendCHAR(' ');
    WriteSupportedVerb(VERB_OPTIONS,TRUE);
    WriteSupportedVerb(VERB_GET);
    WriteSupportedVerb(VERB_HEAD);

    if (fWriteAccess) 
    {
        WriteSupportedVerb(VERB_DELETE);

        if (!IsCollection())
        {
            WriteSupportedVerb(VERB_PUT);
        }
    }

    if (m_pRequest->IsScript(TRUE))
    {
        WriteSupportedVerb(VERB_POST);
    }

    if (! IsCollection())
    {
        WriteSupportedVerb(VERB_MKCOL);
    }

    if (! IsNull()) 
    {
        WriteSupportedVerb(VERB_COPY);

        if (fWriteAccess) 
        {
            WriteSupportedVerb(VERB_MOVE);
            WriteSupportedVerb(VERB_PROPPATCH);
//            m_pRequest->m_bufRespHeaders.AppendData(cszDavSearch,SVSUTIL_CONSTSTRLEN(cszDavSearch));
        }
    }

    WriteSupportedVerb(VERB_LOCK);
    WriteSupportedVerb(VERB_UNLOCK);
    m_pRequest->m_bufRespHeaders.AppendData(cszCRLF,ccCRLF);

    m_pRequest->m_bufRespHeaders.AddHeader(cszAccept_Ranges,(m_rt == RT_COLLECTION) ? cszNone : cszBytes);
    
    fRet = TRUE;
done:
    if (fRet) 
    {
        // common headers.
//        m_pRequest->m_bufRespHeaders.AddHeader(cszDasl,cszSqlQuery);
        m_pRequest->m_bufRespHeaders.AppendData(cszPublic, ccPublic);
        WritePublicOptions();

        m_pRequest->m_bufRespHeaders.AddHeader(cszDavCompliance, cszCompliance);
        m_pRequest->m_bufRespHeaders.AddHeader(cszCache_Control, cszCache_Control_Private);

        CHttpResponse resp(m_pRequest);
        resp.SetZeroLenBody();
        resp.SendResponse();
    }

    DEBUGCHK(m_pRequest->m_bufRespHeaders.m_iNextIn < HEADERBUFSIZE);     // make sure we didn't use more than we claimed we would...
    return fRet;
}


BOOL CWebDav::DavPropfind(void) 
{ 
    BOOL fRet = FALSE;

    if (! (m_pRequest->GetPerms() & HSE_URL_FLAGS_READ)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: PROPFIND returns access denied because HSE_URL_FLAGS_READ is not set\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

#if 0
    if (! m_pRequest->m_dwContentLength) 
    {
        // BUGBUG : No chunked encoding support currently, so this won't help.  Look at other clients 
        // and how frequently POST is used to see if we need it.  If add this, review every place!!
        if (! m_pRequest->FindHttpHeader(cszTransfer_Encoding,ccTransfer_Encoding)) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: PROPFIND fails because no content-length or transfer-encoding was set\r\n"));
            SetStatus(STATUS_REQUEST_TOO_LARGE);
            goto done;
        }
    }
#endif

    if (m_rt == RT_NULL) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: PROPFIND fails because file %s does not exist\r\n",m_pRequest->m_wszPath));
        SetStatus(STATUS_NOTFOUND);
        goto done;
    }

    if (m_rt == RT_COLLECTION) 
    {
        // depth only matters for directories
        if (! GetDepth()) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: PROPFIND fails because depth not set for directory\r\n"));
            SetStatus(STATUS_BADREQ);
            goto done;
        }
    }

    if (! CheckIfHeaders()) 
    {
        SetStatus(STATUS_PRECONDITION_FAILED);
        goto done;
    }

    if (! AddContentLocationHeaderIfNeeded())
    {
        goto done;
    }

    if (! HasBody())
    {
        fRet = SendProperties(DAV_PROP_ALL,FALSE);
    }
    else 
    {
        fRet = SendSubsetOfProperties();
    }

done:
    return fRet;
}

BOOL CWebDav::DavProppatch(void) 
{
    BOOL fRet     = FALSE;
    BOOL fProceed = TRUE;

    CPropPatchParse cPropPatch(this);
    CWebDavFileNode *pFileNode = NULL;

    if (! (m_pRequest->GetPerms() & HSE_URL_FLAGS_WRITE)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: PROPPATCH fails, VRoot does not have HSE_URL_FLAGS_WRITE permission\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

    if (! AddContentLocationHeaderIfNeeded())
    {
        goto done;
    }

    if (! CheckIfHeaders()) 
    {
        DEBUGCHK(m_pRequest->m_rs != STATUS_OK);
        goto done;
    }

    if (! CheckLockHeader()) 
    {
        DEBUGCHK(m_pRequest->m_rs != STATUS_OK);
        goto done;
    }

    // We need to explicitly look this file up in our lock manager cache because
    // holding a file handle to a file (even with minimal sharing) will not prevent
    // a call to SetFileAttributes on it from succeeding.

    g_pFileLockManager->Lock();
    if (! g_pFileLockManager->CanPerformLockedOperation(m_pRequest->m_wszPath,NULL,&m_lockIds,m_nLockTokens,&pFileNode,NULL)) 
    {
        DEBUGCHK(pFileNode);
        SetStatusAndXMLBody(STATUS_LOCKED);
        SendLockTags(pFileNode);
        fProceed = FALSE;
    }
    g_pFileLockManager->Unlock();

    if (!fProceed)
    {
        goto done;
    }

    if (ParseXMLBody(&cPropPatch,FALSE)) 
    {
        DEBUGCHK(m_pRequest->m_rs == STATUS_OK);
        fRet = SendPropPatchResponse(STATUS_OK);
    }
    
done:
    return fRet;
}


BOOL CWebDav::DavMkcol(void) 
{ 
    BOOL fRet = FALSE;

    if (! (m_pRequest->GetPerms() & HSE_URL_FLAGS_WRITE)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: MKCOL fails, VRoot does not have HSE_URL_FLAGS_WRITE permission\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

    if (m_pRequest->m_dwContentLength || m_pRequest->FindHttpHeader(cszContent_Type,ccContent_Type)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: MKCOL fails, client has sent body, server doesn't support body\r\n"));
        SetStatus(STATUS_UNSUPPORTED_MEDIA_TYPE);
        goto done;
    }

    if (! CheckIfHeaders()) 
    {
        DEBUGCHK(m_pRequest->m_rs != STATUS_OK);
        goto done;
    }

    if (! CheckLockHeader()) 
    {
        DEBUGCHK(m_pRequest->m_rs != STATUS_OK);
        goto done;
    }

    if (IsSrcFileNameLenTooLong()) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: MKCOl fails, request URI is too long\r\n")); 
        SetStatus(STATUS_REQUEST_TOO_LARGE);
        goto done;
    }

    if (! CreateDirectoryW(m_pRequest->m_wszPath,NULL)) 
    {
        DWORD dwErr = GetLastError();
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: MKCOL fails, CreateDirectoryW fails, GLE=0x%08x\r\n",dwErr));

        if ((dwErr == ERROR_FILE_EXISTS) || (dwErr == ERROR_ALREADY_EXISTS))
        {
            SetStatus(STATUS_METHODNOTALLOWED);
        }
        else if (dwErr == ERROR_PATH_NOT_FOUND)
        {
            SetStatus(STATUS_CONFLICT);
        }
        else
        {
            SetStatus(STATUS_INTERNALERR);
        }
    }
    else 
    {
        AddContentLocationHeader();

        CHttpResponse resp(m_pRequest,STATUS_CREATED);
        resp.SetZeroLenBody();
        resp.SendResponse();
        fRet = TRUE;
    }
    
done:
    return fRet;
}

BOOL CWebDav::DavPut(void) 
{ 
    BOOL   fRet = FALSE;
    HANDLE hFile = INVALID_HANDLE_VALUE;
    BOOL   fFileExists;
    BOOL   fLockedFile = FALSE;

    if (! (m_pRequest->GetPerms() & HSE_URL_FLAGS_WRITE)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: PUT fails, VRoot does not have HSE_URL_FLAGS_WRITE permission\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

    if (m_pRequest->FindHttpHeader(cszContent_Range,ccContent_Range)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: PUT fails, CE DAV does not support \"Content-Range\" setting\r\n"));
        SetStatus(STATUS_NOTIMPLEM);
        goto done;
    }

#if 0
    if (! m_pRequest->m_dwContentLength) 
    { // NOTE: XP redirector frequently sends 0 length files, so this check is not safe.
        // no chunked-encoding currently
        if (! m_pRequest->FindHttpHeader(cszTransfer_Encoding,ccTransfer_Encoding)) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: PUT fails because no content-length or transfer-encoding was set\r\n"));
            SetStatus(STATUS_LENGTH_REQUIRED);
            goto done;
        }
    }
#endif // 0

    if (IsCollection()) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: PUT fails, attempting to write to a directory\r\n"));
        SetStatus(STATUS_CONFLICT);
        goto done;
    }

    if (IsSrcFileNameLenTooLong()) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: PUT fails, request URI is too long\r\n")); 
        SetStatus(STATUS_REQUEST_TOO_LARGE);
        goto done;
    }

    if (! CheckIfHeaders()) 
    {
        DEBUGCHK(m_pRequest->m_rs != STATUS_OK);
        goto done;
    }

    if (! CheckLockHeader()) 
    {
        DEBUGCHK(m_pRequest->m_rs != STATUS_OK);
        goto done;
    }

    if (INVALID_HANDLE_VALUE == (hFile = MyWriteFile(m_pRequest->m_wszPath))) 
    {
        if (CheckLockTokens()) 
        {
            // Does file have a lock and did client send lock tokens to access it?
            CWebDavFileNode *pFileNode;

            g_pFileLockManager->Lock();

            if (g_pFileLockManager->CanPerformLockedOperation(m_pRequest->m_wszPath,NULL,&m_lockIds,m_nLockTokens,&pFileNode,NULL)) 
            {
                DEBUGCHK(g_pFileLockManager->IsLocked());

                hFile = pFileNode->m_hFile;
                SetFilePointer(hFile,0,NULL,FILE_BEGIN);

                fLockedFile = TRUE;
            }

            g_pFileLockManager->Unlock();
        }
    }

    if (INVALID_HANDLE_VALUE == hFile) 
    {
        DWORD dwErr = GetLastError();
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: PUT fails, unable to open file %s, GLE=0x%08x\r\n",m_pRequest->m_wszPath,dwErr));

        SendLockedConflictOrErr(m_pRequest->m_wszPath);    
        goto done;
    }

    fFileExists = (GetLastError() == ERROR_ALREADY_EXISTS);

    if (WriteBodyToFile(hFile,FALSE)) 
    {        
        SetEndOfFile(hFile); // In event we're writing more data to file than existed previously.
    
        CHttpResponse resp(m_pRequest,fFileExists ? STATUS_OK : STATUS_CREATED);
        resp.SetBody(hFile,NULL,0);
        resp.SetZeroLenBody();
        resp.UnSetMimeType();
        resp.SendResponse();
        fRet = TRUE;
    }
    else 
    {
        SetStatus(GLEtoStatus());
    }

done:
    // Close file if its a valid handle, but only if it's not a previously locked file.
    if ((hFile != INVALID_HANDLE_VALUE) && !fLockedFile)
    {
        CloseHandle(hFile);
    }

    return fRet;
}


BOOL CWebDav::DavDelete(void) 
{ 
    BOOL   fRet = FALSE;

    if (! (m_pRequest->GetPerms() & HSE_URL_FLAGS_WRITE)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: DELETE fails, VRoot does not have HSE_URL_FLAGS_WRITE permission\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

    if (IsNull()) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: DELETE fails, file %s does not exist\r\n",m_pRequest->m_wszPath));
        SetStatus(STATUS_NOTFOUND);
        goto done;
    }

    if (! AddContentLocationHeaderIfNeeded())
    {
        goto done;
    }

    if (! CheckIfHeaders()) 
    {
        DEBUGCHK(m_pRequest->m_rs != STATUS_OK);
        goto done;
    }

    if (! CheckLockHeader()) 
    {
        DEBUGCHK(m_pRequest->m_rs != STATUS_OK);
        goto done;
    }

    if (IsCollection()) 
    {
        fRet = DeleteCollection(m_pRequest->m_wszPath,m_pRequest->m_pszURL);
    }
    else 
    { // single file
        if (!DavDeleteFile(m_pRequest->m_wszPath)) 
        {
            // If we fail because we're locked, send this info to the client
            if (! SendLockErrorInfoIfNeeded(m_pRequest->m_wszPath))
            {
                SetStatus(GLEtoStatus());
            }
        }
        else
        {
            fRet = TRUE;
        }
    }
    
done:
    if (fRet) 
    {
        CHttpResponse resp(m_pRequest,STATUS_NOCONTENT);
        resp.SetZeroLenBody();
        resp.SendResponse();
        m_fSetStatus = TRUE;
    }

    // Possible for us to have returned multistatus contained detailed info, 
    // in which case HTTPD shouldn't put err messages on it.
    return IS_STATUS_2XX(m_pRequest->m_rs); 
}

// Unimplemented
BOOL CWebDav::DavUnsupported(void) 
{ 
    SetStatus(STATUS_NOTIMPLEM);
    return FALSE; 
}

BOOL CWebDav::MoveCopyResource(BOOL fDeleteSrc) 
{
    DWORD dwAccess = fDeleteSrc ? (HSE_URL_FLAGS_READ | HSE_URL_FLAGS_WRITE) : HSE_URL_FLAGS_READ;
    BOOL  fRet = FALSE;

    // Pointer to original URL sent in HTTP headers, and ptr to '\0' to set to '\r'
    PSTR  szDestPrefix     = NULL;
    PSTR  szDestURL        = NULL;
    PSTR  szSave           = NULL;
    WCHAR *wszDestPath     = NULL;

    BOOL        fDestExists  = TRUE;
    PVROOTINFO  pVRoot;     // Use destinations VROOT and permission (not m_pRequest's necessarily) to determine what we can do.

    WIN32_FILE_ATTRIBUTE_DATA  wDestAttribs;


    if (! (m_pRequest->GetPerms() & dwAccess)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, action is forbidden\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

    GetOverwrite();

    // Depth for MOVE must be infinite.  For copy it may be 0 or infinite.
    if (!GetDepth() || (fDeleteSrc && (m_Depth != DEPTH_INFINITY)) ||
        (!fDeleteSrc && (m_Depth != DEPTH_INFINITY && m_Depth != DEPTH_ZERO))) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, depth header is corrupt or invalid for verb requested\r\n"));
        SetStatus(STATUS_BADREQ);
        goto done;
    }

    if (! CheckIfHeaders()) 
    {
        DEBUGCHK(m_pRequest->m_rs != STATUS_OK);
        goto done;
    }

    if (! CheckLockHeader()) 
    {
        DEBUGCHK(m_pRequest->m_rs != STATUS_OK);
        goto done;
    }

    if (NULL == (szDestURL = GetDestinationHeader(&szDestPrefix,&szDestURL,&szSave))) 
    {
        DEBUGCHK(! IS_STATUS_2XX(m_pRequest->m_rs));
        goto done;
    }

    if (IsNull()) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, resource to move (%s) does not exist\r\n",m_pRequest->m_wszPath));
        SetStatus(STATUS_NOTFOUND);
        goto done;
    }

    wszDestPath = m_pRequest->m_pVrootTable->URLAtoPathW(szDestURL,&pVRoot);

    if (IsSrcFileNameLenTooLong() || IsFileNameTooLongForFilesys(wszDestPath)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: move/copy fails, source or dest file name too long for filesys\r\n")); 
        SetStatus(STATUS_REQUEST_TOO_LARGE);
        goto done;
    }

    if (!HasAccessToDestVRoot(szDestURL,wszDestPath,pVRoot)) 
    {
        SetStatus(STATUS_UNAUTHORIZED);
        goto done;
    }

    if ((*wszDestPath == '/' || *wszDestPath == '\\') && (*(wszDestPath+1) == 0)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, request set destination as root of filesystem\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

    if (! GetFileAttributesEx(wszDestPath,GetFileExInfoStandard,&wDestAttribs)) 
    {
        if (GetLastError() == ERROR_ACCESS_DENIED) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, GLE=0x%08x\r\n"));
            SetStatus(STATUS_FORBIDDEN);
            goto done;
        }
        fDestExists = FALSE;
    }

    if (!fDestExists) 
    {
        if (!m_pRequest->m_bufRespHeaders.AddHeader(cszLocation,szDestPrefix))
        {
            goto done;
        }
    }

    if (fDestExists && IsDirectory(&wDestAttribs) && 
        !(pVRoot->dwPermissions & HSE_URL_FLAGS_READ)) 
    {

        DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, destination directory does not have read permissions enabled\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

    if (IsPathSubDir(m_pRequest->m_wszPath,m_pRequest->m_ccWPath,wszDestPath,wcslen(wszDestPath))) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, destination or source url is subset of the other path, not allowed\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

    // Only add location header on copy because on move, src will be deleted on success.
    if (!fDeleteSrc && !AddContentLocationHeaderIfNeeded())
    {
        goto done;
    }

    if ((! (m_Overwrite & OVERWRITE_YES)) && fDestExists) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, destination exists but overwrite option is turned off\r\n"));
        SetStatus(STATUS_PRECONDITION_FAILED);
        goto done;
    }

    // delete the destination before copying, per spec.
    if ((m_Overwrite & OVERWRITE_YES) && fDestExists)  
    {
        if (IsDirectory(&wDestAttribs)) 
        {
            if (! DeleteCollection(wszDestPath,szDestURL)) 
            {
                fRet = (m_pRequest->m_rs == STATUS_MULTISTATUS);
                goto done;
            }
        }
        else 
        { // Single file
            if (!DavDeleteFile(wszDestPath)) 
            {
                SendLockedConflictOrErr(wszDestPath);
                goto done;
            }
        }
    }

    if (IsCollection()) 
    {
        DEBUGCHK(! URLHasTrailingSlashW(m_pRequest->m_wszPath));

        CMoveCopyContext cMoveContext(fDeleteSrc,m_pRequest->GetPerms(),pVRoot->dwPermissions,szDestURL,wszDestPath,m_pRequest->m_ccWPath);
        fRet = MoveCopyCollection(&cMoveContext);
    }
    else 
    {
        if (FALSE == MoveCopyFile(fDeleteSrc,m_pRequest->m_wszPath,wszDestPath)) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDav move or copy fails, GLE = 0x%08x\r\n"));
            SendLockedConflictOrErr(wszDestPath,m_pRequest->m_wszPath);
            fRet = (m_pRequest->m_rs == STATUS_MULTISTATUS);
            goto done;
        }
        fRet = TRUE;
    }

done:
    if (fRet && (m_pRequest->m_rs != STATUS_MULTISTATUS)) 
    {
        DEBUGCHK(m_pRequest->m_rs == STATUS_OK);

        CHttpResponse resp(m_pRequest,fDestExists ? STATUS_NOCONTENT : STATUS_CREATED);
        resp.SetZeroLenBody();
        resp.SendResponse();
    }

    if (szSave) 
    { 
        *szSave = '\r';
        m_pRequest->DebugSetHeadersInvalid(FALSE);
    }

    MyFreeNZ(szDestURL);
    MyFreeNZ(wszDestPath);

    return fRet;
}

BOOL CWebDav::DavMove(void) 
{
    return MoveCopyResource(TRUE);
}

BOOL CWebDav::DavCopy(void) 
{
    return MoveCopyResource(FALSE); 
}

#if 0
BOOL CWebDav::DavSearch(void) 
{ 
    return DavUnsupported(); 
}
BOOL CWebDav::DavSubscribe(void) 
{ 
    return FALSE; 
}
BOOL CWebDav::DavUnsubscribe(void) 
{ 
    return FALSE; 
}
BOOL CWebDav::DavPoll(void) 
{ 
    return FALSE; 
}
BOOL CWebDav::DavBDelete(void) 
{ 
    return FALSE; 
}
BOOL CWebDav::DavBCopy(void) 
{ 
    return FALSE; 
}
BOOL CWebDav::DavBMove(void) 
{ 
    return FALSE; 
}
BOOL CWebDav::DavBProppatch(void) 
{ 
    return FALSE; 
}
BOOL CWebDav::DavBPropfind(void) 
{ 
    return FALSE; 
}
BOOL CWebDav::DavX_MS_Enumatts(void) 
{ 
    return FALSE; 
}
#endif

// Constant strings relating to WebDAV

// DAV XML strings
const CHAR  cszXMLVersionA[]             = "<?xml version=\"1.0\" ?>";
const DWORD ccXMLVersionA                = SVSUTIL_CONSTSTRLEN(cszXMLVersionA);
const CHAR  cszMultiStatusNS[]           = "multistatus xmlns:a=\"DAV:\" xmlns:b=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\" xmlns:c=\"xml:\" xmlns:d=\"urn:schemas-microsoft-com:\"";
const DWORD ccMultiStatusNS              = SVSUTIL_CONSTSTRLEN(cszMultiStatusNS);
const CHAR  cszMultiStatus[]             = "multistatus";
const DWORD ccMultiStatus                = SVSUTIL_CONSTSTRLEN(cszMultiStatus);
const CHAR  cszResponse[]                = "response";
const DWORD ccResponse                   = SVSUTIL_CONSTSTRLEN(cszResponse);
const CHAR  cszHref[]                    = "href";
const DWORD ccHref                       = SVSUTIL_CONSTSTRLEN(cszHref);
const CHAR  cszPropstat[]                = "propstat";
const DWORD ccPropstat                   = SVSUTIL_CONSTSTRLEN(cszPropstat);
const CHAR  cszProp[]                    = "prop";
const DWORD ccProp                       = SVSUTIL_CONSTSTRLEN(cszProp);
const CHAR  cszPropNS[]                  = "prop xmlns:a=\"DAV:\"";
const DWORD ccPropNS                     = SVSUTIL_CONSTSTRLEN(cszPropNS);
const CHAR  cszStatus[]                  = "status";
const DWORD ccStatus                     = SVSUTIL_CONSTSTRLEN(cszStatus);
const CHAR  cszGetcontentLengthNS[]      = "getcontentlength b:dt=\"int\"";
const DWORD ccGetcontentLengthNS         = SVSUTIL_CONSTSTRLEN(cszGetcontentLengthNS);
const CHAR  cszGetcontentLength[]        = "getcontentlength";
const DWORD ccGetcontentLength           = SVSUTIL_CONSTSTRLEN(cszGetcontentLength);

const CHAR  cszDisplayName[]             = "displayname";
const DWORD ccDisplayName                = SVSUTIL_CONSTSTRLEN(cszDisplayName);
const CHAR  cszCreationDateNS[]          = "creationdate b:dt=\"dateTime.tz\"";
const DWORD ccCreationDateNS             = SVSUTIL_CONSTSTRLEN(cszCreationDateNS);
const CHAR  cszCreationDate[]            = "creationdate";
const DWORD ccCreationDate               = SVSUTIL_CONSTSTRLEN(cszCreationDate);
const CHAR  cszGetETag[]                 = "getetag";
const DWORD ccGetETag                    = SVSUTIL_CONSTSTRLEN(cszGetETag);
const CHAR  cszLastModifiedNS[]          = "getlastmodified b:dt=\"dateTime.rfc1123\"";
const DWORD ccLastModifiedNS             = SVSUTIL_CONSTSTRLEN(cszLastModifiedNS);
const CHAR  cszLastModified[]            = "getlastmodified";
const DWORD ccLastModified               = SVSUTIL_CONSTSTRLEN(cszLastModified);
const CHAR  cszResourceType[]            = "resourcetype";
const DWORD ccResourceType               = SVSUTIL_CONSTSTRLEN(cszResourceType);
const CHAR  cszCollection[]              = "collection";
const DWORD ccCollection                 = SVSUTIL_CONSTSTRLEN(cszCollection);
const CHAR  cszIsHiddenNS[]              = "ishidden b:dt=\"boolean\"";
const DWORD ccIsHiddenNS                 = SVSUTIL_CONSTSTRLEN(cszIsHiddenNS);
const CHAR  cszIsHidden[]                = "ishidden";
const DWORD ccIsHidden                   = SVSUTIL_CONSTSTRLEN(cszIsHidden);
const CHAR  cszIsCollectionNS[]          = "iscollection b:dt=\"boolean\"";
const DWORD ccIsCollectionNS             = SVSUTIL_CONSTSTRLEN(cszIsCollectionNS);
const CHAR  cszIsCollection[]            = "iscollection";
const DWORD ccIsCollection               = SVSUTIL_CONSTSTRLEN(cszIsCollection);
const CHAR  cszSupportedLock[]           = "supportedlock";
const DWORD ccSupportedLock              = SVSUTIL_CONSTSTRLEN(cszSupportedLock);
const CHAR  cszLockEntry[]               = "lockentry";
const DWORD ccLockEntry                  = SVSUTIL_CONSTSTRLEN(cszLockEntry);
const CHAR  cszTextXML[]                 = "text/xml";
const DWORD ccTextXML                    = SVSUTIL_CONSTSTRLEN(cszTextXML);
const CHAR  cszApplicationXML[]          = "application/xml";
const DWORD ccApplicationXML             = SVSUTIL_CONSTSTRLEN(cszApplicationXML);
const CHAR  cszGetcontentType[]          = "getcontenttype";
const DWORD ccGetcontentType             = SVSUTIL_CONSTSTRLEN(cszGetcontentType);

// Win32 specific proptags.  We only currently support setting Win32FileAttributes.
const CHAR  cszMSNamespace[]             = "urn:schemas-microsoft-com:";
const DWORD ccMSNamespace                = SVSUTIL_CONSTSTRLEN(cszMSNamespace);

const CHAR  cszWin32FileAttribs[]        = "Win32FileAttributes";
const DWORD ccWin32FileAttribs           = SVSUTIL_CONSTSTRLEN(cszWin32FileAttribs);
const CHAR  cszWin32LastAccessTime[]     = "Win32LastAccessTime";
const DWORD ccWin32LastAccessTime        = SVSUTIL_CONSTSTRLEN(cszWin32LastAccessTime);
const CHAR  cszWin32LastModifiedTime[]   = "Win32LastModifiedTime";
const DWORD ccWin32LastModifiedTime      = SVSUTIL_CONSTSTRLEN(cszWin32LastModifiedTime);
const CHAR  cszWin32CreationTime[]       = "Win32CreationTime";
const DWORD ccWin32CreationTime          = SVSUTIL_CONSTSTRLEN(cszWin32CreationTime);


// LOCK XML strings
const CHAR  cszLockDiscovery[]           = "lockdiscovery";
const DWORD ccLockDiscovery              = SVSUTIL_CONSTSTRLEN(cszLockDiscovery);
const CHAR  cszActiveLock[]              = "activelock";
const DWORD ccActiveLock                 = SVSUTIL_CONSTSTRLEN(cszActiveLock);
const CHAR  cszLockType[]                = "locktype";
const DWORD ccLockType                   = SVSUTIL_CONSTSTRLEN(cszLockType);
const CHAR  cszWrite[]                   = "write";
const DWORD ccWrite                      = SVSUTIL_CONSTSTRLEN(cszWrite);
const CHAR  cszLockScope[]               = "lockscope";
const DWORD ccLockScope                  = SVSUTIL_CONSTSTRLEN(cszLockScope);
const CHAR  cszShared[]                  = "shared";
const DWORD ccShared                     = SVSUTIL_CONSTSTRLEN(cszShared);
const CHAR  cszExclusive[]               = "exclusive";
const DWORD ccExclusive                  = SVSUTIL_CONSTSTRLEN(cszExclusive);
const CHAR  cszLockTokenTag[]            = "locktoken";
const DWORD ccLockTokenTag               = SVSUTIL_CONSTSTRLEN(cszLockTokenTag);
const CHAR  cszOwner[]                   = "owner";
const DWORD ccOwner                      = SVSUTIL_CONSTSTRLEN(cszOwner);
const CHAR  cszTimeoutTag[]              = "timeout";
const DWORD ccTimeoutTag                 = SVSUTIL_CONSTSTRLEN(cszTimeoutTag);
const CHAR  cszDepthTag[]                = "depth";
const DWORD ccDepthTag                   = SVSUTIL_CONSTSTRLEN(cszDepthTag);

const CHAR  cszSecond[]                  = "Second-";
const DWORD ccSecond                     = SVSUTIL_CONSTSTRLEN(cszSecond);
const CHAR  cszInfinite[]                = "Infinite";
const DWORD ccInfinite                   = SVSUTIL_CONSTSTRLEN(cszInfinite);

const CHAR  cszNot[]                     = "not";
const DWORD ccNot                        = SVSUTIL_CONSTSTRLEN(cszNot); 

// DAV header data
const CHAR  csz0[]                       = "0";
const DWORD cc0                          = SVSUTIL_CONSTSTRLEN(csz0);
const CHAR  csz1[]                       = "1";
const DWORD cc1                          = SVSUTIL_CONSTSTRLEN(csz1);
const CHAR  cszInfinity[]                = "infinity";
const DWORD ccInfinity                   = SVSUTIL_CONSTSTRLEN(cszInfinity);
const CHAR  csz1NoRoot[]                 = "1, noroot";
const DWORD cc1NoRoot                    = SVSUTIL_CONSTSTRLEN(csz1NoRoot);
// const CHAR cszInfinityNoRoot      = "infinity,noroot";


const char  cszPrefixBraceOpen[]         = "<a:";
const DWORD ccPrefixBraceOpen            = SVSUTIL_CONSTSTRLEN(cszPrefixBraceOpen);
const char  cszPrefixBraceEnd[]          = "</a:";
const DWORD ccPrefixBraceEnd             = SVSUTIL_CONSTSTRLEN(cszPrefixBraceEnd);
