/*****************************************************************************/
/*
                                  DAVprop.c

Provide DAV properties of collections (directories) and resources (files). 
These include 'live' properties (those stored as part of the file-system, e.g.
CDT, RDT) and 'dead' properties (those provided by clients and stored as XML in
the meta-data).

Apparently <allprop/> is deprecated by RFC 4981.


VERSION HISTORY
---------------
12-SEP-2018  MGD  bugfix; significant refactor of locking
06-SEP-2018  MGD  bugfix; DavPropLive() metadata file also needs to be read
                    for ->LockDiscovery as well as ->AllProp and ->FindProp
15-APR-2018  MGD  SS$_ABORT on ->RequestRundown
                  bugfix; DavPropDead() flush the network buffer periodically
                          (for rediculously but legally sized file it might
                          not quickly enough to prevent no-progress timeout)
                  bugfix; DavPropSearchAst() resultant name already terminated
22-JUN-2016  MGD  DavPropSearchAst() ignore ambiguous file names
                    containing an escaped ("^.") period but no type
27-SEP-2014  MGD  improve "no reverse mapping" detection and reporting
                  bugfix; DavPropEnd() ensure unused meta-data file deleted
16-JUN-2014  MGD  DavPropSearchAst() meta file subdirectory detect and suppress
06-JUN-2013  MGD  bugfix; DavPropSearchAst() on non-ODS_EXTENDED platforms
                    (i.e. VAX) reset .nam$b_rsl to changed resultant length
                    or it can generate RMS$_RSL errors - check it out!
05-SEP-2009  MGD  refinements
24-JUN-2009  MGD  DavPropQuota() RFC 4331 quota properties
                  bugfix; DavPropName() XML
19-APR-2007  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

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

/* VMS related header files */
#include <atrdef.h>
#include <descrip.h>
#include <dvidef.h>
#include <fibdef.h>
#include <iodef.h>
#include <libdtdef.h>
#include <libdef.h>
#include <rmsdef.h>
#include <rms.h>
#include <ssdef.h>
#include <stsdef.h>
#include <uaidef.h>

/* application related header file */
#include "wasd.h"
#include "davweb.h"

#define WASD_MODULE "DAVPROP"

#define FCH$M_DIRECTORY 0x2000

#define TEST_PROP_QUOTA 0
#define _TEST_PROP_QUOTA_USERNAME "DANIEL"

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

extern BOOL  WebDavQuotaEnabled;

extern char  ErrorSanityCheck[];

extern int  SysPrvMask[],
            ToLowerCase[],
            ToUpperCase[];

extern int  EfnWait,
            EfnNoWait,
            HttpdTickSecond;

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

/*****************************************************************************/
/*
This function should check for a mapping-generated error message to determine
is there were any problems.
*/ 
 
DavPropBegin (REQUEST_STRUCT *rqptr)

{
   int  status,
        Length;
   char  *sptr;
   char  Scratch [ODS_MAX_FILE_NAME_LENGTH+1];
   WEBDAV_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavPropBegin() depth:!UL !&Z", 
                 rqptr->WebDavTaskPtr->ToDepth,
                 rqptr->ParseOds.ExpFileName);

   tkptr = rqptr->WebDavTaskPtr;

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                 "PROPFIND!AZ !AZ depth:!UL path:!AZ",
                 rqptr->rqPathSet.WebDavNoProp ?
                    " (path set NOprop)" : "",
                 rqptr->ParseOds.ExpFileName, tkptr->ToDepth,
                 DavWebPathAccess(rqptr));

   /* always flush before no-progress timeout (at 75%) */
   tkptr->PropFindFlushAfter = Config.cfTimeout.NoProgress * 75 / 100;
   tkptr->PropFindFlushSecond = HttpdTickSecond + tkptr->PropFindFlushAfter;

   /* ensure a reverse mapping exists for the file specification */
   Scratch[0] = '\0';
   sptr = MapUrl_Map (Scratch, sizeof(Scratch),
                      rqptr->ParseOds.NamDevicePtr, 0,
                      NULL, 0, NULL, 0, NULL, 0, NULL, rqptr, NULL);

   if (!sptr[0] && sptr[1] ||
       (MATCH8(sptr,MAPURL_NO_REVERSE_PATH) &&
        strsame(sptr,MAPURL_NO_REVERSE_PATH,-1)))
   {
      if (!sptr[0] && sptr[1])
         if (WATCHING (rqptr, WATCH_WEBDAV))
             WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                        "REVERSE MAPPING !AZ error !AZ",
                        rqptr->ParseOds.NamDevicePtr, sptr+1); 
      else
         if (WATCHING (rqptr, WATCH_WEBDAV))
             WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                        "NO REVERSE MAPPING from !AZ to URI",
                        rqptr->ParseOds.NamDevicePtr); 

      rqptr->rqResponse.HttpStatus = 500;
      ErrorVmsStatus (rqptr, SS$_ABORT, FI_LI);
      DavPropEnd (rqptr);
      return;
   }

   /* default to reporting all properties */
   if (!rqptr->rqHeader.ContentLength) tkptr->PropData.AllProp = true;

   /* take out a concurrent read lock on the resource */
   DavWebDlmEnqueue (rqptr, &tkptr->DlmSource, rqptr->ParseOds.ExpFileName,
                     NULL, false, false, DavPropBegin2, rqptr);
}

/*****************************************************************************/
/*
Entry point for AST from VMS DLM lock.
*/ 
 
DavPropBegin2 (REQUEST_STRUCT *rqptr)

{
   int  status;
   WEBDAV_DLM  *dlmptr;
   WEBDAV_TASK  *tkptr;

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

   tkptr = rqptr->WebDavTaskPtr;
   dlmptr = &tkptr->DlmSource;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavPropBegin2() !&S", dlmptr->LockSb.lksb$w_status);

   if (VMSnok (dlmptr->LockSb.lksb$w_status))
   {
      DavPropEnd (rqptr);
      return;
   }

   /* network writes are checked for success, fudge the first one! */
   rqptr->NetIoPtr->WriteStatus = SS$_NORMAL;

   if (rqptr->ParseOds.NamNameLength > 0 ||
       rqptr->ParseOds.NamTypeLength > 1)
   {
      /*******************/
      /* single resource */
      /*******************/

      /* any other depth is meaningless */
      tkptr->ToDepth = WEBDAV_DEPTH_ZERO;

      status = DavPropParse (rqptr);
      if (VMSnok(status))
      {
         /* HTTP status will be set from VMS status */
         ErrorVmsStatus (rqptr, status, FI_LI);
         DavPropEnd (rqptr);
         return;
      }

      if (tkptr->PropData.PropName)
         DavPropName (rqptr);
      else
         DavPropLiveAcp (rqptr);

      return;
   }

   /**************/
   /* collection */
   /**************/

   if (tkptr->ToDepth == WEBDAV_DEPTH_ZERO)
   {
      /* collection itself */
      DavPropParent (rqptr);
      return;
   }

   if (tkptr->ToDepth == WEBDAV_DEPTH_ONE)
   {
      /* collection and children, provide the parent */
      DavPropParent (rqptr);
      return;
   }

   if (tkptr->ToDepth == WEBDAV_DEPTH_INFINITY)
   {
      /* potentially too expensive! */
      rqptr->rqResponse.HttpStatus = 501;
      DavPropEnd (rqptr);
      return;
   }

   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

/*****************************************************************************/
/*
As necessary, close the multistatus XML.
*/ 

DavPropEnd (REQUEST_STRUCT *rqptr)

{
   int  status;
   WEBDAV_META  *mtaptr;
   WEBDAV_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavPropEnd() !&F", &DavPropEnd);

   tkptr = rqptr->WebDavTaskPtr;

   mtaptr = &tkptr->MetaData;

   if (mtaptr->ReadOds.Fab.fab$l_sts == RMS$_CREATED &&
       mtaptr->ReadOds.DeleteOnClose)
   {
      /* meta-data file created and never updated */
      status = OdsClose (&mtaptr->ReadOds, NULL, NULL);
      if (VMSnok (status))
         ErrorNoticed (rqptr, status, mtaptr->ReadMetaName, FI_LI); 
   }

   /* if an 'empty' multistatus response was generated */
   if (!rqptr->rqResponse.HttpStatus)
   {
      rqptr->rqResponse.NoGzip = true;
      DavWebResponse207 (rqptr);
   }

   if (rqptr->rqResponse.HttpStatus == 207)
      FaoToNet (rqptr, "</D:multistatus>\n");

   DavWebEnd (rqptr);
}

/*****************************************************************************/
/*
Get the file name of the directory containing this collection and begin to
process to get it's WebDAV properties.
*/ 

DavPropParent (REQUEST_STRUCT *rqptr)

{
   int  status,
        DirectoryFileLength;
   char  DirectoryFile [ODS_MAX_FILE_NAME_LENGTH+1];
   WEBDAV_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavPropParent() !AZ", rqptr->ParseOds.NamDevicePtr);

   tkptr = rqptr->WebDavTaskPtr;

   /* get name of directory in parent */
   OdsNameOfDirectoryFile (rqptr->ParseOds.NamDevicePtr,
                           rqptr->ParseOds.NamNamePtr -
                              rqptr->ParseOds.NamDevicePtr,
                           DirectoryFile, &DirectoryFileLength);

   AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_READ);

   if (VMSok (status = OdsFileExists (NULL, DirectoryFile)))
      OdsParse (&tkptr->SearchOds, DirectoryFile, DirectoryFileLength,
                NULL, 0, NULL, NULL, rqptr);

   AuthAccessEnable (rqptr, 0, 0);

   if (VMSnok (status))
   {
      DavWebResponse (rqptr, 404, status, "collection does not exist", FI_LI);
      DavPropEnd (rqptr);
      return;
   }

   if (VMSok (status = tkptr->SearchOds.Fab.fab$l_sts))
      status = OdsParseTerminate (&tkptr->SearchOds);

   if (VMSnok(status))
   {
      /* HTTP status will be set from VMS status */
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      ErrorVmsStatus (rqptr, status, FI_LI);
      DavPropEnd (rqptr);
      return;
   }

   tkptr->PropParent = true;

   AuthAccessEnable (rqptr, 0, AUTH_ACCESS_SYSPRV);

   OdsSearch (&tkptr->SearchOds, &DavPropSearchAst, rqptr);

   AuthAccessEnable (rqptr, 0, 0);
}

/*****************************************************************************/
/*
Parse the search structure.
*/ 

int DavPropParse (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *sptr, *zptr;
   WEBDAV_TASK  *tkptr;

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

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

   tkptr = rqptr->WebDavTaskPtr;

   cptr = rqptr->ParseOds.ExpFileName;
   zptr = (sptr = tkptr->SearchSpec) + sizeof(tkptr->SearchSpec);
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (!(rqptr->ParseOds.NamNameLength > 0 ||
         rqptr->ParseOds.NamTypeLength > 1))
   {     
      /* no name or type was supplied with the request, wildcard it */
      for (cptr = "*.*;0"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }
   if (sptr >= zptr)
   {
      ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
      return (SS$_RESULTOVF);
   }
   *sptr = '\0';
   tkptr->SearchSpecLength = sptr - tkptr->SearchSpec;

   AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_READ);

   OdsParse (&tkptr->SearchOds, tkptr->SearchSpec, tkptr->SearchSpecLength,
             NULL, 0, NULL, NULL, rqptr);

   AuthAccessEnable (rqptr, 0, 0);

   if (VMSok (status = tkptr->SearchOds.Fab.fab$l_sts))
      status = OdsParseTerminate (&rqptr->ParseOds);

   return (status);
}

/*****************************************************************************/
/*
AST function to invoke another $SEARCH call.
*/ 

DavPropSearch (REQUEST_STRUCT *rqptr)

{
   int  status;
   WEBDAV_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropSearch() !&F !&S",
                 &DavPropSearch, rqptr->NetIoPtr->WriteStatus);

   if (VMSnok (rqptr->NetIoPtr->WriteStatus))
   {
      /* network write has failed (as AST), bail out now */
      DavPropEnd (rqptr);
      return;
   }

   tkptr = rqptr->WebDavTaskPtr;

   if (tkptr->PropParent)
   {
      /*******************/
      /* finished parent */
      /*******************/

      tkptr->PropParent = false;

      OdsParseRelease (&tkptr->SearchOds);

      if (tkptr->ToDepth == WEBDAV_DEPTH_ZERO)
      {
         DavPropEnd (rqptr);
         return;
      }

      /******************/
      /* begin children */
      /******************/

      status = DavPropParse (rqptr);
      if (VMSnok(status))
      {
         /* HTTP status will be set from VMS status */
         ErrorVmsStatus (rqptr, status, FI_LI);
         DavPropEnd (rqptr);
         return;
      }
   }

   AuthAccessEnable (rqptr, 0, AUTH_ACCESS_SYSPRV);

   OdsSearch (&tkptr->SearchOds, &DavPropSearchAst, rqptr);

   AuthAccessEnable (rqptr, 0, 0);
}

/*****************************************************************************/
/*
AST completion routine called each time sys$search() completes.  It will 
either point to another file name found or have "no more files found" status 
(or an error!).
*/ 

DavPropSearchAst (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *sptr;
   WEBDAV_TASK  *tkptr;

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

#if WATCH_MOD
   HttpdCheckPriv (FI_LI);
#endif /* WATCH_MOD */

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavPropSearchAst() !&F sts:!&S stv:!&S",
                 &DavPropSearchAst,
                 rqptr->WebDavTaskPtr->SearchOds.Fab.fab$l_sts,
                 rqptr->WebDavTaskPtr->SearchOds.Fab.fab$l_stv);

   if (rqptr->RequestState >= REQUEST_STATE_ABORT)
   {
      DavPropEnd (rqptr);
      return;
   }

   tkptr = rqptr->WebDavTaskPtr;

   if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts))
   {
      /* if its a search list treat directory not found as if file not found */
      if ((tkptr->SearchOds.Nam_fnb & NAM$M_SEARCH_LIST) && status == RMS$_DNF)
         status = RMS$_FNF;
      
      if (status == RMS$_FNF || status == RMS$_NMF)
      {
         /***************************/
         /* end of directory search */
         /***************************/

         tkptr->SearchOds.ParseInUse = false;

         DavPropEnd (rqptr);
         return;
      }

      /*************************/
      /* protection violation? */
      /*************************/

      if (status == SS$_NOPRIV || status == RMS$_PRV)
      {
         /* continue the search */
         DavPropSearch (rqptr);
         return;
      }

      /**********************/
      /* sys$search() error */
      /**********************/

      /* assume this can only happen on the first call */
      ErrorVmsStatus (rqptr, status, FI_LI);
      DavPropEnd (rqptr);
      return;
   }

   /*****************/
   /* search result */
   /*****************/

#ifndef ODS_EXTENDED
   /*
      06-JUN-2013  MGD  VAX VMS V7.3 seems to require this adjusted
                        or it generates RMS$_RSL errors (!?)
   */
   if (tkptr->SearchOds.NamTypeLength > 1)
      tkptr->SearchOds.Nam.nam$b_rsl = tkptr->SearchOds.NamVersionPtr -
                                       tkptr->SearchOds.NamDevicePtr;
   else
      tkptr->SearchOds.Nam.nam$b_rsl = tkptr->SearchOds.NamTypePtr -
                                       tkptr->SearchOds.NamDevicePtr;
#endif

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "!&Z", tkptr->SearchOds.NamDevicePtr);

   if (MATCH7(tkptr->SearchOds.NamNamePtr,"000000.") &&
       strsame (tkptr->SearchOds.NamNamePtr, "000000.DIR;", 11))
   {
      status = OdsReallyADir (rqptr, &tkptr->SearchOds);
      if (VMSok (status))
      {
         /* no need to report the MFD */
         DavPropSearch (rqptr);
         return;
      }
   }

   /* if a meta file directory it is not accessible */
   if (DavMetaDir (rqptr, &tkptr->SearchOds))
   {
      DavPropSearch (rqptr);
      return;
   }

   if (DavMetaFile (&tkptr->SearchOds))
   {
      DavPropSearch (rqptr);
      return;
   }

   /* in true Unix-style "hidden" files do not appear (those beginning ".") */
   if (SAME1(tkptr->SearchOds.NamNamePtr,'.') ||
       SAME2(tkptr->SearchOds.NamNamePtr,'^.'))
   {
      /* except in demo mode or to VMS authenticated and profiled requests */
      if (rqptr->rqPathSet.WebDavNoHidden)
      {
         if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, ".FILE (hidden)");
         DavPropSearch (rqptr);
         return;
      }
   }

   if (tkptr->SearchOds.NamTypeLength <= 1)
   {
      /* no explicit type - look to see if name contains escaped period */
      for (cptr = tkptr->SearchOds.NamNamePtr;
           cptr < tkptr->SearchOds.NamTypePtr;
           cptr++)
         if (*(USHORTPTR)cptr == '^.') break;
      if (cptr < tkptr->SearchOds.NamTypePtr)
      {
         /* escaped period - do not list these ambiguous file names */
         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                       "^.NAME.; !AZ", tkptr->SearchOds.NamNamePtr);
         DavPropSearch (rqptr);
         return;
      }
   }

   /**************/
   /* properties */
   /**************/

   if (tkptr->PropData.PropName)
      DavPropName (rqptr);
   else
      DavPropLiveAcp (rqptr);
}

/*****************************************************************************/
/*
Provide the <propname/> property (all property names without values).
*/

DavPropName (REQUEST_STRUCT *rqptr)

{
   WEBDAV_TASK  *tkptr;

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

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

   tkptr = rqptr->WebDavTaskPtr;

   DavWebHref (rqptr, tkptr->SearchOds.NamDevicePtr, 0);

   if (!rqptr->rqResponse.HttpStatus) DavWebResponse207 (rqptr);

   FaoToNet (rqptr,
" <D:response>\n\
  <D:href>!AZ!&%AZ</D:href>\n\
  <D:propstat>\n\
   <D:status>HTTP/1.1 200 OK</D:status>\n\
   <D:prop>\n\
    <creationdate/>\n\
    <displayname/>\n\
    <getlastmodified/>\n\
    <getcontentlanguage/>\n\
    <getcontentlength/>\n\
    <getcontenttype/>\n\
    <getetag/>\n\
    <resourcetype/>\n\
    <source/>\n",
             tkptr->HrefHost,
             tkptr->PropParent ?
                rqptr->rqHeader.PathInfoPtr : tkptr->HrefPath);

   if (tkptr->LockingEnabled)
      FaoToNet (rqptr,
"    <lockdiscovery/>\n\
    <supportedlock/>\n");

   if (tkptr->QuotaEnabled)
      FaoToNet (rqptr,
"    <quota-available-bytes/>\n\
    <quota-used-bytes/>\n");

   FaoToNet (rqptr,
"   </D:prop>\n\
  </D:propstat>\n\
 </D:response>\n");

   DavPropEnd (rqptr);
}

/*****************************************************************************/
/*
Using the ACP-QIO interface obtain the live file properties (size, created
date/time, last modified, etc.)
*/

DavPropLiveAcp (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr;
   WEBDAV_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropLiveAcp() !AZ",
                 rqptr->WebDavTaskPtr->SearchOds.NamDevicePtr);

   tkptr = rqptr->WebDavTaskPtr;

   AuthAccessEnable (rqptr, tkptr->SearchOds.NamDevicePtr, AUTH_ACCESS_READ);

   OdsFileAcpInfo (&tkptr->SearchOds, &DavPropLive, rqptr); 

   AuthAccessEnable (rqptr, 0, 0);
}

/****************************************************************************/
/*
AST called from OdsFileAcpInfo() (invoked by DavPropLiveAcp()) when ACP QIO
completes.  This function supplies the 'live' properties associated with a file
(or directory file).
*/ 

DavPropLive (REQUEST_STRUCT *rqptr)

{
   int  status,
        Bytes,
        NameLength;
   unsigned long  AllocatedVbn,
                  EndOfFileVbn;
   char  *cptr, *sptr, *zptr;
   char  DateTime [32],
         EntityTag [32],
         FileSpec [ODS_MAX_FILE_NAME_LENGTH+1];
   WEBDAV_PROP  *proptr;
   WEBDAV_TASK  *tkptr;

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

#if WATCH_MOD
   HttpdCheckPriv (FI_LI);
#endif /* WATCH_MOD */

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavPropLive() !&F !&S", &DavPropLive,
                 rqptr->WebDavTaskPtr->SearchOds.FileQio.IOsb.Status);

   tkptr = rqptr->WebDavTaskPtr;
   proptr = &tkptr->PropData;

   /* first deassign the channel allocated by OdsFileAcpInfo() */
   sys$dassgn (tkptr->SearchOds.FileQio.AcpChannel);

   if ((status = tkptr->SearchOds.FileQio.IOsb.Status) == SS$_NOSUCHFILE)
      status = RMS$_FNF;

   if (VMSnok (status)) 
   {
      /****************/
      /* error status */
      /****************/

      if (tkptr->ToDepth == WEBDAV_DEPTH_ZERO)
      {
         /* just the single resource */
         ErrorVmsStatus (rqptr, status, FI_LI);
         DavPropEnd (rqptr);
         return;
      }

      /*************************/
      /* protection violation? */
      /*************************/

      if (status == SS$_NOPRIV || status == RMS$_PRV)
      {
         DavPropSearch (rqptr);
         return;
      }

      /*******************/
      /* something else! */
      /*******************/

      DavWebMultiStatus (rqptr, status, tkptr->SearchOds.ExpFileName);
      DavPropEnd (rqptr);
      return;
   }

   if (!rqptr->rqResponse.HttpStatus) DavWebResponse207 (rqptr);

   /**********/
   /* report */
   /**********/

   /* same status as OdsReallyADir() */
   if (tkptr->SearchOds.FileQio.AtrUchar & FCH$M_DIRECTORY)
   {
      tkptr->SearchIsDirectory = true;
      status = SS$_NORMAL;
   }
   else
   {
      tkptr->SearchIsDirectory = false;
      status = SS$_ABORT;
   }

   DavWebHref (rqptr, tkptr->SearchOds.NamDevicePtr, status);

   FaoToNet (rqptr,
" <D:response>\n\
  <D:href>!AZ!&%AZ</D:href>\n\
  <D:propstat>\n\
   <D:status>HTTP/1.1 200 OK</D:status>\n\
   <D:prop>\n",
             tkptr->HrefHost,
             tkptr->PropParent ?
                rqptr->rqHeader.PathInfoPtr : tkptr->HrefPath);

   if (proptr->AllProp || proptr->DisplayName)
   {
      /********/
      /* name */
      /********/

      if (tkptr->PropParent)
         zptr = cptr = "";
      else
      {
         /* resolve the name from the href path */
         for (cptr = sptr = tkptr->HrefPath; *cptr; cptr++);
         zptr = cptr;
         while (cptr > sptr && *cptr != '/') cptr--;
         if (tkptr->SearchIsDirectory)
         {
            if (cptr > sptr && *cptr == '/')
            {
               cptr--;
               zptr--;
            }
            while (cptr > sptr && *cptr != '/') cptr--;
         }
         if (*cptr == '/') cptr++;
      }

      FaoToNet (rqptr,
"     <D:displayname>!#&;AZ</D:displayname>\n",
                zptr-cptr, cptr);
   }

   if (proptr->AllProp || proptr->ResourceType)
   {
      /******************/
      /* file/directory */
      /******************/

      if (tkptr->SearchIsDirectory)
         FaoToNet (rqptr,
"     <D:resourcetype><D:collection/></D:resourcetype>\n");
      else
         FaoToNet (rqptr, "     <D:resourcetype/>\n");
   }

   if (proptr->AllProp || proptr->CreationDate)
   {
      /***********/
      /* created */
      /***********/

      /* this property format is "1995-0825T17:32:40Z" */
      DavWebDateTimeTo3339 (DateTime, &tkptr->SearchOds.FileQio.CdtTime64);
      FaoToNet (rqptr,
"     <D:creationdate>!AZ</D:creationdate>\n",
                DateTime);
   }

   if (proptr->AllProp || proptr->GetContentLanguage)
      /* WASD dosn't handle this datum */
      FaoToNet (rqptr,
"     <D:getcontentlanguage/>\n");

   if (proptr->AllProp || proptr->GetContentLength)
   {
      /******************/
      /* content length */
      /******************/

      if (!tkptr->SearchIsDirectory)
      {
         AllocatedVbn =
((tkptr->SearchOds.FileQio.RecAttr.fat$l_hiblk & 0xffff) << 16) |
((tkptr->SearchOds.FileQio.RecAttr.fat$l_hiblk & 0xffff0000) >> 16);
         EndOfFileVbn =
((tkptr->SearchOds.FileQio.RecAttr.fat$l_efblk & 0xffff) << 16) |
((tkptr->SearchOds.FileQio.RecAttr.fat$l_efblk & 0xffff0000) >> 16);

         if (EndOfFileVbn <= 1)
            Bytes = tkptr->SearchOds.FileQio.RecAttr.fat$w_ffbyte;
         else
            Bytes = ((EndOfFileVbn-1) << 9) +
                    tkptr->SearchOds.FileQio.RecAttr.fat$w_ffbyte;

         if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
               "AllocatedVbn:!UL EndOfFileVbn:!UL FirstFreeByte:!UL Bytes;!UL",
               AllocatedVbn, EndOfFileVbn,
               tkptr->SearchOds.FileQio.RecAttr.fat$w_ffbyte,
               Bytes);

         FaoToNet (rqptr,
"     <D:getcontentlength>!UL</D:getcontentlength>\n",
                   Bytes);
      }
   }

   if (proptr->AllProp || proptr->GetContentType)
   {
      /****************/
      /* content type */
      /****************/

      if (tkptr->SearchIsDirectory)
         cptr = "httpd/unix-directory";
      else
      {
         if (rqptr->PathOds == MAPURL_PATH_ODS_ADS ||
             rqptr->PathOds == MAPURL_PATH_ODS_SMB)
            cptr = MapOdsAdsFileType (tkptr->SearchOds.NamTypePtr);
         else
         if (rqptr->PathOds == MAPURL_PATH_ODS_SRI)
            cptr = MapOdsSriFileType (tkptr->SearchOds.NamTypePtr);
         else
            cptr = tkptr->SearchOds.NamTypePtr;
         ConfigContentType (&tkptr->ContentInfo, cptr);
         cptr = tkptr->ContentInfo.ContentTypePtr;
         /* transmogrify WASD internal types */
         if (SAME2(cptr,'x-') || SAME2(cptr,'X-'))
            cptr = "application/octet-stream";
      }

      FaoToNet (rqptr,
"     <D:getcontenttype>!AZ</D:getcontenttype>\n",
                cptr);
   }

   if (proptr->AllProp || proptr->GetEtag)
   {
      /********/
      /* etag */
      /********/

      FileGenerateEntityTag (EntityTag, &tkptr->SearchOds.FileQio);
      FaoToNet (rqptr,
"     <D:getetag>\"!AZ\"</D:getetag>\n",
                EntityTag);
   }

   if (proptr->AllProp || proptr->GetLastModified)
   {
      /*****************/
      /* last modified */
      /*****************/

      /* this property format is "Fri, 25 Aug 1995 17:32:40 GMT" */
      HttpGmTimeString (DateTime, &tkptr->SearchOds.FileQio.RdtTime64);
      FaoToNet (rqptr,
"     <D:getlastmodified>!AZ</D:getlastmodified>\n",
                DateTime);
   }

#if TEST_PROP_QUOTA
   WebDavQuotaEnabled = proptr->QuotaUsedBytes =
      proptr->QuotaAvailableBytes = true;
#endif

   if (WebDavQuotaEnabled)
      if (proptr->QuotaUsedBytes || proptr->QuotaAvailableBytes)
         DavPropQuota (rqptr);

   if (tkptr->LockingEnabled &&
       (proptr->AllProp || proptr->LockDiscovery))
   {
      /*********************/
      /* locking available */
      /*********************/

      FaoToNet (rqptr,
"     <D:supportedlock>\n\
      <D:lockentry>\n\
       <D:lockscope><D:exclusive/></D:lockscope>\n\
       <D:locktype><D:write/></D:locktype>\n\
      </D:lockentry>\n\
      <D:lockentry>\n\
       <D:lockscope><D:shared/></D:lockscope>\n\
       <D:locktype><D:write/></D:locktype>\n\
      </D:lockentry>\n\
     </D:supportedlock>\n");
   }

   if (tkptr->MicrosoftAgent &&
       rqptr->rqPathSet.WebDavNoWinProp &&
       (proptr->AllProp || proptr->FindProp))
   {
      /******************************/
      /* fudge Microsoft properties */
      /******************************/

      /* these property formats are "Fri, 25 Aug 1995 17:32:40 GMT" */
      HttpGmTimeString (DateTime, &tkptr->SearchOds.FileQio.CdtTime64);
      FaoToNet (rqptr,
"     <MS:Win32CreationTime xmlns:MS=\"urn:schemas-microsoft-com:\">\
!AZ</MS:Win32CreationTime>\n",
                DateTime);

      HttpGmTimeString (DateTime, &tkptr->SearchOds.FileQio.RdtTime64);
      FaoToNet (rqptr,
"     <MS:Win32LastModifiedTime xmlns:MS=\"urn:schemas-microsoft-com:\">\
!AZ</MS:Win32LastModifiedTime>\n",
                DateTime);

      /* NOT the last access time but what the hey it's a fudge! */
      FaoToNet (rqptr,
"     <MS:Win32LastAccessTime xmlns:MS=\"urn:schemas-microsoft-com:\">\
!AZ</MS:Win32LastAccessTime>\n",
                DateTime);

      /* apparently it is good practice to always set the archive bit */
      FaoToNet (rqptr,
"     <MS:Win32FileAttributes xmlns:MS=\"urn:schemas-microsoft-com:\">\
00000020</MS:Win32FileAttributes>\n");
   }

   tkptr->MetaData.VmsStatus = 0;

   if (rqptr->rqPathSet.WebDavNoProp)
      DavPropDead (rqptr);
   else
   if (proptr->AllProp || proptr->FindProp || proptr->LockDiscovery)
   {
      /*************/
      /* meta-data */
      /*************/

      if (tkptr->ToDepth == WEBDAV_DEPTH_ZERO)
      {
         /*
            The collection itself.
            Do NOT use DavPropParent() non-concealed specification to
            access the meta-data.  Use the original request specification.
         */
         DavMetaRead (rqptr, &tkptr->MetaData, rqptr->ParseOds.NamDevicePtr,
                      &DavPropDead, rqptr);
         return;
      }

      zptr = (sptr = FileSpec) + sizeof(FileSpec)-1;
      cptr = tkptr->SearchOds.NamDevicePtr;
      if (tkptr->SearchOds.FileQio.AtrUchar & FCH$M_DIRECTORY)
      {
         /* munge the ]NAME.DIR into a directory specification .NAME] */
         while (*cptr && cptr < tkptr->SearchOds.NamNamePtr-1 && sptr < zptr)
            *sptr++ = *cptr++;
         if (sptr < zptr) *sptr++ = '.';
         if (*cptr) cptr++;
         while (*cptr && cptr < tkptr->SearchOds.NamTypePtr && sptr < zptr)
            *sptr++ = *cptr++;
         if (sptr < zptr) *sptr++ = ']';
      }
      else
      {
         /* just a POF */
         while (*cptr && cptr < tkptr->SearchOds.NamVersionPtr && sptr < zptr)
            *sptr++ = *cptr++;
      }
      *sptr = '\0';

      DavMetaRead (rqptr, &tkptr->MetaData, FileSpec, &DavPropDead, rqptr);
   }
   else
      DavPropDead (rqptr);
}

/*****************************************************************************/
/*
AST called from DavMetaRead() (invoked by DavPropLive()), or called directly
from DavPropLive().  This function supplies the 'dead' properties assocated
with a file (or directory).  These properties are stored in the meta-data and
accessed as required.
*/ 
 
DavPropDead (REQUEST_STRUCT *rqptr)
{
   int  status;
   STR_DSC  *fdptr, *sdptr;
   WEBDAV_META  *mtaptr;
   WEBDAV_PROP  *proptr;
   WEBDAV_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavPropDead() !&S",
                 rqptr->WebDavTaskPtr->MetaData.VmsStatus);

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

   if (VMSok (tkptr->MetaData.VmsStatus))
   {
      if (tkptr->LockingEnabled &&
          (proptr->AllProp || proptr->LockDiscovery))
      {
         /* what locking is available */
         DavLockDiscovery (rqptr);
      }

      if (proptr->AllProp)
      {
         /* list all of the 'dead' properties */
         STR_DSC_ITERATE (sdptr, &tkptr->MetaData.PropDsc)
            if (STR_DSC_LEN(sdptr))
               FaoToNet (rqptr, "     !AZ\n", STR_DSC_PTR(sdptr));
      }
      else
      if (proptr->FindProp)
      {
         /* search for requested properties and report any found */
         STR_DSC_ITERATE (sdptr, &tkptr->XmlData.PropFindDsc)
            if (fdptr = DavMetaSearch (rqptr, &tkptr->MetaData.PropDsc, sdptr))
               if (STR_DSC_LEN(fdptr))
                  FaoToNet (rqptr, "     !AZ\n", STR_DSC_PTR(fdptr));
      }
   }

   FaoToNet (rqptr,
"   </D:prop>\n\
  </D:propstat>\n\
 </D:response>\n");

   if (tkptr->ToDepth == WEBDAV_DEPTH_ZERO)
      DavPropEnd (rqptr);
   else
   if (tkptr->PropFindFlushSecond <= HttpdTickSecond)
   {
      /* no output for this period so let's force a flush */
      tkptr->PropFindFlushSecond = HttpdTickSecond +
                                   tkptr->PropFindFlushAfter;
      NetWriteBuffered (rqptr, DavPropSearch, NULL, 0);
   }
   else
   {
      /* reset flush timer if the buffer was at the last attempt */
      if (STR_DSC_LEN(&rqptr->NetWriteBufferDsc) < tkptr->PropFindFlushLength)
         tkptr->PropFindFlushSecond = HttpdTickSecond +
                                      tkptr->PropFindFlushAfter;
      /* set the current length of the buffer */
      tkptr->PropFindFlushLength = STR_DSC_LEN(&rqptr->NetWriteBufferDsc);
      /* flush if buffer full(ish) */
      NetWriteBuffered (rqptr, DavPropSearch, NULL, -1);
   }
}

/*****************************************************************************/
/*
Implement the PROPPATCH method.  Modfiy the meta-data properties.
*/ 
 
DavPropPatchBegin (REQUEST_STRUCT *rqptr)

{
   STR_DSC  *sdptr;
   WEBDAV_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavPropPatchBegin() !&Z",  rqptr->ParseOds.ExpFileName);

   if (!rqptr->RemoteUser[0])
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   tkptr = rqptr->WebDavTaskPtr;

   if (rqptr->rqPathSet.WebDavNoProp)
   {
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                    "PROPPATCH (path set NOprop) !AZ",
                    rqptr->ParseOds.ExpFileName);
      DavPropPatchEnd (rqptr);
      return;
   }

   if (tkptr->MicrosoftAgent &&
       rqptr->rqPathSet.WebDavNoWinProp)
   {
      /* step through each property in the request body */
      STR_DSC_ITERATE (sdptr, &tkptr->XmlData.PropSetDsc)
      {
         if (!STR_DSC_LEN(sdptr)) continue;
         if (strstr(STR_DSC_PTR(sdptr),"urn:schemas-microsoft-com:")) continue;
         /* break on the first non-MS property */
         break;
      }
      if (!sdptr)
      {
         /* no properties (unlikely) or all MS properties (almost certainly) */
         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                       "PROPPATCH (path set NOWINPROP) !AZ",
                       rqptr->ParseOds.ExpFileName);
         tkptr->MetaData.VmsStatus = SS$_NORMAL;
         DavPropPatchEnd (rqptr);
         return;
      }
      /* otherwise drop thru to continue */
   }

   tkptr->TestLockState = 0;

   DavPropPatchBegin2 (rqptr);
}

/*****************************************************************************/
/*
Asynchronously test for locking then if not begin the patching.
*/ 
 
DavPropPatchBegin2 (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr;
   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,
                 "DavPropPatchBegin2() !&F !UL",
                 DavPropPatchBegin2, tkptr->TestLockState);

   if (tkptr->SearchOds.NamDeviceLength)
      cptr = tkptr->SearchOds.ExpFileName;
   else
   if (rqptr->ParseOds.NamDeviceLength)
      cptr = rqptr->ParseOds.ExpFileName;
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /***********/
   /* locking */
   /***********/

   switch (tkptr->TestLockState)
   {
      case 0 :

         /* establish a request (overall) VMS DLM lock on source */
         tkptr->TestLockState++;
         DavWebDlmEnqueue (rqptr, &tkptr->DlmSource,
                           rqptr->ParseOds.ExpFileName,
                           NULL, true, false,
                           DavPropPatchBegin2, rqptr);
         return;

      case 1 :

         if (VMSnok (status = tkptr->DlmSource.LockSb.lksb$w_status))
         {
            /* ordinarily shouldn't return any errors */
            DavWebResponse (rqptr, 500, status, NULL, FI_LI);
            DavPropPatchEnd (rqptr);
            return;
         }

         /* check for meta-lock on existing source */
         tkptr->TestLockState++;
         DavLockTest (rqptr, rqptr->ParseOds.ExpFileName, false,
                      DavPropPatchBegin2, rqptr);
         return;

      case 2 :

         if (VMSnok (tkptr->TestLockStatus))
         {
            mtaptr->VmsStatus = tkptr->TestLockStatus;
            DavWebResponse (rqptr, 423, 0, "source locked", FI_LI);
            DavPropPatchEnd (rqptr);
            return;
         }
   }

   /**************/
   /* not locked */
   /**************/

   if (WATCHING (rqptr, WATCH_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "PROPPATCH !AZ", cptr);

   DavMetaLock (rqptr, &tkptr->MetaData, rqptr->ParseOds.ExpFileName,
                &DavPropPatchUpdate, &tkptr->MetaData);
}

/*****************************************************************************/
/*
*/ 
 
DavPropPatchUpdate (WEBDAV_META *mtaptr)

{
   int  status;
   REQUEST_STRUCT  *rqptr;

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

   rqptr = mtaptr->RequestPtr;

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

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

/*****************************************************************************/
/*
Complete a PROPPATCH request.
*/ 
 
DavPropPatchEnd (REQUEST_STRUCT *rqptr)

{
   int  status, xmlsts;
   char  *cptr;
   WEBDAV_META  *mtaptr;
   WEBDAV_TASK  *tkptr;

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

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

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

   if (VMSok (mtaptr->VmsStatus))
   {
      rqptr->rqResponse.NoGzip = true;
      DavWebResponse207 (rqptr);

      DavWebHref (rqptr, rqptr->ParseOds.ExpFileName, 0);

      FaoToNet (rqptr,
" <D:response>\n\
   <D:href>!AZ!&%AZ</D:href>\n\
 </D:response>\n\
</D:multistatus>\n",
                tkptr->HrefHost,
                tkptr->PropParent ?
                   rqptr->rqHeader.PathInfoPtr : tkptr->HrefPath);
   }
   else
   {
      if (mtaptr->VmsStatus == SS$_ACCONFLICT)
      {
         /* resource is locked */
         DavWebResponse (rqptr, 423, 0, NULL, FI_LI);
      }
      else
      {
         /* other reason for failure */
         DavWebResponse (rqptr, 500, mtaptr->VmsStatus, NULL, FI_LI);
      }
   }

   DavWebEnd (rqptr);
}

/*****************************************************************************/
/*
Provide RFC 4331 quota properties (all synchronous).

If the path access is SET webdav=server attempt to return any disk quota for
the HTTPd server account or if SET webdav=profile the SYSUAF authenticated
request's VMS username.  Both of these require a $GETUAI to obtain the UIC.  If
neither of these (or if the $GETUAI fails for some reason) then return the
request path's device used and available space.
*/

DavPropQuota (REQUEST_STRUCT *rqptr)

{
   static unsigned long  AddendZero = 0,
                         FiveTwelve = 512,
                         UaiContext = -1;
   static unsigned long  DviFreeBlocks,
                         DviMaxBlock,
                         UaiUic;

   static char  UserName [12+1];
   static int  UserNameLength;
   static $DESCRIPTOR (UserNameDsc, UserName);

   static VMS_ITEM_LIST2  DeviceFibDsc,
                          InQuotaDsc,
                          OutQuotaDsc;

   static VMS_ITEM_LIST3 
   UaiItems [] = 
   {
      { sizeof(UaiUic), UAI$_UIC, &UaiUic, 0 },
      { 0,0,0,0 }
   },
   BlocksItemList [] = 
   {
      { sizeof(DviMaxBlock), DVI$_MAXBLOCK, &DviMaxBlock, 0 },
      { sizeof(DviFreeBlocks), DVI$_FREEBLOCKS, &DviFreeBlocks, 0 },
      { 0, 0, 0, 0 }
   };

   int  status;
   unsigned short  DeviceChannel,
                   OutLength;
   unsigned long  BytesAvailable,
                  BytesUsed;
   unsigned long  QuadBytes [2];
   $DESCRIPTOR (DeviceDsc, "");
   struct fibdef  DeviceFib; 

   WEBDAV_PROP  *proptr;
   WEBDAV_TASK  *tkptr;

   struct {
      unsigned long flags;
      unsigned long uic;
      unsigned long used;
      unsigned long perm;
      unsigned long over;
      unsigned long unused[3];
   } InQuota, OutQuota;

   struct {
      unsigned short  iosb$w_status;
      unsigned short  iosb$w_bcnt;
      unsigned long  iosb$l_reserved;
   } IOsb;

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

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

   tkptr = rqptr->WebDavTaskPtr;
   proptr = &tkptr->PropData;

   DeviceDsc.dsc$a_pointer = rqptr->ParseOds.NamDevicePtr;
   DeviceDsc.dsc$w_length = rqptr->ParseOds.NamDeviceLength;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!#AZ",
               DeviceDsc.dsc$w_length, DeviceDsc.dsc$a_pointer);

   status = sys$assign (&DeviceDsc, &DeviceChannel, 0, 0);
   if (VMSnok (status))
   {
      if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                    "sys$assign() !&S", status);
      return (status);
   }

   UserNameDsc.dsc$a_pointer = NULL;
   if (rqptr->rqPathSet.WebDavServer)
   {
      /* try to check HTTPd server account quota */
      UserNameDsc.dsc$a_pointer = HttpdProcess.UserName;
      UserNameDsc.dsc$w_length = HttpdProcess.UserNameLength;
   }
   else
   if (rqptr->rqPathSet.WebDavProfile)
   {
      /* try to check SYSUAF authenticated user account quota */
      if (rqptr->rqAuth.SysUafAuthenticated &&
          rqptr->rqAuth.VmsUserProfileLength)
      {
         UserNameDsc.dsc$a_pointer = rqptr->RemoteUser;
         UserNameDsc.dsc$w_length = rqptr-> RemoteUserLength;
      }
   }

#if TEST_PROP_QUOTA
#ifdef TEST_PROP_QUOTA_USERNAME
   UserNameDsc.dsc$a_pointer = TEST_PROP_QUOTA_USERNAME;
   UserNameDsc.dsc$w_length = strlen(TEST_PROP_QUOTA_USERNAME);
#endif
#endif

   status = 0;

   if (UserNameDsc.dsc$a_pointer)
   {
      /****************/
      /* username UIC */
      /****************/

      if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "{!UL}!#AZ !&Z",
                    UserNameDsc.dsc$w_length, UserNameDsc.dsc$w_length,
                    UserNameDsc.dsc$a_pointer, UserName);

      if (UserNameLength != UserNameDsc.dsc$w_length ||
          strncmp (UserName, UserNameDsc.dsc$a_pointer, UserNameLength))
      {
         /* buffer the username (and consequently the UIC) */
         char  *cptr, *sptr, *zptr;
         zptr = (sptr = UserName) + sizeof(UserName)-1;
         for (cptr = UserNameDsc.dsc$a_pointer;
              cptr < UserNameDsc.dsc$a_pointer + UserNameDsc.dsc$w_length;
              *sptr++ = *cptr++);
         *sptr = '\0';
         UserNameLength = sptr - UserName;

         UserNameDsc.dsc$a_pointer = UserName;
         UserNameDsc.dsc$w_length = UserNameLength;

         sys$setprv (1, &SysPrvMask, 0, 0);
         status = sys$getuai (0, &UaiContext, &UserNameDsc, &UaiItems,
                              0, 0, 0);
         sys$setprv (0, &SysPrvMask, 0, 0);

         if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                       "sys$getuai() !&S", status);
      }
      else
         status = SS$_NORMAL;
   }

   /* if UIC was successfully retrieved */
   if (VMSok (status))
   {
      /********************/
      /* check disk quota */
      /********************/

      memset (&DeviceFib, 0, sizeof(DeviceFib));
      DeviceFib.fib$w_exctl = FIB$C_EXA_QUOTA;
      DeviceFibDsc.buf_len = sizeof(DeviceFib);
      DeviceFibDsc.buf_addr = &DeviceFib;

      memset (&InQuota, 0, sizeof(InQuota));
      InQuotaDsc.buf_len = sizeof(InQuota);
      InQuotaDsc.buf_addr = &InQuota;
      InQuota.uic = UaiUic;

      memset (&OutQuota, 0, sizeof(OutQuota));
      OutQuotaDsc.buf_len = sizeof(OutQuota);
      OutQuotaDsc.buf_addr = &OutQuota;

      status = sys$qiow (0, DeviceChannel, IO$_ACPCONTROL, &IOsb, 0, 0,
                         &DeviceFibDsc, &InQuotaDsc, &OutLength, &OutQuotaDsc,
                         0, 0);

      if (VMSok (status)) status = IOsb.iosb$w_status;
      if (VMSok (status))
      {
         BytesAvailable = OutQuota.perm - OutQuota.used;
         BytesUsed = OutQuota.used;
         if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                       "QUOTA perm:!UL used:!UL",
                       OutQuota.perm, OutQuota.used);
      }
      else
      if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                    "sys$qiow() !&S", status);
   }

   /* if the quota ACP failed or if quota not checked */
   if (VMSnok (status))
   {
      /********************/
      /* get device usage */
      /********************/
                                                
      status = sys$getdviw (EfnWait, DeviceChannel, 0,
                            &BlocksItemList, &IOsb, 0, 0, 0);

      if (VMSok (status)) status = IOsb.iosb$w_status;
      if (VMSok (status))
      {
         BytesAvailable = DviFreeBlocks;
         BytesUsed = DviMaxBlock - DviFreeBlocks;
         if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                       "BLOCKS max:!UL free:!UL", DviMaxBlock, DviFreeBlocks);
      }
      else
      if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                    "sys$qiow() !&S", status);
   }

   if (VMSok (status))
   {
      /***************/
      /* list quotas */
      /***************/

      if (proptr->QuotaUsedBytes)
      {
         status = lib$emul (&FiveTwelve, &BytesUsed,
                            &AddendZero, &QuadBytes);
         if (VMSok (status))
            FaoToNet (rqptr,
"     <D:quota-used-bytes>!@SQ</D:quota-used-bytes>\n",
                      &QuadBytes);
         else
            ErrorNoticed (rqptr, status, NULL, FI_LI);
      }

      if (proptr->QuotaAvailableBytes)
      {
         status = lib$emul (&FiveTwelve, &BytesAvailable,
                            &AddendZero, &QuadBytes);
         if (VMSok (status))
            FaoToNet (rqptr,
"     <D:quota-available-bytes>!@SQ</D:quota-available-bytes>\n",
                      &QuadBytes);
         else
            ErrorNoticed (rqptr, status, NULL, FI_LI);
      }
   }

   sys$dassgn (DeviceChannel);

   return (status);
}

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

