/*****************************************************************************/
/*
                                  DAVlock.c

WASD WebDAV locks are implemented as meta-data and stored along with properties
in the meta-data administered by the DAVMETA.C module.


VERSION HISTORY
---------------
19-OCT-2018  MGD  bugfix; DavLockTest() munge the ]NAME.DIR into a directory
                    specification .NAME]
12-SEP-2018  MGD  bugfix; significant refactor of locking
18-JUN-2014  MGD  DavLockUrnUuidToken() move from opaque to uuid token
                    (from "opaquelocktoken:..." to "urn:uuid:...")
15-JUN-2014  MGD  interim test of metadata directory
07-SEP-2013  MGD  DavLockSetTimeout() BitKinex at least uses a timeout
                    of "infinity" rather than the RFC "infinite"
02-SEP-2009  MGD  bugfix; scanning backwards through extended file spec
31-DEC-2006  MGD  initial
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

#include <stdio.h>
#include <ctype.h>

#include <libdef.h>
#include <rmsdef.h>

#include "wasd.h"
#include "davweb.h"

#define WASD_MODULE "DAVLOCK"

#ifndef WASD_WEBDAV
#define WASD_WEBDAV 1
#endif

/******************/
/* global storage */
/******************/

/********************/
/* external storage */
/********************/

extern BOOL  WebDavEnabled,
             WebDavLockingEnabled;

extern const int64  Delta01Sec;

extern int  WebDavLockCollectionDepth;
            WebDavLockTimeoutDefaultSeconds,
            WebDavLockTimeoutMaxSeconds;

extern int64  HttpdTime64;

extern unsigned long  SysPrvMask[];

extern char  ErrorSanityCheck[],
             HttpdVersion[];

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Should be performed as soon as the metadata file XML has been parsed.

Scan all locks looking for any that may have timed-out.  Simply remove them
from the in-memory lock list.  If the metadata file is updated those lock(s)
will not be updated with it.  If the file is not updated then they just sit
there harmlessly until it is.  If a lock is being checked then timed-out locks
will be absent. 
*/

DavLockPreProcess (REQUEST_STRUCT *rqptr)

{
   int  status;
   int64  ScratchTime64;
   WEBDAV_LOCK  *lckptr, *nextptr;
   WEBDAV_META  *mtaptr;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavLockPreProcess()");

   tkptr = rqptr->WebDavTaskPtr;
   mtaptr = &tkptr->MetaData;

   /* don't use LIST_ITERATE as we may need to manipulate the list itself */
   for (lckptr = LIST_GET_HEAD(&mtaptr->LockList); lckptr; lckptr = nextptr)
   {
      /* get any next entry in case the current is removed */
      nextptr = LIST_GET_NEXT (&lckptr->ListEntry);

      if (lckptr->Timeout)
      {
         /* a negative time indicates the timeout has expired */
         ScratchTime64 = lckptr->ExpiresTime64 - HttpdTime64;
         if (ScratchTime64 < 0)
         {
            if (WATCHING (rqptr, WATCH_WEBDAV))
               WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                  "LOCK timeout !20%D expired", lckptr->ExpiresTime64);
            ListRemove (&mtaptr->LockList, lckptr);
         }
      }
   }
}

/*****************************************************************************/
/*
No up-front VMS DLM lock required.
Just rely on the DLM lock acquired by the meta-data lock.
*/

DavLockBegin (REQUEST_STRUCT *rqptr)

{
   WEBDAV_META  *mtaptr;

   /*********/
   /* begin */
   /*********/

   if (!WebDavLockingEnabled)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavLockBegin()");

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "LOCK !AZ",
                 rqptr->ParseOds.ExpFileName);

   mtaptr = &rqptr->WebDavTaskPtr->MetaData;

   DavMetaLock (rqptr, mtaptr, rqptr->ParseOds.ExpFileName,
                &DavLockUpdate, mtaptr);
}

/*****************************************************************************/
/*
If obtained the DLM and meta-data lock the update with new/refresh lock.
*/

DavLockUpdate (WEBDAV_META *mtaptr)

{
   REQUEST_STRUCT  *rqptr;

   /*********/
   /* begin */
   /*********/

   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavLockUpdate()");

   if (VMSok (mtaptr->VmsStatus))
      DavMetaUpdate (mtaptr, DavLockEnd, mtaptr);
   else
      DavLockEnd (mtaptr);
}

/*****************************************************************************/
/*
Results of the lock update.
*/

DavLockEnd (WEBDAV_META *mtaptr)

{
   static char  LockToken [64] = "Lock-Token: ";

   int  status;
   char  *ltptr;
   REQUEST_STRUCT  *rqptr;
   WEBDAV_LOCK  *lckptr;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   rqptr = mtaptr->RequestPtr;
   tkptr = rqptr->WebDavTaskPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavLockEnd()");

   if (tkptr->DestOds.Fab.fab$l_sts)
   {
      /* just created an unmapped URL empty resource */
      mtaptr->VmsStatus = tkptr->DestOds.Fab.fab$l_sts;

      if (WATCHING (rqptr, WATCH_WEBDAV))
         if (VMSok (mtaptr->VmsStatus))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "LOCK unmapped URL");
         else
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "LOCK !&S unmapped URL",
                       mtaptr->VmsStatus);

      if (VMSok (mtaptr->VmsStatus))
      {
         tkptr->ResponseStatusCode = 201;
         status = OdsClose (&tkptr->DestOds, NULL, NULL);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      }
   }
   else
   if (mtaptr->MetaCreated)
   {
      if (!mtaptr->MetaForDir)
      {
         /* not a directory meta, check if the resource exists */
         sys$setprv (1, &SysPrvMask, 0, 0);
         status = OdsFileExists (NULL, rqptr->ParseOds.ExpFileName);
         sys$setprv (0, &SysPrvMask, 0, 0);

         if (VMSnok (status))
         {
            /* does not exists - create unmapped URL empty resource */
            AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName,
                              AUTH_ACCESS_WRITE);

            OdsCreate (&tkptr->DestOds,
                       rqptr->ParseOds.ExpFileName,
                       rqptr->ParseOds.ExpFileNameLength,
                       NULL, 0,
                       FAB$M_PUT + FAB$M_GET + FAB$M_BIO + FAB$M_TRN,
                       FAB$M_CIF + FAB$M_TEF,
                       FAB$M_NIL, FAB$C_UDF, 0, NULL,
                       &DavLockEnd, mtaptr);

            AuthAccessEnable (rqptr, 0, 0);
            return;
         }
      }
   }

   if (VMSok (mtaptr->VmsStatus))
   {
      /* the list head will be the most recently added/refreshed lock */
      if (!(lckptr = LIST_GET_HEAD(&mtaptr->LockList)))
      {
         ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
         DavWebResponse (rqptr, 500, SS$_BUGCHECK, NULL, FI_LI);
         DavWebEnd (rqptr);
         return;
      }

      if (mtaptr->LockCreated)
      {
         /* lock-token response only supplied with new locks not refreshes */
         strzcpy (LockToken+12, lckptr->TokenString, sizeof(LockToken)-12);
         strzcat (LockToken+12, "\r\n", sizeof(LockToken)-12);
         ltptr = LockToken;
      }
      else
         ltptr = "";
   }
   else
   {
      /* blanket 412 for any locking failure */
      DavWebResponse (rqptr, 412, mtaptr->VmsStatus, NULL, FI_LI);
      DavWebEnd (rqptr);
      return;
   }

   rqptr->rqResponse.NoGzip = true;
   ResponseHeader (rqptr, tkptr->ResponseStatusCode,
                   "text/xml; charset=\"utf-8\"",
                   -1, NULL, ltptr);


   FaoToNet (rqptr,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\
<D:prop xmlns:D=\"DAV:\">\n\
 <D:lockdiscovery>\n\
  <D:activelock>\n\
   <D:locktype><D:!AZ/></D:locktype>\n\
   <D:lockscope><D:!AZ!AZ/></D:lockscope>\n\
   <D:locktoken><D:href>!AZ</D:href></D:locktoken>\n\
   <D:depth>!AZ!AZ!AZ</D:depth>\n\
!AZ!AZ!AZ\
!AZ!AZ!AZ\
  </D:activelock>\n\
 </D:lockdiscovery>\n\
</D:prop>\n",
             lckptr->Type == WEBDAV_LOCK_TYPE_WRITE ? "write" : "",
             lckptr->Scope == WEBDAV_LOCK_SCOPE_EXCLUSIVE ? "exclusive" : "",
             lckptr->Scope == WEBDAV_LOCK_SCOPE_SHARED ? "shared" : "",
             lckptr->TokenString,
             lckptr->Depth == WEBDAV_DEPTH_ZERO ? "0" : "",
             lckptr->Depth == WEBDAV_DEPTH_ONE ? "1" : "",
             lckptr->Depth == WEBDAV_DEPTH_INFINITY ? "infinity" : "",
             lckptr->Timeout ? "   <D:timeout>" : "",
             lckptr->Timeout ? lckptr->TimeoutString : "",
             lckptr->Timeout ? "</D:timeout>\n" : "",
             STR_DSC_LEN(&lckptr->OwnerDsc) ? "        <D:owner>" : "",
             /* the owner string has already been HTML-sanitised */
             STR_DSC_LEN(&lckptr->OwnerDsc) ? STR_DSC_PTR(&lckptr->OwnerDsc) : "",
             STR_DSC_LEN(&lckptr->OwnerDsc) ? "</D:owner>\n" : "");

   DavWebEnd (rqptr);
}

/*****************************************************************************/
/*
No up-front VMS DLM lock required.
Just rely on the DLM lock acquired by the meta-data lock.
*/

DavUnLockBegin (REQUEST_STRUCT *rqptr)

{
   WEBDAV_META  *mtaptr;

   /*********/
   /* begin */
   /*********/

   if (!WebDavLockingEnabled)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavUnLockBegin()");

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "UNLOCK !AZ !AZ",
                 rqptr->ParseOds.ExpFileName,
                 rqptr->rqHeader.WebDavLockTokenPtr);

   mtaptr = &rqptr->WebDavTaskPtr->MetaData;

   DavMetaLock (rqptr, mtaptr, rqptr->ParseOds.ExpFileName,
                &DavUnLockUpdate, mtaptr);
}

/*****************************************************************************/
/*
If obtained the DLM and meta-data lock the update with no lock.
*/

DavUnLockUpdate (WEBDAV_META *mtaptr)

{
   REQUEST_STRUCT  *rqptr;

   /*********/
   /* begin */
   /*********/

   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavUnLockUpdate()");

   if (VMSok (mtaptr->VmsStatus))
      DavMetaUpdate (mtaptr, DavUnLockEnd, mtaptr);
   else
      DavUnLockEnd (mtaptr);
}

/*****************************************************************************/
/*
*/

DavUnLockEnd (WEBDAV_META *mtaptr)

{
   REQUEST_STRUCT  *rqptr;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavUnLockEnd() !&S", mtaptr->VmsStatus);

   tkptr = rqptr->WebDavTaskPtr;

   if (VMSok (mtaptr->VmsStatus))
   {
      /* success status but with no content */
      DavWebResponse (rqptr, 204, 0, "success", FI_LI);
   }
   else
   {
      /* unlock failed */
      rqptr->rqResponse.HttpStatus = 412;
      DavWebResponse (rqptr, 412, mtaptr->VmsStatus, NULL, FI_LI);
   }

   DavWebEnd (rqptr);
}

/*****************************************************************************/
/*
Build either a new or refreshed lock XML text into the supplied lock.
Return a zero to indicate success or an HTTP error status code.
*/

int DavLockSetTimeout
(
REQUEST_STRUCT *rqptr,
WEBDAV_LOCK *lckptr
)
{
   int  status, expsecs, tmosecs;
   char  ExpiresDateTime [32];
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavLockSetTimeout() !&Z",
                 rqptr->rqHeader.WebDavTimeoutPtr);

   tkptr = rqptr->WebDavTaskPtr;

   if (rqptr->rqHeader.WebDavTimeoutPtr)
   {
      /* from request header (both "infinite" and "infinity" seem in use) */
      if (strsame (rqptr->rqHeader.WebDavTimeoutPtr, "Infinit", 7))
         lckptr->Timeout = WEBDAV_TIMEOUT_INFINITE;
      else
      if (strsame (rqptr->rqHeader.WebDavTimeoutPtr, "Second-", 7))
         lckptr->Timeout = atoi(rqptr->rqHeader.WebDavTimeoutPtr+7);
      else
      {
         if (WATCHING (rqptr, WATCH_RESPONSE))
            WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "TIMEOUT? !AZ",
                       rqptr->rqHeader.WebDavTimeoutPtr);
         /* default timeouts */
         if (!(lckptr->Timeout = rqptr->rqPathSet.WebDavLockTimeoutDefault))
            lckptr->Timeout = WebDavLockTimeoutDefaultSeconds;
      }
   }
   else
   {
      /* default timeouts */
      if (!(lckptr->Timeout = rqptr->rqPathSet.WebDavLockTimeoutDefault))
         lckptr->Timeout = WebDavLockTimeoutDefaultSeconds;
   }

   /* limit to configured maximum */
   if (rqptr->rqPathSet.WebDavLockTimeoutMax) {
      if (lckptr->Timeout > rqptr->rqPathSet.WebDavLockTimeoutMax)
         lckptr->Timeout = rqptr->rqPathSet.WebDavLockTimeoutMax;
   }
   else
   if (WebDavLockTimeoutMaxSeconds) {
      if (lckptr->Timeout > WebDavLockTimeoutMaxSeconds)
         lckptr->Timeout = WebDavLockTimeoutMaxSeconds;
   }

   if (lckptr->Timeout)
   {
      /* current lock timeout */
      expsecs = tmosecs = lckptr->Timeout;
   }
   else
   {
      /* default timeouts */
      if (!(expsecs = tmosecs = rqptr->rqPathSet.WebDavLockTimeoutDefault))
         expsecs = tmosecs = WebDavLockTimeoutDefaultSeconds;
   }

   /* limit to configured maximum */
   if (rqptr->rqPathSet.WebDavLockTimeoutMax) {
      if (expsecs > rqptr->rqPathSet.WebDavLockTimeoutMax)
         expsecs = rqptr->rqPathSet.WebDavLockTimeoutMax;
   }
   else
   if (WebDavLockTimeoutMaxSeconds) {
      if (expsecs > WebDavLockTimeoutMaxSeconds)
         expsecs = WebDavLockTimeoutMaxSeconds;
   }

   if (lckptr->Timeout == WEBDAV_TIMEOUT_INFINITE)
      strzcpy (lckptr->TimeoutString, "infinite",
               sizeof(lckptr->TimeoutString));
   else
   if (lckptr->Timeout)
      sprintf (lckptr->TimeoutString, "Second-%d", lckptr->Timeout);
   else
      lckptr->TimeoutString[0] = '\0';

   lckptr->ExpiresTime64 = HttpdTime64 + (int64)expsecs * DELTA64_ONE_SEC;
   if (lckptr->ExpiresTime64)
   {
      DavWebDateTimeTo3339 (ExpiresDateTime, &lckptr->ExpiresTime64);
      FaoToBuffer (lckptr->ExpiresString, sizeof(lckptr->ExpiresString), NULL,
                   "!AZ !20%D", ExpiresDateTime, &lckptr->ExpiresTime64);
   }
   else
      lckptr->ExpiresString[0] = '\0';

   return (0);
}

/*****************************************************************************/
/*
Generate XML text equivalent to the lock data adding it to the supplied string
descriptor.
*/

DavLockXml
(
REQUEST_STRUCT *rqptr,
WEBDAV_LOCK *lckptr,
STR_DSC *wdptr
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavLockXml() !8XL !8XL", lckptr, wdptr);

   StrDscBuild (wdptr, NULL, "<WASDAV:lock\n");

   StrDscBuild (wdptr, NULL, "token=\"");
   StrDscBuild (wdptr, NULL, lckptr->TokenString);
   StrDscBuild (wdptr, NULL, "\"\n");

   if (lckptr->Depth == WEBDAV_DEPTH_ZERO)
      StrDscBuild (wdptr, NULL, "depth=\"0\"\n");
   else
   if (lckptr->Depth == WEBDAV_DEPTH_ONE)
      StrDscBuild (wdptr, NULL, "depth=\"1\"\n");
   else
   if (lckptr->Depth == WEBDAV_DEPTH_INFINITY)
      StrDscBuild (wdptr, NULL, "depth=\"infinity\"\n");

   if (lckptr->Type == WEBDAV_LOCK_TYPE_WRITE)
      StrDscBuild (wdptr, NULL, "type=\"write\"\n");

   if (lckptr->Scope == WEBDAV_LOCK_SCOPE_EXCLUSIVE)
      StrDscBuild (wdptr, NULL, "scope=\"exclusive\"\n");
   else
   if (lckptr->Scope == WEBDAV_LOCK_SCOPE_SHARED)
      StrDscBuild (wdptr, NULL, "scope=\"shared\"\n");

   if (lckptr->Timeout == WEBDAV_TIMEOUT_INFINITE)
      StrDscBuild (wdptr, NULL, "timeout=\"infinite\"");
   else
   if (lckptr->Timeout)
      FaoToBuffer (wdptr, -1, NULL, "timeout=\"Second-!UL\"", lckptr->Timeout);
   else
      FaoToBuffer (wdptr, -1, NULL, "timeout=\"Second-!UL\"",
                   WebDavLockTimeoutDefaultSeconds);

   if (lckptr->ExpiresTime64)
      FaoToBuffer (wdptr, -1, NULL, "\nexpires=\"!AZ\">\n",
                   lckptr->ExpiresString);
   else
      FaoToBuffer (wdptr, -1, NULL, ">\n");

   if (STR_DSC_LEN(&lckptr->OwnerDsc))
      FaoToBuffer (wdptr, -1, NULL, "<WASDAV:owner>!&;AZ</WASDAV:owner>\n",
                   STR_DSC_PTR(&lckptr->OwnerDsc));

   StrDscBuild (wdptr, NULL, "</WASDAV:lock>\n");

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!AZ",
                 STR_DSC_PTR(wdptr));
}

/*****************************************************************************/
/*
From the meta-data.
*/

DavLockDiscovery (REQUEST_STRUCT *rqptr)

{
   WEBDAV_LOCK  *lckptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavLockDiscovery()");

   /* step through each lock in the list generating an XML text equivalent */
   LIST_ITERATE (lckptr, &rqptr->WebDavTaskPtr->MetaData.LockList)
   {
      FaoToNet (rqptr,
"     <D:lockdiscovery>\n\
      <D:activelock>\n\
       <D:locktype><D:!AZ/></D:locktype>\n\
       <D:lockscope><D:!AZ!AZ/></D:lockscope>\n\
       <D:locktoken><D:href>!AZ</D:href></D:locktoken>\n\
       <D:depth>!AZ!AZ!AZ</D:depth>\n\
!AZ!AZ!AZ\
!AZ!AZ!AZ\
      </D:activelock>\n\
     </D:lockdiscovery>\n",
             lckptr->Type == WEBDAV_LOCK_TYPE_WRITE ? "write" : "",
             lckptr->Scope == WEBDAV_LOCK_SCOPE_EXCLUSIVE ? "exclusive" : "",
             lckptr->Scope == WEBDAV_LOCK_SCOPE_SHARED ? "shared" : "",
             lckptr->TokenString,
             lckptr->Depth == WEBDAV_DEPTH_ZERO ? "0" : "",
             lckptr->Depth == WEBDAV_DEPTH_ONE ? "1" : "",
             lckptr->Depth == WEBDAV_DEPTH_INFINITY ? "infinity" : "",
             lckptr->Timeout ? "       <D:timeout>" : "",
             lckptr->Timeout ? lckptr->TimeoutString : "",
             lckptr->Timeout ? "</D:timeout>\n" : "",
             STR_DSC_LEN(&lckptr->OwnerDsc) ? "        <D:owner>" : "",
             /* the owner string has already been HTML-sanitised */
             STR_DSC_LEN(&lckptr->OwnerDsc) ? STR_DSC_PTR(&lckptr->OwnerDsc) : "",
             STR_DSC_LEN(&lckptr->OwnerDsc) ? "</D:owner>\n" : "");
   }
}

/*****************************************************************************/
/*
Find a lock that matches the request header "Lock-token:".  Return NULL if no
locks.  Return NOTPTR if lock(s) exist but the header token did not match (any
one).  Returns NOPTR if there is no Lock-Token: header.  Returns the pointer to
any matching lock.
*/

WEBDAV_LOCK* DavLockFindToken (REQUEST_STRUCT *rqptr)

{
   char  *cptr, *sptr;
   WEBDAV_META  *mtaptr;
   WEBDAV_LOCK  *lckptr;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavLockFindToken() !AZ",
                 rqptr->rqHeader.WebDavLockTokenPtr ?
                    rqptr->rqHeader.WebDavLockTokenPtr : "NONE");

   tkptr = rqptr->WebDavTaskPtr;
   mtaptr = &tkptr->MetaData;

   /* if no header token */
   if (!rqptr->rqHeader.WebDavLockTokenPtr) return (NOTPTR);

   /* if no locks */
   if (!LIST_GET_HEAD(&mtaptr->LockList)) return (NULL);

   LIST_ITERATE (lckptr, &mtaptr->LockList)
   {
      sptr = lckptr->TokenString;
      cptr = rqptr->rqHeader.WebDavLockTokenPtr;
      if (*cptr == '<') cptr++;
      while (*cptr && *cptr != '>' && *sptr && *cptr == *sptr)
      {
         cptr++;
         sptr++;
      }
      /* if a match return the lock pointer */
      if (*cptr == '>' && !*sptr) break;
   }

   if (!lckptr) lckptr = NOTPTR;

   if (WATCHING (rqptr, WATCH_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "TOKEN match !AZ",
                 lckptr && lckptr != NOTPTR ? lckptr->TokenString : "NONE");

   return (lckptr);
}

/*****************************************************************************/
/*
Search through the lock list for any lock matching the supplied scope.  If
found check the If: header data to determine whether the action is permitted. 
Return a pointer to lock if permitted, a NOTPTR if not permitted, or a NULL if
no applicable lock.
*/

WEBDAV_LOCK* DavLockFindIf
(
REQUEST_STRUCT *rqptr,
int scope
)
{
   char  *cptr, *sptr;
   WEBDAV_LOCK  *lckptr, *notptr;
   WEBDAV_META  *mtaptr;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavLockFindIf() !UL", scope);

   tkptr = rqptr->WebDavTaskPtr;
   mtaptr = &tkptr->MetaData;
   notptr = NULL;

   LIST_ITERATE (lckptr, &mtaptr->LockList)
   {
      if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!AZ !UL !UL",
                    lckptr->TokenString, lckptr->Type, lckptr->Scope);

      if (lckptr->Type == WEBDAV_LOCK_TYPE_WRITE && lckptr->Scope == scope)
      {
         if (DavLockIfToken (rqptr, lckptr)) break;
         notptr = NOTPTR;
      }
   }

   if (!lckptr) lckptr = notptr;

   if (WATCHING (rqptr, WATCH_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "LOCK search !AZ match !AZ",
                 scope == WEBDAV_LOCK_SCOPE_EXCLUSIVE ? "EXCLUSIVE" :
                                                        "SHARED",
                 (!lckptr || lckptr == NOTPTR) ? "NONE" : lckptr->TokenString);

   return (lckptr);
}

/*****************************************************************************/
/*
Asynchronously reads the meta-data (if any) and tests locking (if any) for
permission to modify resource.  Return success VMS status for modify permission
or an error status to forbid.  The meta-data read ASTs back this same function
which then dispatches another AST to the originally supplied routine (and of
course only the first argument is then valid).  Performs this test for the
supplied file specification and any parent directory(ies) depending on
configuration.  The configuration depth parameter is 0 (default) or 1 for files
only, 2 for the parent directory, 3 for the grandparent directory, etc.  If the
resource is locked then 'TestLockedAt' is set to the collection level, where 1
is the resource itself, 2 is the parent, 3 the grandparent, etc., otherwise it
is zero.
*/

DavLockTest
(
REQUEST_STRUCT *rqptr,
char *FileName,
BOOL IsDirFile,
REQUEST_AST AstFunction,
unsigned long AstParam
)
{
   char  *cptr, *sptr, *zptr;
   WEBDAV_LOCK  *lckptr;
   WEBDAV_META  *mtaptr;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   tkptr = rqptr->WebDavTaskPtr;
   mtaptr = &tkptr->MetaData;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavLockTest() !&Z !UL !&S",
                 tkptr->LockTestName, tkptr->TestLockDepth, mtaptr->VmsStatus);

   if (tkptr->TestLockDepth)
   {
      /***************/
      /* assess test */
      /***************/

      /* meta-data has been read, check it */
      if (mtaptr->VmsStatus == RMS$_FNF ||
          mtaptr->VmsStatus == RMS$_DNF)
         tkptr->TestLockStatus = SS$_NORMAL;
      else
      if (VMSok (mtaptr->VmsStatus))
      {
         tkptr->TestLockStatus = SS$_NORMAL;
         if (rqptr->rqHeader.WebDavIfPtr)
         {
            if (lckptr = DavLockFindIf (rqptr, WEBDAV_LOCK_SCOPE_EXCLUSIVE))
            {
               if (lckptr == NOTPTR)
               {
                  tkptr->TestLockStatus = SS$_ACCONFLICT;
                  tkptr->TestLockedAt = tkptr->TestLockDepth;
               }
            }
         }
         else
         if (rqptr->rqHeader.WebDavLockTokenPtr)
         {
            if (lckptr = DavLockFindToken (rqptr))
               if (lckptr == NOTPTR)
                  tkptr->TestLockStatus = SS$_ACCONFLICT;
         }
         else
         if (LIST_GET_HEAD(&mtaptr->LockList))
         {
            /* the metadata contains a lock and so a conflict */
            tkptr->TestLockStatus = SS$_ACCONFLICT;
         }
      }
      else
         tkptr->TestLockStatus = mtaptr->VmsStatus;

      if (VMSnok (tkptr->TestLockStatus) ||
          tkptr->TestLockDepth > WebDavLockCollectionDepth)
      {
         /*********************/
         /* testing concluded */
         /*********************/

         SysDclAst (tkptr->TestLockAstFunction, tkptr->TestLockAstParam);
         tkptr->TestLockAstFunction = NULL;
         tkptr->TestLockAstParam = tkptr->TestLockDepth = 0;
         tkptr->LockTestName[0] = '\0';
         return;
      }

      /******************************/
      /* test parent directory(ies) */
      /******************************/

      /* create a parent directory out of the current file specification */
      zptr = (sptr = tkptr->LockTestName) + sizeof(tkptr->LockTestName)-1;
      while (*sptr) sptr++;

      if (*(sptr-1) == ']')
         while (sptr > tkptr->LockTestName &&
                (*sptr != '.' || SAME2(sptr-1,'^.')) &&
                (*sptr != '[' || SAME2(sptr-1,'^['))) sptr--;
      else
         while (sptr > tkptr->LockTestName &&
                (*sptr != ']' || SAME2(sptr-1,'^]')) &&
                (*sptr != '[' || SAME2(sptr-1,'^['))) sptr--;

      if (*sptr == '[')
      {
         for (cptr = "[000000]"; *cptr && sptr < zptr; *sptr++ = *cptr++);
         /* reached the MFD so this will be the final test */
         tkptr->TestLockDepth = WebDavLockCollectionDepth;
      }
      else
         *sptr++ = ']';
      *sptr = '\0';

      tkptr->TestLockDepth++;
      DavMetaRead (rqptr, mtaptr, tkptr->LockTestName, &DavLockTest, rqptr);
      return;
   }

   /****************/
   /* initial call */
   /****************/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&Z !&B",
                 FileName, IsDirFile);

   zptr = (sptr = tkptr->LockTestName) + sizeof(tkptr->LockTestName)-1;
   for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   if (IsDirFile)
   {
      /* munge the ]NAME.DIR into a directory specification .NAME] */
      while (sptr > tkptr->LockTestName &&
             (*sptr != '.' || SAME2(sptr-1,'^.')) &&
             (*sptr != ']' || SAME2(sptr-1,'^]'))) sptr--;
      SET2(sptr,']\0');
      if (sptr > tkptr->LockTestName) sptr--;
      while (sptr > tkptr->LockTestName &&
             (*sptr != ']' || SAME2(sptr-1,'^]'))) sptr--;
      if (sptr > tkptr->LockTestName) *sptr = '.';
   }

   tkptr->TestLockAstFunction = AstFunction;
   tkptr->TestLockAstParam = AstParam;
   tkptr->TestLockedAt = 0;
   tkptr->TestLockDepth++;

   DavMetaRead (rqptr, mtaptr, tkptr->LockTestName, &DavLockTest, rqptr);
}

/*****************************************************************************/
/*
When looking for the lock's token in the request's if: header.  If found and
not negated return true.  If not found and negated returns true.  Otherwise
returns false.  The function does not fully implement RFC2518 section 9.4.  As
WASD uses unqiue (UUID) lock tokens it should only have to check for the
presence of one of these, and not need to match on a tagged list or etag.
*/

BOOL DavLockIfToken
(
REQUEST_STRUCT *rqptr,
WEBDAV_LOCK  *lckptr
)
{
   BOOL  hit, notif;
   char  *cptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavLockIfToken() !8XL !8XL",
                 rqptr->rqHeader.WebDavIfPtr, lckptr);

   if (!lckptr) return (false);

   if (!(cptr = rqptr->rqHeader.WebDavIfPtr))
   {
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "IF: none");
      return (false);
   }

   while (*cptr == ' ' || *cptr == '\t') cptr++;

   if (*cptr == '(' && strsame (cptr, "(NOT ", 5))
      notif = true;
   else
      notif = false;

   while (*cptr)
   {
      if (*cptr == '<' &&
          MATCH10 (cptr, "<urn:uuid:") &&
          !memcmp (cptr+1, lckptr->TokenString, 45) &&
          *(cptr+46) == '>') break;
      cptr++;
   }

   hit = (*cptr && !notif) || (!*cptr && notif);

   if (WATCHING (rqptr, WATCH_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "IF: !&B !AZ!AZ>",
                 hit, notif ? "(NOT <" : "<", lckptr->TokenString);

   return (hit);
}

/*****************************************************************************/
/*
Generates a lock token UUID as described in RFC 2518, section 6.3.
This function (substantially and practically) meets this requirement by
combining a quadword time component, a function-internal counter, and the
file-system specification for the resource.  The UUID is then just the MD5
hash transformed into the required "urn:uuid:..." format.
*/

DavLockSetUrnUuidToken
(
REQUEST_STRUCT *rqptr,
WEBDAV_LOCK *lckptr
)
{
   static int64  TokenCounter;
   static $DESCRIPTOR (TokenDsc, "");
   static $DESCRIPTOR (TokenFaoDsc, "urn:uuid:!8XL-!4XL-!4XL-!4XL-!4XL!8XL\0");

   ulong  *qptr;
   unsigned char  Hash16 [16];
   char  *cptr, *sptr, *zptr;
   char  NameBuffer [1024];
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavLockSetUrnUuidToken()");

   tkptr = rqptr->WebDavTaskPtr;

   /* initialize to a quasi-indeterminate value */
   if (!TokenCounter++) TokenCounter = rqptr->rqTime.BeginTime64; 

   zptr = (sptr = NameBuffer) + sizeof(NameBuffer)-1;
   qptr = &rqptr->rqTime.BeginTime64;
   *(ULONGPTR)sptr = qptr[0];
   sptr += 4;
   *(ULONGPTR)sptr = qptr[1];
   sptr += 4;
   *(INT64PTR)sptr = TokenCounter;
   sptr += 8;
   for (cptr = rqptr->ParseOds.ExpFileName;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);

   Md5Digest (NameBuffer, sptr-NameBuffer, &Hash16);

   TokenDsc.dsc$a_pointer = lckptr->TokenString;
   TokenDsc.dsc$w_length = sizeof(lckptr->TokenString);

   sys$fao (&TokenFaoDsc, 0, &TokenDsc, *(ULONGPTR)Hash16,
                                        *(USHORTPTR)(Hash16+4),
                                        *(USHORTPTR)(Hash16+6),
                                        *(USHORTPTR)(Hash16+8),
                                        *(USHORTPTR)(Hash16+10),
                                        *(ULONGPTR)(Hash16+12));

   if (WATCHING (rqptr, WATCH_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "TOKEN !AZ",
                 TokenDsc.dsc$a_pointer);
}

/*****************************************************************************/

