//
// 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: davlock.cpp
Abstract: WebDAV implementation of locking functions
--*/


#include "httpd.h"

#if defined (DEBUG)
inline void DebugCheckCacheManagerLocked(void) 
{ 
    DEBUGCHK(g_pFileLockManager->IsLocked()); 
}
inline void DebugCheckCacheManagerNotLocked(void) 
{ 
    DEBUGCHK(! g_pFileLockManager->IsLocked()); 
}
#else
inline void DebugCheckCacheManagerLocked(void) 
{ 
    ; 
}
inline void DebugCheckCacheManagerNotLocked(void) 
{ 
    ; 
}
#endif



// BUGBUG - Our filesys doesn't do the right thing in protecting MOVE files.  
// This applies to both static and to ISAPIs loaded with LoadLibrary().

//  *********************************************************
//  Main VERB handeling routines.
//  *********************************************************

BOOL CWebDav::DavLock(void) 
{ 
    CWebDavFileNode *pFileNode    = NULL;
    BOOL fRet                     = FALSE;
    PSTR szLockHeader = NULL;


    if (! (m_pRequest->GetPerms() & HSE_URL_FLAGS_WRITE)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: LOCK fails, write permissions not turned on\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

    if (IsCollection()) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: LOCK fails, WinCE DAV does not support locking collections\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

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

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

    if (NULL != (szLockHeader = m_pRequest->FindHttpHeader(cszIfToken,ccIfToken))) 
    {
        // If LOCK sets a "Lock-Token", the only supported scenario is refreshing
        // the timeout on a lock.
        DWORD dwTimeout = 0;

        if (m_pRequest->m_dwContentLength) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDav LOCK sets Lock-Token but has a body, illegal\r\n"));
            SetStatus(STATUS_UNSUPPORTED_MEDIA_TYPE);
            goto done;
        }

        if (! GetLockTimeout(&dwTimeout) || (dwTimeout == 0)) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDAV lock unable to set updated lock, failing request\r\n"));
            SetStatus(STATUS_BADREQ);
            goto done;
        }

        if (m_nLockTokens != 1) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: LOCK refresh timeout sent %d lock tokens, need exactly 1\r\n",m_nLockTokens));
            SetStatus(STATUS_BADREQ);
            goto done;
        }

        g_pFileLockManager->Lock();

        if (NULL != (pFileNode = g_pFileLockManager->GetNodeFromFileName(m_pRequest->m_wszPath)))
        {
            CWebDavLock *pLock = NULL;
            if (pFileNode->HasLockId(m_lockIds[0],&pLock)) 
            {
                DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: LOCK updating timeout on LockID=%I64d, file=%s, timeout seconds=%d\r\n",m_lockIds[0],m_pRequest->m_wszPath,dwTimeout));
                pLock->SetTimeout(dwTimeout);

                SetStatusAndXMLBody(STATUS_OK);
                SendLockTags(pFileNode);
                fRet = TRUE;
            }
            else 
            {
                DEBUGMSG(ZONE_ERROR,(L"HTTPD: LOCK update timer failed, file %s not associated with lockID %I64d\r\n",m_pRequest->m_wszPath,m_lockIds[0]));
                SetStatus(STATUS_PRECONDITION_FAILED);
            }
        }
        else 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: LOCK update timer failed, file %s does not have any locks associated with it\r\n",m_pRequest->m_wszPath));
            SetStatus(STATUS_PRECONDITION_FAILED);
        }

        g_pFileLockManager->Unlock();
    }
    else 
    {
        // Request for a new lock.
        CLockParse      lockParse(this); 

        if (! GetDepth(DEPTH_ZERO) || ((m_Depth != DEPTH_INFINITY) && (m_Depth != DEPTH_ZERO))) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDAV LOCK fails, depth not set to infinity or zero\r\n"));
            SetStatus(STATUS_BADREQ);
            goto done;
        }
#if defined (DEBUG)
        // Like IIS's behavior in accepting depth infinity.
        if (m_Depth == DEPTH_INFINITY)
        {
            DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: Warning, LOCK accepting depth infinite tag, however it will be treated like depth: 0 because WinCE doesn't support locking collections.\r\n"));
        }
#endif
        if (! ParseXMLBody(&lockParse,FALSE))
        {
            goto done;
        }

        fRet = g_pFileLockManager->CreateLock(&lockParse,this);
    }

done:
    return fRet; 
}

BOOL CWebDav::DavUnlock(void) 
{
    BOOL fRet = FALSE;
    __int64 iLockId;

    if (! (m_pRequest->GetPerms() & HSE_URL_FLAGS_WRITE)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: UNLOCK fails, write permissions not turned on\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

    if (IsCollection()) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: UNLOCK fails, WinCE DAV does not support locking collections\r\n"));
        SetStatus(STATUS_FORBIDDEN);
        goto done;
    }

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

    if (0 == (iLockId = CheckLockTokenHeader())) 
    {
        DEBUGCHK(m_pRequest->m_rs != STATUS_OK); //CheckLockTokenHeader sets errors 
        goto done;
    }

    if (! g_pFileLockManager->IsFileAssociatedWithLockId(m_pRequest->m_wszPath,iLockId)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: File <%s> is not associated with LockID %I64d\r\n",m_pRequest->m_wszPath,iLockId));
        SetStatus(STATUS_PRECONDITION_FAILED);
        goto done;
    }

    if (! g_pFileLockManager->DeleteLock(iLockId)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD UNLOCK fails, lockID %I64d does not exist\r\n",iLockId));
        SetStatus(STATUS_PRECONDITION_FAILED);
        goto done;
    }

    DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: UNLOCK successfully deleted LockID %I64d\r\n",iLockId));
    fRet = TRUE;
done:
    if (fRet) 
    {
        DEBUGCHK(m_pRequest->m_rs == STATUS_OK);
        CHttpResponse resp(m_pRequest,STATUS_NOCONTENT);
        resp.SendResponse();
    }

    return fRet;
}


//  *********************************************************
//  Helper routines for LOCK, UNLOCK, and other verbs
//  like MOVE and DELETE that query lock status.
//  *********************************************************


BOOL CWebDav::GetLockTimeout(DWORD *pdwTimeout) 
{
    DEBUGCHK((*pdwTimeout) == 0);
    DEBUGCHK(m_pRequest->m_idMethod == VERB_LOCK);

    CHAR *szTimeout = m_pRequest->FindHttpHeader(cszTimeOut,ccTimeOut);
    CHAR *szTrav;

    if (! szTimeout) 
    {
        DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDAV did not send \"timeout:\" http header\r\n"));
        return TRUE; // lack of header is not an error.
    }

    CHeaderIter header(szTimeout);
    for (szTrav = header.GetNext(); szTrav; szTrav = header.GetNext()) 
    {
        if (0 == _strnicmp(szTrav,cszSecond,ccSecond)) 
        {
            szTrav += ccSecond;
            if (0 == (*pdwTimeout = atoi(szTrav))) 
            {
                DEBUGMSG(ZONE_ERROR,(L"HTTPD: LOCK \"timeout:\" header not propertly formatted\r\n"));
                SetStatus(STATUS_BADREQ);   
                return FALSE;
            }
            break;
        }
        else if (0 == _strnicmp(szTrav,cszInfinite,ccInfinite)) 
        {
            *pdwTimeout = INFINITE;
            break;
        }
#if defined (DEBUG)
        else 
        {
            DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: Received unknown field in \"timeout:\" header.  Ignoring, no error\r\n"));
        }
#endif // DEBUG
    }

    if (*pdwTimeout > MAX_DAV_LOCK_TIMEOUT) 
    {
        DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: WebDAV LOCK requested timeout of %ud seconds, setting to maximum allowed %ud seconds\r\n",*pdwTimeout,MAX_DAV_LOCK_TIMEOUT));
        *pdwTimeout = MAX_DAV_LOCK_TIMEOUT;
    }

    return TRUE;
}


PSTR CHeaderIter::GetNext(void) 
{
    ResetSaved();
    PSTR szTrav = m_szNextString;

    SkipWhiteSpaceNonCRLF(szTrav);

    if (*szTrav == 0 || *szTrav == '\r')
    {
        return NULL;
    }

    m_szStartString = szTrav;

    // look for ','
    while ((*szTrav != '\r') && (*szTrav != ','))
    {
        szTrav++;
    }

    m_szNextString = szTrav+1;
    {
        szTrav--;
    }

    // backup to remove existing spaces
    while (IsNonCRLFSpace(*szTrav)) szTrav--;

    m_szSave  = (szTrav+1);
    cSave     = *m_szSave;
    *m_szSave = 0;

    return m_szStartString;
}

// Skip W/ in weak header parsing
inline PSTR SkipWeakTagIfNeeded(PSTR pszToken) 
{
    if (*pszToken == 'W' && *(pszToken+1) == '/')
    {
        return pszToken+2;
    }

    return pszToken;
}

BOOL ETagEqualsHeader(PSTR szHeader, PSTR szETag) 
{
    DEBUGCHK(szHeader);
    PSTR    szToken;

    CHeaderIter headIter(szHeader);

    for (szToken = headIter.GetNext(); szToken; szToken = headIter.GetNext()) 
    {
        szToken = SkipWeakTagIfNeeded(szToken);

        if (*szToken == '*')
        {
            return TRUE;
        }
        else if (0 == strcmp(szETag,szToken))
        {
            return TRUE;
        }
    }

    return FALSE;
}

int WriteOpaqueLockToken(PSTR szBuf, __int64 iLockId, BOOL fClosingBrackets) 
{
    PSTR szWrite = szBuf;

    if (fClosingBrackets)
    {
        *szWrite++ = '<';
    }

    strcpy(szWrite,cszOpaquelocktokenPrefix);
    szWrite += ccOpaquelocktokenPrefix;

    strcpy(szWrite,g_pFileLockManager->m_szLockGUID);
    szWrite += SVSUTIL_GUID_STR_LENGTH;

    *szWrite++ = ':';

    szWrite += sprintf(szWrite,"%I64d",iLockId);

    if (fClosingBrackets) 
    {
        *szWrite++ = '>';
        *szWrite++ = 0;
    }

    return (szWrite - szBuf);
}

// Checks that szHeader is equal to "opaquelocktoken:CE_SERVER_GUID:" and removes the LockID after
// returns a pointer to next.  Returns 0 on failure.
__int64 GetLockIDFromOpaqueHeader(PSTR szToken) 
{
    __int64 iLockId;

    if (0 != _strnicmp(szToken,cszOpaquelocktokenPrefix,ccOpaquelocktokenPrefix)) 
    {
        DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: DAV Lock header check fails, '<' not followed by %s\r\n",cszOpaquelocktokenPrefix));
        return 0;
    }

    szToken += ccOpaquelocktokenPrefix;
    if (0 != _strnicmp(szToken,g_pFileLockManager->m_szLockGUID,SVSUTIL_GUID_STR_LENGTH)) 
    {
        DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: DAV Lock header check fails, GUID sent does not match system's, which is <<%s>>\r\n",g_pFileLockManager->m_szLockGUID));
        return 0;
    }

    szToken += SVSUTIL_GUID_STR_LENGTH;
    if (*szToken != ':') 
    {
        DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: DAV Lock header check fails, GUID sent does not contain ':' at end\r\n"));
        return 0;
    }
    szToken++;  // skip ':'

    if (0 == (iLockId = _atoi64(szToken))) 
    {
        DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: DAV Lock header check fails, GUID does not contain 64 bit lockID\r\n"));
        return 0;
    }
    return iLockId;
}


#define OPEN_URI         '<'
#define CLOSE_URI        '>'
#define OPEN_LIST        '('
#define CLOSE_LIST       ')'
#define OPEN_LOCK_TOKEN  '<'
#define CLOSE_LOCK_TOKEN '>'
#define OPEN_ETAG        '['
#define CLOSE_ETAG       ']'


// Per RFC 2518, section 9.5:
//   Lock-Token = "Lock-Token" ":" Coded-URL
//   Coded-URL  = "<" URI ">"

__int64 CWebDav::CheckLockTokenHeader(void) 
{
    PSTR szLockToken = NULL;
    PSTR szTrav;
    PSTR szEndOfURI;
    PSTR szEndOfHeader;
    __int64 iLockId = 0;

    DEBUGCHK(m_pRequest->m_idMethod == VERB_UNLOCK);

    if (NULL == (szLockToken = m_pRequest->FindHttpHeader(cszLockToken,ccLockToken))) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: UNLOCK fails, \"Lock-token\" not sent and is required\r\n"));
        SetStatus(STATUS_BADREQ);
        goto done;
    }
    szTrav = szLockToken;

    svsutil_SkipWhiteSpace(szTrav);
    if (*szTrav != OPEN_URI) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: UNLOCK fails, \"Lock-token\" header does not begin with '<'\r\n"));
        SetStatus(STATUS_BADREQ);
        goto done;
    }
    szTrav++;  // skip '<'

    szEndOfHeader = strchr(szLockToken,'\r');
    szEndOfURI = strchr(szTrav,CLOSE_URI);

    if (!szEndOfURI || (szEndOfURI > szEndOfHeader)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: UNLOCK fails,\"Lock-token\" did not terminate with '>'\r\n"));
        SetStatus(STATUS_BADREQ);
        goto done;
    }

    iLockId = GetLockIDFromOpaqueHeader(szTrav);

    if (iLockId == 0) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: UNLOCK fails, \"Lock-token\" fields formatted incorrectly\r\n"));
        SetStatus(STATUS_BADREQ);
        goto done;
    }
done:
    return iLockId;
}


// Constants and logic in CheckLockHeader per RFC 2518, section 9.4 parsing of "If:" header rules
//   Format of the If header
//      If = "If" ":" ( 1*No-tag-list | 1*Tagged-list)
//      No-tag-list = List
//      Tagged-list = Resource 1*List
//      Resource = Coded-url
//      List = "(" 1*(["Not"](State-token | "[" entity-tag "]")) ")"
//      State-token = Coded-url
//      Coded-url = "<" URI ">"

// Parses "If:" HTTP header to see what locks we have.
BOOL CWebDav::CheckLockHeader(void) 
{
    PSTR    szLockHeaderInBuf;    // points inside main request class, do not free
    PSTR    szLockHeader        = NULL;
    PSTR    szEndOfHeader       = NULL;
    PSTR    szEndOfStatement    = NULL;
    BOOL    fRet                = FALSE;
    //   BOOL    fFirstPass          = TRUE;
    WCHAR   *wszPhysicalPath    = NULL;
    PSTR    szTrav;
    SVSSimpleBuffer urlBuf(512);

    if (NULL == (szLockHeaderInBuf = m_pRequest->FindHttpHeader(cszIfToken,ccIfToken)))
    {
        return TRUE;
    }

    szEndOfHeader = strchr(szLockHeaderInBuf,'\r');
    DEBUGCHK(szEndOfHeader); // otherwise invalid HTTP headers

    *szEndOfHeader = 0;
    szLockHeader = MySzDupA(szLockHeaderInBuf);
    *szEndOfHeader = '\r';

    if (!szLockHeader) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDAV out of memory\r\n"));
        SetStatus(STATUS_INTERNALERR);
        goto done;
    }
    szTrav = szLockHeader;

    while (*szTrav) 
    {
        urlBuf.Reset();

        svsutil_SkipWhiteSpace(szTrav);
        if (*szTrav != OPEN_URI) 
        {

#if 0  // New policy is to be more lenient, accept this case at any time.  See RFC 2518, section 8.9.6 for example of why we accept this.
            // On the first pass through loop, we'll treat a no-tag production 
            // as if it refers to the request URI

            if (!fFirstPass) 
            {
                DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDAV does not have '<' for tagged-list, skipping to end\r\n"));
                fRet = TRUE;
                goto done;
            }
#endif

            MyFreeNZ(wszPhysicalPath);
            wszPhysicalPath = NULL;
        }
        else 
        {
            PSTR szEndOfUrl = NULL;
            szTrav++;  // skip '<'
            szTrav = SkipHTTPPrefixAndHostName(szTrav);

            if (szTrav)
            {
                szEndOfUrl = strchr(szTrav,CLOSE_URI);
            }

            if (NULL == szEndOfUrl) 
            {
                DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDAV skipping host name and headers of tagged-URL failed in \"if:\" header\r\n"));
                SetStatus(STATUS_BADREQ);
                goto done;
            }

            // need to copy results to a new buffer, as canonicilization changes data in place.
            if (! urlBuf.AllocMem(szEndOfUrl-szTrav+1)) 
            {
                DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDAV out of memory\r\n"));
                SetStatus(STATUS_INTERNALERR);
                goto done;
            }
            *szEndOfUrl = 0;
            svsutil_HttpCanonicalizeUrlA(szTrav,(CHAR*)urlBuf.pBuffer);
            *szEndOfUrl = CLOSE_URI;

            MyFreeNZ(wszPhysicalPath);

            wszPhysicalPath = m_pRequest->m_pVrootTable->URLAtoPathW((CHAR*)urlBuf.pBuffer);
            szTrav = szEndOfUrl+1;  // skip past the URI and the '>'
        }
        //      fFirstPass = FALSE;

        svsutil_SkipWhiteSpace(szTrav);

        if (*szTrav != OPEN_LIST) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDAV \"If:\" headers did not send a statement, got %c when expecting %c\r\n",*szTrav,OPEN_LIST));
            SetStatus(STATUS_BADREQ);
            goto done;
        }
        szTrav++; // skip '('
        svsutil_SkipWhiteSpace(szTrav);

        if (NULL == (szEndOfStatement = strchr(szTrav,CLOSE_LIST))) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDAV \"If:\" headers did not send closing end of statement ')' character\r\n"));
            SetStatus(STATUS_BADREQ);
            goto done;
        }
        *szEndOfStatement = 0;

        while (szTrav < szEndOfStatement) 
        {
            // A list has one or more state-tokens and/or entity tags.  Parse these now.
            // There are two types of "failure" conditions we can hit during parsing: 
            // an out and out parse error (for instance not closing '>'), which should cause
            // request to fail, send 400.  However we can also have case where a lock doesn't match
            // what we were requested, but this is allowed if it's preceeded by a "NOT" token.

            BOOL fNot;
            BOOL fSuccess = FALSE;

            // NOT statement's scope is for the immediatly proceeding state-token/entity tag.
            if (0 == _strnicmp(szTrav,cszNot,ccNot)) 
            {
                fNot = TRUE;
                szTrav += ccNot;
                svsutil_SkipWhiteSpace(szTrav);
            }
            else 
            {
                fNot = FALSE;
            }

            if (*szTrav == OPEN_LOCK_TOKEN) 
            {
                PSTR szEndToken;
                if (NULL == (szEndToken = strchr(szTrav,CLOSE_LOCK_TOKEN))) 
                {
                    DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDAV \"If:\" headers did not send closing end of statement '>' character\r\n"));
                    SetStatus(STATUS_BADREQ);
                    goto done;
                }

                *szEndToken = 0;
                fSuccess = CheckTokenExpression(++szTrav,wszPhysicalPath);
                *szEndToken = CLOSE_LOCK_TOKEN;
                szTrav = szEndToken+1;
            }
            else if (*szTrav == OPEN_ETAG) 
            {
                PSTR szEndEtag;
                if (NULL == (szEndEtag = strchr(szTrav,CLOSE_ETAG))) 
                {
                    DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDAV \"If:\" headers did not send closing end of statement ']' character\r\n"));
                    SetStatus(STATUS_BADREQ);
                    goto done;
                }

                *szEndEtag = 0;
                fSuccess = CheckETagExpression(++szTrav,wszPhysicalPath);
                *szEndEtag = CLOSE_ETAG;
                szTrav = szEndEtag+1;
            }
            else 
            {
                DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDav \"If:\" headers did not set '<' or '[' inside statement, invalid statement\r\n"));
                SetStatus(STATUS_BADREQ);
                goto done;
            }

            // Either we successfully matched a token but we shouldn't have (NOT before rule)
            // or we didn't match a token and there was not a "NOT" string.
            if ((fSuccess && fNot) || (!fSuccess && !fNot)) 
            {
                DEBUGMSG(ZONE_ERROR,(L"HTTPD: DAV parsing \"If:\" headers failed, condition not met\r\n"));
                SetStatus(STATUS_PRECONDITION_FAILED);
                goto done;
            }

            svsutil_SkipWhiteSpace(szTrav);
        }

        *szEndOfStatement = CLOSE_LIST;
        szTrav = szEndOfStatement + 1;
        szEndOfStatement  = NULL;

        svsutil_SkipWhiteSpace(szTrav);
    }
    DEBUGCHK((szTrav-szLockHeader) == (int)strlen(szLockHeader));

    fRet = TRUE;
done:
    DebugCheckCacheManagerNotLocked();
    MyFreeNZ(szLockHeader);
    MyFreeNZ(wszPhysicalPath);

    if (szEndOfStatement)
    {
        *szEndOfStatement = CLOSE_LIST;
    }

    return fRet;
}


// Checks contetns of <...> statement in an "if:" lock header

// Note: Don't set error in here or in helper functions because if "Not"
// prepends the string then failure is expected.
BOOL CWebDav::CheckTokenExpression(PSTR szToken, WCHAR *szPath) 
{
    svsutil_SkipWhiteSpace(szToken);
    __int64 iLockId;

    if (*szToken == '\"' || *szToken == '<')
    {
        szToken++;
    }

    if (0 == (iLockId = GetLockIDFromOpaqueHeader(szToken)))
    {
        return FALSE;
    }

    if (szPath && ! g_pFileLockManager->IsFileAssociatedWithLockId(szPath,iLockId))
    {
        return FALSE;
    }

    // store the lockId for possible later use during the request.
    if (! m_lockIds.SRealloc(m_nLockTokens+1))
    {
        return FALSE;
    }

    m_lockIds[m_nLockTokens++] = iLockId;
    {
        return TRUE;
    }
}

// Don't set error here because if "not" prepends string then failure is expected.
BOOL CWebDav::CheckETagExpression(PSTR szToken, WCHAR *szPath) 
{
    svsutil_SkipWhiteSpace(szToken);

    WIN32_FILE_ATTRIBUTE_DATA w32Data = {0};
    WIN32_FILE_ATTRIBUTE_DATA *pData = NULL;

    CHAR szETag[MAX_PATH];

    szETag[0] = '\0';

    // If szPath is known use it, otherwise default to 
    if (szPath) 
    {
        if (! GetFileAttributesEx(szPath,GetFileExInfoStandard,&w32Data)) 
        {
            DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: GetFileAttributesEx(%s) failed, GLE=0x%08x\r\n",szPath,GetLastError()));
            return FALSE;
        }
        pData = &w32Data;
    }
    else 
    {
        if (! DavGetFileAttributesEx())
        {
            return FALSE;
        }

        pData = &m_fileAttribs;
    }

    GetETagFromFiletime(&pData->ftLastWriteTime,pData->dwFileAttributes,szETag);

    szToken = SkipWeakTagIfNeeded(szToken);
    return (0 == (strcmp(szToken,szETag)));
}

// looks at 'if-match' and 'if-not-match' fields to determine whether to continue processing.
BOOL CWebDav::CheckIfHeaders(void) 
{
    PSTR szHeader = NULL;
    CHAR szETag[256];

    szETag[0] = '\0';

    if (IsNull()) 
    {
        // If file doesn't exist, "If-match" headers are invalid.
        if (m_pRequest->FindHttpHeader(cszIf_Match,ccIf_Match)) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: resource %s is not existant, so \"if-match\" headers sent cause request to fail\r\n",m_pRequest->m_wszPath));
            SetStatus(STATUS_PRECONDITION_FAILED);
            return FALSE;
        }
        return TRUE;
    }

    if (! DavGetFileAttributesEx()) 
    {
        DEBUGCHK(0);  // resource should be NULL in this case.
        return FALSE;
    }

    GetETagFromFiletime(&m_fileAttribs.ftLastWriteTime,m_fileAttribs.dwFileAttributes,szETag);

    if (szHeader = m_pRequest->FindHttpHeader(cszIf_Match,ccIf_Match)) 
    {
        if (! ETagEqualsHeader(szHeader,szETag)) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDAV request fails because if-match header doesn't match, required etag=%s\r\n",szETag));
            SetStatus(STATUS_PRECONDITION_FAILED);
            return FALSE;
        }
    }

    if (szHeader = m_pRequest->FindHttpHeader(cszIf_None_Match,ccIf_None_Match)) 
    {
        if (ETagEqualsHeader(szHeader,szETag)) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: WebDAV request fails because if-not-match header matches etag=%s\r\n",szETag));
            SetStatus(STATUS_PRECONDITION_FAILED);
            return FALSE;
        }
    }

    return TRUE;
}

// prints out information about lock(s) associated with a file.
BOOL CWebDav::SendLockTags(CWebDavFileNode *pLockedNode, BOOL fPrintPropTag) 
{
    DebugCheckCacheManagerLocked();
    CHAR szBuf[256];

    BOOL fRet = FALSE;

    CWebDavLock *pLock = pLockedNode->m_pLockList;
    PCSTR szScope = pLockedNode->IsSharedLock() ? cszShared : cszExclusive;

    szBuf[0] = '\0';

    if (fPrintPropTag) 
    {
        if (! m_bufResp.StartTagNoEncode(cszPropNS,ccPropNS))
        {
            goto done;
        }
    }

    while (pLock) 
    {
        WriteOpaqueLockToken(szBuf,pLock->m_i64LockId,FALSE);

        if (! m_bufResp.StartTag(cszLockDiscovery)||
        ! m_bufResp.StartTag(cszActiveLock)       ||
        ! m_bufResp.StartTag(cszLockType)         ||
        ! m_bufResp.SetEmptyElement(cszWrite)     || // spec only supports <write> locks currently
        ! m_bufResp.EndTag(cszLockType)           ||
        ! m_bufResp.StartTag(cszLockScope)        ||
        ! m_bufResp.SetEmptyElement(szScope)      ||
        ! m_bufResp.EndTag(cszLockScope)          ||
        ! m_bufResp.StartTag(cszLockTokenTag)     ||
        ! m_bufResp.StartTag(cszHref)             ||
        ! m_bufResp.Encode(szBuf)                 ||
        ! m_bufResp.EndTag(cszHref)               ||
        ! m_bufResp.EndTag(cszLockTokenTag)) 
        {
            goto done;
        }

        if (pLock->m_szOwner) 
        {
            if (! m_bufResp.BeginBrace()  ||
            ! m_bufResp.Append(cszOwner,ccOwner)) 
            {
                goto done;
            }

            if (pLock->m_szOwnerNSTags) 
            {
                if (! m_bufResp.AppendCHAR(' ') ||
                ! m_bufResp.Append(pLock->m_szOwnerNSTags,pLock->m_ccOwnerNSTags)) 
                {
                    goto done;
                }
            }

            if (! m_bufResp.CloseBrace() ||
            ! m_bufResp.Append(pLock->m_szOwner,pLock->m_ccOwner)    ||
            ! m_bufResp.EndTag(cszOwner)) 
            {
                goto done;
            }
        }

        sprintf(szBuf,"%s%d",cszSecond,pLock->m_dwBaseTimeout);

        if ( !m_bufResp.StartTag(cszDepthTag)              ||
        !m_bufResp.Append(csz0,cc0)                        ||
        !m_bufResp.EndTag(cszDepthTag)                     ||
        !m_bufResp.StartTag(cszTimeoutTag)                 ||
        !m_bufResp.Encode(szBuf)                           ||
        !m_bufResp.EndTag(cszTimeoutTag))
        {
            goto done;
        }

        if (! m_bufResp.EndTag(cszActiveLock)          ||
        ! m_bufResp.EndTag(cszLockDiscovery))
        {
            goto done;
        }

        pLock = pLock->m_pNext;
    }

    if (fPrintPropTag) 
    {
        if (! m_bufResp.EndTag(cszProp))
        {
            goto done;
        }
    }

    fRet = TRUE;
done:
    return fRet;
}

// Certain VERBs want STATUS_CONFLICT on a not found, or can also handle LOCKed.
void CWebDav::SendLockedConflictOrErr(WCHAR *szPath, WCHAR *szAlternatPath) 
{
    if (SendLockErrorInfoIfNeeded(szPath))
    {
        return;
    }

    if (szAlternatPath && IsFileLocked(szAlternatPath)) 
    {
        SetMultiStatusErrorFromURL(STATUS_LOCKED,m_pRequest->m_pszURL);
        return;
    }

    DWORD dwErr = GetLastError();

    if (dwErr == ERROR_PATH_NOT_FOUND)
    {
        SetStatus(STATUS_CONFLICT);
    }
    else
    {
        SetStatus(GLEtoStatus(dwErr));
    }
}

BOOL CWebDav::IsFileLocked(const WCHAR *szPath) 
{
    BOOL fRet = FALSE;

    if (IsGLELocked()) 
    {
        g_pFileLockManager->Lock();

        if (g_pFileLockManager->GetNodeFromFileName((WCHAR*)szPath))
        {
            fRet = TRUE;
        }

        g_pFileLockManager->Unlock();
    }
    return fRet;
}

// If a file is locked and we have lock info about it, then send this to the client.
// If we're not locked, return FALSE so that the calling function can set its own error.
BOOL CWebDav::SendLockErrorInfoIfNeeded(WCHAR *szPath) 
{
    BOOL fRet = FALSE;

    if (IsGLELocked()) 
    {
        g_pFileLockManager->Lock();
        CWebDavFileNode *pFileNode = g_pFileLockManager->GetNodeFromFileName(szPath);
        if (pFileNode) 
        {
            SetStatusAndXMLBody(STATUS_LOCKED);
            SendLockTags(pFileNode);
            fRet = TRUE;
        }
    g_pFileLockManager->Unlock();
    }

return fRet;
}

//  *********************************************************
//  Implementation of Lock cache manager
//  *********************************************************

static const char cszXMLNS[] = "xmlns:";
static const DWORD ccXMLNS   = SVSUTIL_CONSTSTRLEN(cszXMLNS);

//
//  CWebDavLock
//
BOOL CWebDavLock::InitLock(PSTR szOwner, CNameSpaceMap *pNSMap, __int64 iLockId, DWORD dwBaseTimeout) 
{
    // m_szOwner is allowed to be NULL, not a required field.
    m_pNext         = NULL;
    m_szOwner = m_szOwnerNSTags = NULL;

    if (szOwner && (NULL == (m_szOwner = MySzDupA(szOwner))))
    {
        return FALSE;
    }

    if (m_szOwner)
    {
        m_ccOwner = strlen(m_szOwner);
    }
    else
    {
        m_ccOwner = 0;
    }

    // Setup NameSpace prefix->URI tags that are put in <owner> XML tag.
    if (m_szOwner && pNSMap) 
    {
        CNameSpaceMap *pTrav = pNSMap;
        int iLenRequired = 0;
        int iOffset      = 0;

        while (pTrav) 
        {
            iLenRequired += MyW2UTF8(pTrav->m_szPrefix,0,0) + MyW2UTF8(pTrav->m_szURI,0,0) + ccXMLNS + 15;
            pTrav = pTrav->m_pNext;
        }

        if (NULL == (m_szOwnerNSTags = MySzAllocA(iLenRequired)))
        {
            return FALSE;
        }

        pTrav = pNSMap;

        while (pTrav) 
        {
            // If there are multiple URI's with the same Namespace, use the last one
            // put into the list for the URI.
            CNameSpaceMap *pNode = pTrav;
            while (pNode->m_pChild)
            pNode = pNode->m_pChild;


            // The code below is effectively doing this sprintf.  Don't use sprintf
            // because we want UTF8 conversion if it's available, sprintf does CP_ACP on %S.
            // iOffset += sprintf(m_szOwnerNSTags+iOffset," %s%S=\"%S\"",cszXMLNS,pTrav->m_szPrefix,pNode->m_szURI);

            // don't spit out ' ' on 1st pass.
            if (iOffset != 0)
            {
                m_szOwnerNSTags[iOffset++] = ' ';
            }

            strcpy(m_szOwnerNSTags+iOffset,cszXMLNS);
            iOffset += ccXMLNS;

            iOffset += MyW2UTF8(pTrav->m_szPrefix,m_szOwnerNSTags+iOffset,iLenRequired-iOffset) - 1;
            m_szOwnerNSTags[iOffset++] = '=';
            m_szOwnerNSTags[iOffset++] = '\"';

            iOffset += MyW2UTF8(pNode->m_szURI,m_szOwnerNSTags+iOffset,iLenRequired-iOffset) - 1;
            m_szOwnerNSTags[iOffset++] = '\"';

            pTrav = pTrav->m_pNext;
        }
        m_szOwnerNSTags[iOffset++] = 0;

        m_ccOwnerNSTags = strlen(m_szOwnerNSTags);
        DEBUGCHK(m_ccOwnerNSTags < (DWORD)iLenRequired);
    }
    else 
    {
        m_ccOwnerNSTags = 0;
    }

    m_i64LockId     = iLockId;
    SetTimeout(dwBaseTimeout);

    return TRUE;
}

void CWebDavLock::SetTimeout(DWORD dwSecondsToLive) 
{
    DEBUGCHK(dwSecondsToLive != 0);

    __int64 ft = 0;
    GetCurrentFT((FILETIME*)&ft);
    m_ftExpires = ft + (dwSecondsToLive*1000*FILETIME_TO_MILLISECONDS);
    m_dwBaseTimeout = dwSecondsToLive;
}

void CWebDavLock::DeInitLock(void) 
{
    MyFreeNZ(m_szOwner);
    MyFreeNZ(m_szOwnerNSTags);
}

//
//  CWebDavFileNode
//
BOOL CWebDavFileNode::InitFileNode(WCHAR *szFile, DAV_LOCK_SHARE_MODE lockShareMode, HANDLE hFile) 
{
    DebugCheckCacheManagerLocked();
    DEBUGCHK(IsValidLockState(lockShareMode));
    DEBUGCHK(hFile != INVALID_HANDLE_VALUE);

    if (NULL == (m_szFileName = MySzDupW(szFile)))
    {
        return FALSE;
    }

    m_lockShareMode  = lockShareMode;
    m_hFile          = hFile;
    m_pNext          = NULL;
    m_pLockList      = NULL;
    m_fBusy          = FALSE;
    return TRUE;
}

void CWebDavFileNode::DeInitFileNode(void) 
{
    DebugCheckCacheManagerLocked();
    CWebDavLock *pNext;

    while (m_pLockList) 
    {
        pNext = m_pLockList->m_pNext;
        DeInitAndFreeLock(m_pLockList);
        m_pLockList = pNext;
    }

    MyFreeNZ(m_szFileName);
    MyCloseHandle(m_hFile);
}

BOOL CWebDavFileNode::AddLock(PSTR szOwner, CNameSpaceMap *pNSMap, __int64 iLockId, DWORD dwBaseTimeout) 
{
    CWebDavLock *pLock = g_pFileLockManager->AllocFixedLock();
    if (!pLock)
    {
        return FALSE;
    }

    if (! pLock->InitLock(szOwner,pNSMap,iLockId,dwBaseTimeout)) 
    {
        g_pFileLockManager->FreeFixedLock(pLock);
        return FALSE;
    }

    pLock->m_pNext = m_pLockList;
    m_pLockList    = pLock;

    return TRUE;
}

void CWebDavFileNode::DeInitAndFreeLock(CWebDavLock *pLockList) 
{
    DebugCheckCacheManagerLocked();
    pLockList->DeInitLock();
    g_pFileLockManager->FreeFixedLock(pLockList);
}

BOOL CWebDavFileNode::DeleteLock(CWebDavLock *pLockList) 
{
    DebugCheckCacheManagerLocked();

    // If another thread is using lock in PUT/MOVE/COPY/... then leave it alone.
    if (IsBusy())
    {
        return FALSE;
    }

    CWebDavLock *pTrav   = m_pLockList;
    CWebDavLock *pFollow = NULL;

    // remove item from linked list
    while (pTrav && (pTrav != pLockList)) 
    {
        pFollow = pTrav;
        pTrav   = pTrav->m_pNext;
    }
    DEBUGCHK(pTrav); // pLockList must be a valid node.

    if (pFollow)
    {
        pFollow->m_pNext = pTrav->m_pNext;
    }
    else 
    {
        DEBUGCHK(pLockList == m_pLockList);
        m_pLockList = pLockList->m_pNext;
    }

    DeInitAndFreeLock(pLockList);
    return TRUE;
}

void CWebDavFileNode::RemoveTimedOutLocks(FILETIME *pft) 
{
    DebugCheckCacheManagerLocked();

    // If another thread is using lock in PUT/MOVE/COPY/... then leave it alone.
    if (IsBusy())
    {
        return;
    }

    CWebDavLock *pTrav   = m_pLockList;
    CWebDavLock *pFollow = NULL;

    while (pTrav) 
    {
        CWebDavLock *pNext = pTrav->m_pNext;

        if (pTrav->HasLockExpired(pft)) 
        {
            DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: Lock for file %s, lockID=%I64d has expired, removing\r\n",m_szFileName,pTrav->m_i64LockId));
            DeInitAndFreeLock(pTrav);

            if (pFollow)
            {
                pFollow->m_pNext = pNext;
            }
            else 
            {
                DEBUGCHK(pTrav == m_pLockList);
                m_pLockList = pNext;
            }
        }
        else 
        {
            pFollow = pTrav;
        }
        pTrav = pNext;
    }
}


BOOL CWebDavFileNode::HasLockId(__int64 iLockId, CWebDavLock **ppLockMatch) 
{
    DEBUGCHK(m_pLockList); // if list is NULL, *this should've been deleted.
    DebugCheckCacheManagerLocked();

    CWebDavLock *pLock = m_pLockList;

    while (pLock) 
    {
        if (pLock->m_i64LockId == iLockId) 
        {
            if (ppLockMatch)
            {
                *ppLockMatch = pLock;
            }
            return TRUE;
        }

    pLock = pLock->m_pNext;
    }
    return FALSE;
}

// A client has passed one or more LockIDs to our server.  Verify that at least one
// of them corresponds with the lockIDs associated with this file.
BOOL CWebDavFileNode::HasLockIdInArray(LockIdArray *pLockIDs, int numLockIds) 
{
    DebugCheckCacheManagerLocked();
    DEBUGCHK(pLockIDs && numLockIds);   

    for (int i = 0; i < numLockIds; i++) 
    {
        if (HasLockId((*pLockIDs)[i]))
        {
            return TRUE;
        }
    }
    return FALSE;
}

// 
//  Utility
//
unsigned short us_rand (void) 
{
    unsigned short usRes = 0; 
    BCryptGenRandom(NULL, (unsigned char *)&usRes, sizeof(unsigned short), BCRYPT_USE_SYSTEM_PREFERRED_RNG);

    return usRes;
}

typedef int (*CeGenerateGUID_t) (GUID *);

void GenerateGUID (GUID *pGuid) 
{
    HMODULE hLib = LoadLibrary (L"\\windows\\lpcrt.dll");
    if (hLib) 
    {
        CeGenerateGUID_t CeGenerateGUID = (CeGenerateGUID_t)GetProcAddress (hLib, L"CeGenerateGUID");
        int fRet = (CeGenerateGUID && CeGenerateGUID (pGuid) == 0);
        FreeLibrary (hLib);

        if (fRet)
        {
            return;
        }
    }

    srand (GetTickCount ());
    pGuid->Data1 = (us_rand () << 16) | us_rand();
    pGuid->Data2 = us_rand();
    pGuid->Data3 = us_rand();
    for (int i = 0 ; i < 8 ; i += 2) 
    {
        unsigned short usRand = us_rand();
        pGuid->Data4[i]     = (unsigned char)(usRand & 0xff);
        pGuid->Data4[i + 1] = (unsigned char)(usRand >> 8);
    }
}

//
// CWebDavFileLockManager
//

// start with same LockID as IIS
#define DAV_INITIAL_LOCK_ID      ((((__int64)50 << 32)) + 50)

#if defined (DEBUG)
// Debug verification that CWebDavFileLockManager is only instantiated once 
// per web server being loaded / unloaded
int cWebDavFileLockManagerInitializations = 0;
#endif

CWebDavFileLockManager::CWebDavFileLockManager() 
{
#if defined (DEBUG)
    DEBUGCHK(cWebDavFileLockManagerInitializations == 0);
    cWebDavFileLockManagerInitializations++;
#endif

    m_pFileLockList = NULL;

    m_iNextLockId = DAV_INITIAL_LOCK_ID;

    GUID lockGuid = {0};
    GenerateGUID(&lockGuid);

    HRESULT hr = StringCchPrintfA(m_szLockGUID,SVSUTIL_ARRLEN(m_szLockGUID),SVSUTIL_GUID_FORMAT_A,SVSUTIL_RGUID_ELEMENTS(lockGuid));
    DEBUGCHK(hr == S_OK && (strlen(m_szLockGUID) == SVSUTIL_GUID_STR_LENGTH));

    m_pfmdFileNodes = svsutil_AllocFixedMemDescr (sizeof(CWebDavFileNode), 10);
    m_pfmdLocks = svsutil_AllocFixedMemDescr (sizeof(CWebDavLock), 10);

    m_pFileLockList = NULL;
}

CWebDavFileLockManager::~CWebDavFileLockManager() 
{
    DEBUGCHK(g_fState == SERVICE_STATE_SHUTTING_DOWN || g_fState == SERVICE_STATE_UNLOADING);

    Lock();

    // remove everything in FileLock List
    while (m_pFileLockList) 
    {
        CWebDavFileNode *pNext = m_pFileLockList->m_pNext;
        DeInitAndFreeFileNode(m_pFileLockList);
        m_pFileLockList = pNext;
    }

    if (m_pfmdFileNodes)
    {
        svsutil_ReleaseFixedNonEmpty(m_pfmdFileNodes);
    }

    if (m_pfmdLocks)
    {
        svsutil_ReleaseFixedNonEmpty (m_pfmdLocks);
    }

    Unlock();
}


CWebDavFileNode* CWebDavFileLockManager::GetNodeFromID(__int64 iLockId, CWebDavLock **ppLockMatch) 
{
    DEBUGCHK(IsLocked());
    CWebDavFileNode *pFile = m_pFileLockList;

    while (pFile) 
    {
        if (pFile->HasLockId(iLockId,ppLockMatch))
        {
            return pFile;
        }

        pFile = pFile->m_pNext;
    }
    return NULL;
}

CWebDavFileNode * CWebDavFileLockManager::GetNodeFromFileName(WCHAR *szFileName) 
{
    DEBUGCHK(IsLocked());
    CWebDavFileNode *pTrav = m_pFileLockList;

    while (pTrav) 
    {
        if (0 == PathNameCompare(pTrav->m_szFileName,szFileName))
        {
            return pTrav;
        }

        pTrav = pTrav->m_pNext;
    }
    return NULL;
}

BOOL CWebDavFileLockManager::IsFileAssociatedWithLockId(WCHAR *szFileName, __int64 iLockId) 
{
    BOOL fRet = FALSE;
    Lock();

    CWebDavFileNode *pFileNode = GetNodeFromFileName(szFileName);

    if (pFileNode)
    {
        fRet = pFileNode->HasLockId(iLockId);
    }

    Unlock();

#if defined (DEBUG)
    if (!fRet)
    {
        DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: File <%s> is not associated with LockID %I64d\r\n",szFileName,iLockId));
    }
#endif

    return fRet;
}


void CWebDavFileLockManager::RemoveTimedOutLocks(void) 
{
    Lock();

    FILETIME ft = {0};
    GetCurrentFT(&ft);

    DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: file lock cleanup thread woke up, ft.dwHighDateTime = 0x%08x, ft.dwLowDateTime = 0x%08x\r\n",
    ft.dwHighDateTime,ft.dwLowDateTime));

    CWebDavFileNode *pTrav   = m_pFileLockList;
    CWebDavFileNode *pFollow = NULL;

    while (pTrav) 
    {
        CWebDavFileNode *pNext = pTrav->m_pNext;
        pTrav->RemoveTimedOutLocks(&ft);

        // if there are no more locks associated with file resource, remove the node.
        if (! pTrav->HasLocks()) 
        { 
            DeInitAndFreeFileNode(pTrav);

            if (pFollow)
            {
                pFollow->m_pNext = pNext;
            }
            else 
            {
                DEBUGCHK(pTrav == m_pFileLockList);
                m_pFileLockList = pNext;
            }
        }
        else 
        {
            pFollow = pTrav;
        }
        pTrav = pNext;
    }

    Unlock();
}

void CWebDavFileLockManager::DeInitAndFreeFileNode(CWebDavFileNode *pFileNode) 
{
    DEBUGCHK(IsLocked());
    pFileNode->DeInitFileNode();
    svsutil_FreeFixed(pFileNode,m_pfmdFileNodes);
}

BOOL CWebDavFileLockManager::CreateLock(CLockParse *pLockParams, CWebDav *pWebDav) 
{
    CWebDavFileNode *pFileNode        = NULL;
    CWebDavLock     *pLock            = NULL;
    BOOL       fRet                   = FALSE;
    HANDLE     hFile                  = INVALID_HANDLE_VALUE;
    WCHAR      *szFile                = pWebDav->m_pRequest->m_wszPath;
    DWORD      dwTimeout              = 0;
    BOOL       fCreateFileNode        = TRUE;
    BOOL       fFileCreated           = FALSE;
    CHAR       szLockToken[256];      // response header "Lock-token: <opaquelocktoken:GUID:LockID>"
    __int64    iLockId = 0;


    szLockToken[0] = '\0';

    if (pLockParams->m_lockShareMode == DAV_LOCK_UNKNOWN) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: LOCK fails, client did not set <lockscope> to <exclusive> or <shared>\r\n"));
        pWebDav->SetStatus(STATUS_BADREQ);
        return FALSE;
    }

    // Get HTTP timeout header.  If timeout not set, then no problem (will use default)
    // Function will return FALSE on syntax errors
    if (!pWebDav->GetLockTimeout(&dwTimeout))
    {
        return FALSE;
    }

    if (dwTimeout == 0)
    {
        dwTimeout = pWebDav->m_pRequest->m_pWebsite->m_dwDavDefaultLockTimeout; 
    }

    Lock();

    if (NULL != (pFileNode = GetNodeFromFileName(szFile))) 
    {
        // found the file already has a lock associated with it.
        fCreateFileNode = FALSE;

        if (pFileNode->IsExclusiveLock() || (pLockParams->m_lockShareMode == DAV_LOCK_EXCLUSIVE)) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: LOCK attempted to either gain exclusive ownership of an existing lock, or shared ownership of an exclusive lock for file <%s>\r\n",szFile));
            pWebDav->SetStatusAndXMLBody(STATUS_LOCKED);
            pWebDav->SendLockTags(pFileNode);
            goto done;
        }
    }

    if (fCreateFileNode) 
    {
        // file does not have a lock associated with it already, create a new one now.
        DEBUGCHK(!pFileNode);

        if (INVALID_HANDLE_VALUE == (hFile = CreateFile(szFile,DAV_FILE_ACCESS,DAV_SHARE_ACCESS,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL))) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: LOCK call to CreateFile(%s) failed, GLE=0x%08x\r\n",szFile,GetLastError()));
            pWebDav->SetStatus(GLEtoStatus());
            goto done;
        }
        fFileCreated = (GetLastError() != ERROR_ALREADY_EXISTS);

        if (NULL == (pFileNode = (CWebDavFileNode*) svsutil_GetFixed(m_pfmdFileNodes))) 
        {
            pWebDav->SetStatus(STATUS_INTERNALERR);
            goto done;
        }

        if (! pFileNode->InitFileNode(szFile,pLockParams->m_lockShareMode,hFile)) 
        {
            pWebDav->SetStatus(STATUS_INTERNALERR);
            goto done;
        }
    }

    iLockId = GetNextID();
    if (! pFileNode->AddLock(pLockParams->m_szLockOwner,pLockParams->m_NSList,iLockId,dwTimeout)) 
    {
        pWebDav->SetStatus(STATUS_INTERNALERR);
        goto done;
    }

    // after this point no calls that can fail should be added, or if they are
    // be sure to cleanup linked list in 'done:' handeling.
    if (fCreateFileNode) 
    {
        pFileNode->m_pNext = m_pFileLockList;
        m_pFileLockList    = pFileNode;
    }

    if (fFileCreated) 
    {
        pWebDav->m_pRequest->m_bufRespHeaders.AppendData(cszLocation,ccLocation);
        pWebDav->m_pRequest->m_bufRespHeaders.AppendData(" ",1);
        pWebDav->BuildBaseURLFromSourceURL(TRUE);
    }

    WriteOpaqueLockToken(szLockToken,iLockId,TRUE);
    pWebDav->m_pRequest->m_bufRespHeaders.AddHeader(cszLockToken,szLockToken,FALSE);

    pWebDav->SetStatusAndXMLBody(fFileCreated ? STATUS_CREATED : STATUS_OK);
    pWebDav->SendLockTags(pFileNode);

    fRet = TRUE;
done:
    DEBUGCHK(IsLocked());

    if (!fRet) 
    {
        if (hFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(hFile);
        }

        if (fCreateFileNode && pFileNode)
        {
            pFileNode->DeInitFileNode();
        }
    }
#if defined (DEBUG)
    else 
    {
        DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: LOCK created lock for file %s, lockID=%I64d, timeout=%d seconds\r\n",szFile,iLockId,dwTimeout));
    }
#endif

    Unlock();
    return fRet;
}


BOOL CWebDavFileLockManager::CanPerformLockedOperation(WCHAR *szFile1, WCHAR *szFile2, LockIdArray *pLockIDs, int numLocks, CWebDavFileNode **ppFileNode1, CWebDavFileNode **ppFileNode2) 
{
    DEBUGCHK(IsLocked());
    BOOL fRet = FALSE;

    CWebDavFileNode *pFileNode1 = NULL;
    CWebDavFileNode *pFileNode2 = NULL;

    pFileNode1 = GetNodeFromFileName(szFile1);
    if (szFile2)
    {
        pFileNode2 = GetNodeFromFileName(szFile2);
    }

    if (numLocks == 0) 
    {
        if (pFileNode1 || pFileNode2) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: File %s and/or %s require a lock token for access, but none sent by client\r\n",szFile1,szFile2));
            goto done;
        }
        fRet = TRUE;
        goto done;
    }

    if (!pFileNode1 && !pFileNode2) 
    {
        DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: CanPerformLockedOperation cannot proceed, neither %s nor %s have any locks associated with them\r\n",szFile1,szFile2));
        goto done;
    }

    if (pFileNode1 && !pFileNode1->HasLockIdInArray(pLockIDs,numLocks)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: File %s has lock, but none of client lockIDS match\r\n",szFile1));
        goto done;
    }

    if (pFileNode2 && !pFileNode2->HasLockIdInArray(pLockIDs,numLocks)) 
    {
        DEBUGMSG(ZONE_ERROR,(L"HTTPD: File %s has lock, but none of client lockIDS match\r\n",szFile2));
        goto done;
    }

    fRet = TRUE;
done:
    if (ppFileNode1)
    {
        *ppFileNode1 = pFileNode1;
    }

    if (ppFileNode2)
    {
        *ppFileNode2 = pFileNode2;
    }

    DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: Found mapping between file names (%s,%s) and Lock tokens client sent\r\n",szFile1,szFile2));
    return fRet;
}

void CWebDavFileLockManager::RemoveFileNodeFromLinkedList(CWebDavFileNode *pNode) 
{
    DEBUGCHK(IsLocked());

    CWebDavFileNode *pTrav    = m_pFileLockList;
    CWebDavFileNode *pFollow  = NULL;

    while (pTrav != pNode) 
    {
        pFollow = pTrav;
        pTrav   = pTrav->m_pNext;
    }
    DEBUGCHK(pTrav); // we know it's in list

    if (pFollow)
    {
        pFollow->m_pNext = pTrav->m_pNext;
    }
    else 
    {
        DEBUGCHK(pTrav == m_pFileLockList);
        m_pFileLockList = pTrav->m_pNext;
    }
}

BOOL CWebDavFileLockManager::DeleteLock(__int64 iLockId) 
{
    CWebDavFileNode *pFileNode = NULL;
    CWebDavLock     *pLock = NULL;
    BOOL            fRet = FALSE;

    Lock();

    if (NULL == (pFileNode = GetNodeFromID(iLockId,&pLock)))
    {
        goto done;
    }

    DEBUGCHK(pLock);
    if (pFileNode->DeleteLock(pLock)) 
    {
        if (! pFileNode->HasLocks()) 
        {
            RemoveFileNodeFromLinkedList(pFileNode);
            DeInitAndFreeFileNode(pFileNode);
        }
        fRet = TRUE;
    }

    done:
    Unlock();
    return fRet;
}
