/*****************************************************************************/
/*
                                 DAVxml.c

Uses the EXPAT XML parser to parse the request body.


REFERENCES
----------
Using Expat by Clark Cooper
http://en.wikipedia.org/wiki/Expat_(XML)
https://libexpat.github.io
http://www.xml.com/pub/a/1999/09/expat/index.html
http://www.xml.com/lpt/a/47


VERSION HISTORY
---------------
09-MAY-2018  MGD  DavXmlMemoryError() detect out-of-memory
                  DavXmlParseText() detect out-of-memory
19-NOV-2017  MGD  always DavWebEnd() not RequestEnd()
23-APR-2015  MGD  bugfix; DavXmlStartElement() PROPFIND accumulate list of
                    dead properties subsequently searched for in the metadata
20-AUG-2009  MGD  DavXmlStartElement() begin parsing lock own immediately
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

#define DAVXML_MEMORY 1
#ifndef DAVXML_MEMORY
#define DAVXML_MEMORY 1
#endif

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

#include <libdef.h>

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

#define WASD_MODULE "DAVXML"

/* just reduce the amount of XML parse noise when WATCHing */
#define WATCH_XML 1

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

static XML_Memory_Handling_Suite  DavXmlMemorySuite;

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

extern int  HttpdTickSecond;

extern char  ErrorSanityCheck[];

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

/*****************************************************************************/
/*
Initialise ememory management for when parsing XML.
*/

void DavXmlInit ()

{
   if (VmExpatInit ())
   {
      /* initialise the EXPAT memory mnagement suite */
      DavXmlMemorySuite.free_fcn = VmExpatFree;
      DavXmlMemorySuite.malloc_fcn = VmExpatMalloc;
      DavXmlMemorySuite.realloc_fcn = VmExpatRealloc;
   }
}

/*****************************************************************************/
/*
Wrap XML_ParserCreateNS() to allow memory management and monitoring.
Note that the request pointer is currently unused.
*/

XML_Parser* DavXmlParserCreate (REQUEST_STRUCT *rqptr)

{
   static char  nssep[] = " ",
                nsenc[] = "UTF-8";

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

   if (WATCH_XML && WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavXmlParserCreate()");

   if (DavXmlMemorySuite.free_fcn)
      return (XML_ParserCreate_MM (nsenc, &DavXmlMemorySuite, nssep));
   else
      return (XML_ParserCreate_MM (nsenc, NULL, nssep));
}

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

int DavXmlParseText
(
REQUEST_STRUCT *rqptr,
STR_DSC *sdptr
)
{
   int  cnt, column, line, status, xmlsts;
   char  *cptr, *eptr, *sptr;
   WEBDAV_LOCK  *lckptr;
   WEBDAV_TASK  *tkptr;
   WEBDAV_XML  *xmlptr;

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

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

   /* shouldn't be calling this without content to parse! */
   if (!(STR_DSC_SANITY(sdptr) && STR_DSC_LEN(sdptr))) return (SS$_BUGCHECK);

   status = SS$_NORMAL;

   tkptr = rqptr->WebDavTaskPtr;
   lckptr = &tkptr->LockData;
   xmlptr = &tkptr->XmlData;

   InstanceGblSecIncrLong (&AccountingPtr->WebDavXmlParseCount);

   xmlptr->ParserPtr = DavXmlParserCreate (rqptr);

   if (!xmlptr->ParserPtr)
   {
      status = vaxc$errno;
      rqptr->rqResponse.HttpStatus = 500;
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

   xmlptr->ParseLockInfo = xmlptr->ParseLockOwner =
      xmlptr->ParseLockScope = xmlptr->ParseLockType =
      xmlptr->ParseProp = xmlptr->ParsePropertyBehavior =
      xmlptr->ParsePropertyUpdate = xmlptr->ParsePropFind =
      xmlptr->ParseRemove = xmlptr->ParseSet = WEBDAV_STATE_NOT_PARSING;

   lckptr->Type = lckptr->Scope = xmlptr->ElementDepth = 0;

   /* properties will be in a descriptor collection */
   StrDscColIfNotBegin (rqptr, &xmlptr->PropFindDsc, 256);
   StrDscColIfNotBegin (rqptr, &xmlptr->PropRemoveDsc, 256);
   StrDscColIfNotBegin (rqptr, &xmlptr->PropSetDsc, 256);

   XML_SetUserData (xmlptr->ParserPtr, rqptr);

   XML_SetElementHandler (xmlptr->ParserPtr, DavXmlStartElement,
                                             DavXmlEndElement);

   XML_SetCharacterDataHandler (xmlptr->ParserPtr, DavXmlCharData);

   xmlsts = XML_Parse (xmlptr->ParserPtr, STR_DSC_PTR(sdptr),
                                          STR_DSC_LEN(sdptr), 1);

   if (xmlsts == XML_STATUS_ERROR)
   {
      DavXmlParseError (rqptr, xmlptr->ParserPtr,
                        sdptr, &xmlptr->ParseErrorDsc);
      if (DavXmlMemoryError (rqptr, tkptr->XmlData.ParserPtr))
         status = LIB$_INSVIRMEM;
      else
         status = SS$_ABORT;
   }

   if (xmlptr->ParserPtr) XML_ParserFree (xmlptr->ParserPtr);

   if (xmlsts == XML_STATUS_ERROR)
   {
      DavWebResponse (rqptr, 400, 0, "XML_STATUS_ERROR", FI_LI);
      return (status);
   }

   if (WATCHING (rqptr, WATCH_WEBDAV))
   {
      if (STR_DSC_LEN(&xmlptr->PropRemoveDsc))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "REMOVE");
      STR_DSC_ITERATE (sdptr, &xmlptr->PropRemoveDsc)
         if (STR_DSC_LEN(sdptr))
            WatchDataFormatted ("!AZ\n", STR_DSC_PTR(sdptr));

      if (STR_DSC_LEN(&xmlptr->PropSetDsc))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "SET");
      STR_DSC_ITERATE (sdptr, &xmlptr->PropSetDsc)
         if (STR_DSC_LEN(sdptr))
            WatchDataFormatted ("!AZ\n", STR_DSC_PTR(sdptr));
   }

   if (WATCHING (rqptr, WATCH_WEBDAV)) DavXmlWatchProp (rqptr);

   return (status);
}

/*****************************************************************************/
/*
Return true if error was "out of memory", false if not.
*/

BOOL DavXmlMemoryError
(
REQUEST_STRUCT *rqptr,
XML_Parser *XmlParserPtr
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCH_XML && WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavXmlMemoryError()");

   return (XML_GetErrorCode(XmlParserPtr) == XML_ERROR_NO_MEMORY);
}

/*****************************************************************************/
/*
The ExPat XML parser has returned an error code.
Generate a WATCH item and textual report describing that error.
*/

DavXmlParseError
(
REQUEST_STRUCT *rqptr,
XML_Parser *XmlParserPtr,
STR_DSC *xdptr,
STR_DSC *sdptr
)
{
   int  cnt, column, line, status, xmlsts;
   char  *cptr, *eptr, *sptr;
   WEBDAV_TASK  *tkptr;
   WEBDAV_XML  *xmlptr;

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

   if (WATCH_XML && WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavXmlParseError()");

   tkptr = rqptr->WebDavTaskPtr;
   xmlptr = &tkptr->XmlData;

   line = cnt = XML_GetCurrentLineNumber(XmlParserPtr);
   column = XML_GetCurrentColumnNumber(XmlParserPtr);
   eptr = (char*)XML_ErrorString(XML_GetErrorCode(XmlParserPtr));

   /* delimit the problematic line */
   if (cnt) cnt--;
   for (cptr = STR_DSC_PTR(xdptr); *cptr && cnt; cptr++)
      if (*cptr == '\n') cnt--;
   for (sptr = cptr; *sptr && *sptr != '\n'; sptr++);

   if (WATCHING (rqptr, WATCH_WEBDAV))
   {
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                 "XML parse ERROR line:!UL column:!UL !AZ\n!#AZ\n!#*-^",
                 line, column, eptr, sptr-cptr, cptr, column);
      WatchDataDump (cptr, sptr-cptr);
   }

   /* create an error report that is also available for interrogation */
   StrDscBegin (rqptr, sdptr, -1);
   FaoToBuffer (sdptr, -1, NULL,
                "XML parse ERROR line:!UL column:!UL !AZ:\n!#AZ\n!#*-^",
                line, column, eptr, sptr-cptr, cptr, column);
}

/*****************************************************************************/
/*
This function and DavXmlEndElement() set various flags to indicate the state of
the current XML element.  From that various flags indicating properties to be
manipulated are set.

This pretty-much assumes that the parsing library is ensuring document
well-formedness, so the checks on element nesting are very elementary.
*/

void XMLCALL DavXmlStartElement
(
REQUEST_STRUCT *rqptr,
char *ElementPtr,
char **AttrPtr
)
{
   int  idx;
   char  *cptr, *sptr;
   STR_DSC  *sdptr;
   STR_DSC_AUTO (ScratchDsc);
   WEBDAV_LOCK  *lckptr;
   WEBDAV_PROP  *proptr;
   WEBDAV_TASK  *tkptr;
   WEBDAV_XML  *xmlptr;

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

   if (WATCH_XML && WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavXmlStartElement() !UL",
                 rqptr->WebDavTaskPtr->XmlData.ElementDepth+1);

   tkptr = rqptr->WebDavTaskPtr;
   lckptr = &tkptr->LockData;
   proptr = &tkptr->PropData;
   xmlptr = &tkptr->XmlData;

   xmlptr->ElementDepth++;

   if (WATCH_XML && WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
   {
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "ELEMENT !AZ", ElementPtr);
      for (idx = 0; AttrPtr[idx]; idx += 2)
         WatchDataFormatted ("!AZ=\"!AZ\"\n", AttrPtr[idx], AttrPtr[idx+1]);
   }

   sdptr = NULL;
   if (WEBDAV_IS_PARSING(xmlptr->ParseSet) &&
       WEBDAV_IS_PARSING(xmlptr->ParseProp))
      sdptr = &xmlptr->PropSetDsc;
   else
   if (WEBDAV_IS_PARSING(xmlptr->ParseRemove) &&
       WEBDAV_IS_PARSING(xmlptr->ParseProp))
      sdptr = &xmlptr->PropRemoveDsc;
   else
   if (WEBDAV_IS_PARSING(xmlptr->ParseLockOwner))
      sdptr = &lckptr->OwnerDsc;

   if (WEBDAV_IS_PARSING(xmlptr->ParseLockInfo))
   {
      /************/
      /* lockinfo */
      /************/

      StrDscColIfNotBegin (rqptr, &lckptr->OwnerDsc, 256);
      /* initialise the descriptor to reallocate expanded storage */
      STR_DSC_REALLOC(&lckptr->OwnerDsc, 256);

      if (WEBDAV_IS_PARSING(xmlptr->ParseLockType))
      {
         if (WEBDAV_IS_DAVEL(ElementPtr, "write"))
            lckptr->Type = WEBDAV_LOCK_TYPE_WRITE;
      }
      else
      if (WEBDAV_IS_PARSING(xmlptr->ParseLockScope))
      {
         if (WEBDAV_IS_DAVEL(ElementPtr, "exclusive"))
            lckptr->Scope = WEBDAV_LOCK_SCOPE_EXCLUSIVE;
         else
         if (WEBDAV_IS_DAVEL(ElementPtr, "shared"))
            lckptr->Scope = WEBDAV_LOCK_SCOPE_SHARED;
      }
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "locktype"))
         WEBDAV_PARSING(xmlptr->ParseLockType);
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "lockscope"))
         WEBDAV_PARSING(xmlptr->ParseLockScope);
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "owner"))
      {
         WEBDAV_PARSING(xmlptr->ParseLockOwner);
         WEBDAV_PARSING(xmlptr->ParseCharData);
      }
   }
   else
   if (WEBDAV_IS_PARSING(xmlptr->ParsePropFind))
   {
      /************/
      /* propfind */
      /************/

      if (WEBDAV_IS_DAVEL(ElementPtr, "allprop"))
         proptr->AllProp = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "creationdate"))
         proptr->CreationDate = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "displayname"))
         proptr->DisplayName = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "resourcetype"))
         proptr->ResourceType = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "getcontenttype"))
         proptr->GetContentType = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "getcontentlength"))
         proptr->GetContentLength = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "getetag"))
         proptr->GetEtag = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "getlastmodified"))
         proptr->GetLastModified = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "lockdiscovery"))
         proptr->LockDiscovery = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "propname"))
         proptr->PropName = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "supportedlock"))
         proptr->SupportedLock = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "quota-available-bytes"))
         proptr->QuotaAvailableBytes = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "quota-used-bytes"))
         proptr->QuotaUsedBytes = true;
      else
      if (!WEBDAV_IS_DAVEL(ElementPtr, "prop"))
      {
         /* stored and searched for in the dead properties */
         sdptr = &xmlptr->PropFindDsc;
         proptr->FindProp = true;
         WEBDAV_PARSING(xmlptr->ParseFind);
      }

      if (WATCHING (rqptr, WATCH_WEBDAV)) DavXmlWatchProp (rqptr);
   }
   else
   if (WEBDAV_IS_PARSING(xmlptr->ParsePropertyBehavior))
   {
      /********************/
      /* propertybehavior */
      /********************/

      if (WEBDAV_IS_DAVEL(ElementPtr, "keepalive"))
         proptr->PropertyBehaviorKeepalive = true;
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "omit"))
         proptr->PropertyBehaviorOmit = true;
   }
   else
   if (WEBDAV_IS_PARSING(xmlptr->ParsePropertyUpdate))
   {
      /******************/
      /* propertyupdate */
      /******************/

      if (WEBDAV_IS_PARSING(xmlptr->ParseSet) ||
          WEBDAV_IS_PARSING(xmlptr->ParseRemove))
      {
         if (WEBDAV_IS_DAVEL(ElementPtr, "prop"))
            WEBDAV_PARSING(xmlptr->ParseProp);
      }
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "set"))
         WEBDAV_PARSING(xmlptr->ParseSet);
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "remove"))
         WEBDAV_PARSING(xmlptr->ParseRemove);
   }
   else
   if (WEBDAV_IS_DAVEL(ElementPtr, "lockinfo"))
      WEBDAV_PARSING(xmlptr->ParseLockInfo);
   else
   if (WEBDAV_IS_DAVEL(ElementPtr, "propfind"))
      WEBDAV_PARSING(xmlptr->ParsePropFind);
   else
   if (WEBDAV_IS_DAVEL(ElementPtr, "propertybehavior"))
      WEBDAV_PARSING(xmlptr->ParsePropertyBehavior);
   else
   if (WEBDAV_IS_DAVEL(ElementPtr, "propertyupdate"))
      WEBDAV_PARSING(xmlptr->ParsePropertyUpdate);

   if (sdptr)
   {
      for (cptr = ElementPtr; *cptr && *cptr != ' '; cptr++);
      sdptr = StrDscBuild (sdptr, NULL, "<NS:");
      StrDscBuild (sdptr, NULL, cptr+1);
      for (idx = 0; AttrPtr[idx]; idx += 2)
      {
         StrDscBuild (sdptr, NULL, " ");
         StrDscBuild (sdptr, NULL, AttrPtr[idx]);
         StrDscBuild (sdptr, NULL, "=\"");
         StrDscBuild (sdptr, NULL, AttrPtr[idx+1]);
         StrDscBuild (sdptr, NULL, "\"");
      }
      StrDscBuild (sdptr, NULL, " xmlns:NS=\"");
      StrDscThis (NULL, &ScratchDsc, ElementPtr, cptr - ElementPtr);
      StrDscBuild (sdptr, &ScratchDsc, NULL);
      StrDscBuild (sdptr, NULL, "\">");

      if (WEBDAV_IS_PARSING(xmlptr->ParsePropFind))
         STR_DSC_SET_FROZEN (sdptr)

      WEBDAV_PARSING(xmlptr->ParseCharData);
   }
}

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

void XMLCALL DavXmlEndElement
(
REQUEST_STRUCT *rqptr,
char *ElementPtr
)
{
   char  *cptr, *sptr;
   STR_DSC  *sdptr;
   STR_DSC_AUTO (ScratchDsc);
   WEBDAV_LOCK  *lckptr;
   WEBDAV_TASK  *tkptr;
   WEBDAV_XML  *xmlptr;

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

   if (WATCH_XML && WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavXmlEndElement() !UL !AZ",
                 rqptr->WebDavTaskPtr->XmlData.ElementDepth, ElementPtr);

   tkptr = rqptr->WebDavTaskPtr;
   lckptr = &tkptr->LockData;
   xmlptr = &tkptr->XmlData;

   if (WEBDAV_IS_PARSING(xmlptr->ParseLockInfo))
   {
      if (WEBDAV_IS_PARSING(xmlptr->ParseLockScope) &&
          WEBDAV_IS_DAVEL(ElementPtr, "lockscope"))
         WEBDAV_PARSED(xmlptr->ParseLockScope);
      else
      if (WEBDAV_IS_PARSING(xmlptr->ParseLockType) &&
          WEBDAV_IS_DAVEL(ElementPtr, "locktype"))
         WEBDAV_PARSED(xmlptr->ParseLockType);
      else
      if (WEBDAV_IS_PARSING(xmlptr->ParseLockOwner) &&
          WEBDAV_IS_DAVEL(ElementPtr, "owner"))
      {
         WEBDAV_PARSED(xmlptr->ParseLockOwner);
         DavXmlTrimElement (&lckptr->OwnerDsc);
         if (WATCH_XML && WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                       "!&Z", STR_DSC_PTR(&lckptr->OwnerDsc));
      }
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "lockinfo"))
         WEBDAV_PARSED(xmlptr->ParseLockInfo);
   }
   else
   if (WEBDAV_IS_PARSING(xmlptr->ParsePropFind))
   {
      if (WEBDAV_IS_DAVEL(ElementPtr, "propfind"))
         WEBDAV_PARSED(xmlptr->ParsePropFind);
      else
         WEBDAV_PARSED(xmlptr->ParseFind);
   }
   else
   if (WEBDAV_IS_PARSING(xmlptr->ParsePropertyBehavior))
   {
      if (WEBDAV_IS_DAVEL(ElementPtr, "propertybehavior"))
         WEBDAV_PARSED(xmlptr->ParsePropertyBehavior);
   }
   else
   if (WEBDAV_IS_PARSING(xmlptr->ParsePropertyUpdate))
   {
      if (WEBDAV_IS_PARSING(xmlptr->ParseSet))
      {
         if (WEBDAV_IS_PARSING(xmlptr->ParseProp))
         {
            if (WEBDAV_IS_DAVEL(ElementPtr, "prop"))
               WEBDAV_PARSED(xmlptr->ParseProp);
         }
         else
         if (WEBDAV_IS_DAVEL(ElementPtr, "set"))
            WEBDAV_PARSED(xmlptr->ParseSet);
      }
      else
      if (WEBDAV_IS_PARSING(xmlptr->ParseRemove))
      {
         if (WEBDAV_IS_PARSING(xmlptr->ParseProp))
         {
            if (WEBDAV_IS_DAVEL(ElementPtr, "prop"))
               WEBDAV_PARSED(xmlptr->ParseProp);
         }
         else
         if (WEBDAV_IS_DAVEL(ElementPtr, "remove"))
            WEBDAV_PARSED(xmlptr->ParseRemove);
      }
      else
      if (WEBDAV_IS_DAVEL(ElementPtr, "propertyupdate"))
         WEBDAV_PARSED(xmlptr->ParsePropertyUpdate);
   }

   sdptr = NULL;
   if (WEBDAV_IS_PARSING(xmlptr->ParseSet) &&
       WEBDAV_IS_PARSING(xmlptr->ParseProp))
      sdptr = &xmlptr->PropSetDsc;
   else
   if (WEBDAV_IS_PARSING(xmlptr->ParseRemove) &&
       WEBDAV_IS_PARSING(xmlptr->ParseProp))
      sdptr = &xmlptr->PropRemoveDsc;
   else
   if (WEBDAV_IS_PARSING(xmlptr->ParseLockOwner))
      sdptr = &lckptr->OwnerDsc;

   if (sdptr)
   {
      for (cptr = ElementPtr; *cptr && *cptr != ' '; cptr++);
      sdptr = StrDscBuild (sdptr, NULL, "</NS:");
      StrDscBuild (sdptr, NULL, cptr+1);
      StrDscBuild (sdptr, NULL, ">");

      if (WEBDAV_IS_PARSING(xmlptr->ParseProp) &&
          xmlptr->ElementDepth == 4)
      {
         /* end of this property, freeze current content */ 
         STR_DSC_SET_FROZEN (sdptr)
         WEBDAV_NOT_PARSING(xmlptr->ParseCharData);
      }
      else
         WEBDAV_PARSING(xmlptr->ParseCharData);
   }
   else
      WEBDAV_NOT_PARSING(xmlptr->ParseCharData);

   if (xmlptr->ElementDepth) xmlptr->ElementDepth--;
}

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

void XMLCALL DavXmlCharData
(
REQUEST_STRUCT *rqptr,
XML_Char *DataPtr,
int DataLength
)
{
   STR_DSC  *sdptr;
   STR_DSC_AUTO (CharDataDsc);
   WEBDAV_LOCK  *lckptr;
   WEBDAV_TASK  *tkptr;
   WEBDAV_XML  *xmlptr;

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

   if (WATCH_XML && WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavXmlCharData() !&B !UL |!#AZ",
                 rqptr->WebDavTaskPtr->XmlData.ParseCharData,
                 DataLength, DataLength, DataPtr);

   tkptr = rqptr->WebDavTaskPtr;
   lckptr = &tkptr->LockData;
   xmlptr = &tkptr->XmlData;

   if (!WEBDAV_IS_PARSING(xmlptr->ParseCharData)) return;

   if (WEBDAV_IS_PARSING(xmlptr->ParseSet) &&
       WEBDAV_IS_PARSING(xmlptr->ParseProp))
      sdptr = &xmlptr->PropSetDsc;
   else
   if (WEBDAV_IS_PARSING(xmlptr->ParseRemove) &&
       WEBDAV_IS_PARSING(xmlptr->ParseProp))
      sdptr = &xmlptr->PropRemoveDsc;
   else
   if (WEBDAV_IS_PARSING(xmlptr->ParseLockOwner))
      sdptr = &lckptr->OwnerDsc;
   else
      sdptr = NULL;

   if (sdptr)
   {
      StrDscThis (NULL, &CharDataDsc, DataPtr, DataLength);
      StrDscBuild (sdptr, &CharDataDsc, NULL);
   }
}

/*****************************************************************************/
/*
Trim leading and trailing white-space from a string containing an XML element.
*/

DavXmlTrimElement (STR_DSC *sdptr)

{
   char  *cptr, *sptr;

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

   if (!STR_DSC_PTR(sdptr) || !STR_DSC_LEN(sdptr)) return;

   for (cptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
        cptr > STR_DSC_PTR(sdptr) && *cptr != '>';
        cptr--);
   if (cptr > STR_DSC_PTR(sdptr))
   {
      *++cptr = '\0';
      STR_DSC_LEN(sdptr) = cptr - STR_DSC_PTR(sdptr);
   }
   for (cptr = STR_DSC_PTR(sdptr); *cptr && *cptr != '<'; cptr++);
   if (cptr > STR_DSC_PTR(sdptr) &&
       cptr < STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr))
   {
      for (sptr = STR_DSC_PTR(sdptr); *cptr; *sptr++ = *cptr++);
      *sptr = '\0';
      STR_DSC_LEN(sdptr) = sptr - STR_DSC_PTR(sdptr);
   }
}

/*****************************************************************************/
/*
Report on the XML.
*/ 

DavXmlReport (REQUEST_STRUCT *rqptr)

{
   static char  ReportFormFao [] =
"<form action=\"" ADMIN_REPORT_WEBDAV_XML "\" METHOD=\"POST\" \
ENCtype=\"application/x-www-form-urlencoded\">\n\
<p><table cellpadding=\"1\" cellspacing=\"0\" border=\"0\">\n\
<tr><td>\
<textarea name=\"XMLtext\" id=\"XMLtext\" \
rows=\"24\" cols=\"80\" nowrap></textarea>\n\
</td></tr>\n\
</table>\n\
<p><input type=\"submit\" name=\"submit\" value=\"Parse XML\">\
&nbsp;&nbsp;<input type=\"submit\" name=\"submit\" value=\"Display XML\">\
&nbsp;&nbsp;<input type=\"submit\" name=\"submit\" value=\"Display Plain\">\
&nbsp;&nbsp;&nbsp;<input type=\"reset\" value=\"Clear\">\n\
</form>\n\
</body>\n\
</html>\n";

   static char  ReportPage1Fao [] =
"<p><table cellpadding=\"5\" cellspacing=\"0\" border=\"1\">\n\
<tr><td>";

   static char  ReportPage2Fao [] =
"</td></tr>\n\
</table>\n\
</tr></td>\n\
</body>\n\
</html>\n";

   BOOL  DisplayPlain,
         DisplayXML,
         ParseXML;
   int  qlen, status,
        FieldValueSize,
        XmlTextLength;
   unsigned long  FaoVector [16];
   unsigned long  *vecptr;
   char  *qptr,
         *FieldValuePtr,
         *XmlTextPtr;
   char  FieldName [256],
         FieldValue [256];
   BODY_PROCESS  *prptr;
   STR_DSC_AUTO (XmlTextDsc);
   WEBDAV_TASK  *tkptr;
   WEBDAV_XML  *xmlptr;

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

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

   if (rqptr->rqHeader.ContentLength64)
   {
      if (!rqptr->rqBody.DataPtr)
      {
         /* read all of request body and then AST back to this function */
         BodyReadBegin (rqptr, &DavXmlReport, &BodyProcessReadAll);
         return;
      }
      if (rqptr->rqBody.DataStatus != SS$_ENDOFFILE)
      {
         DavWebEnd (rqptr);
         return;
      }
      if (!(prptr = rqptr->rqBody.ProcessPtr))
      {
         ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
         rqptr->rqResponse.ErrorTextPtr = ErrorSanityCheck;
         ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI);
         DavWebEnd (rqptr);
         return;
      }
      FieldValueSize = prptr->BlockBufferCount;
   }
   else
      FieldValueSize = 256;

   FieldValuePtr = (char*)VmGetHeap (rqptr, FieldValueSize);

   DisplayPlain = DisplayXML = ParseXML = false;
   XmlTextPtr = "";

   if (rqptr->rqHeader.Method == HTTP_METHOD_POST)
   {
      if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!UL bytes",
                    prptr->BlockBufferCount);
      qptr = prptr->BlockBufferPtr;
      qlen = prptr->BlockBufferCount;
   }
   else
   if (rqptr->rqHeader.QueryStringLength)
   {
      qptr = rqptr->rqHeader.QueryStringPtr;
      qlen = rqptr->rqHeader.QueryStringLength;
   }
   else
   {
      qptr = "";
      qlen = 0;
   }

   while (*qptr)
   {
      status = StringParseQuery (&qptr, FieldName, sizeof(FieldName),
                                        FieldValuePtr, FieldValueSize);
      if (VMSnok (status))
      {
         /* error occured */
         if (status == SS$_IVCHAR) rqptr->rqResponse.HttpStatus = 400;
         rqptr->rqResponse.ErrorTextPtr = "parsing query string";
         ErrorVmsStatus (rqptr, status, FI_LI);
         DavWebEnd (rqptr);
         return;
      }
      if (strsame (FieldName, "XMLtext", -1))
      {
         XmlTextPtr = FieldValuePtr;
         /* subsequent fields into fixed storage */
         FieldValuePtr = FieldValue;
         FieldValueSize = sizeof(FieldValue);
      }
      if (strsame (FieldName, "submit", -1))
      {
         if (strsame (FieldValue, "Display XML", -1))
            DisplayXML = true;
         else
         if (strsame (FieldValue, "Display Plain", -1))
            DisplayPlain = true;
         else
         if (strsame (FieldValue, "Parse XML", -1))
            ParseXML = true;
      }
   }

   if (DisplayXML)
   {
      StrDscBegin (rqptr, &rqptr->NetWriteBufferDsc,
                   rqptr->rqHeader.ContentLength64);
      StrDscBuild (&rqptr->NetWriteBufferDsc, NULL, XmlTextPtr);
      XmlTextLength = StrDscLength (&rqptr->NetWriteBufferDsc);
      ResponseHeader (rqptr, 200, "text/xml; charset=\"utf-8\"",
                      XmlTextLength, NULL, NULL);
      DavWebEnd (rqptr);
      return;
   }

   if (DisplayPlain)
   {
      StrDscBegin (rqptr, &rqptr->NetWriteBufferDsc,
                   rqptr->rqHeader.ContentLength64);
      StrDscBuild (&rqptr->NetWriteBufferDsc, NULL, XmlTextPtr);
      XmlTextLength = StrDscLength (&rqptr->NetWriteBufferDsc);
      ResponseHeader (rqptr, 200, "text/plain", XmlTextLength, NULL, NULL);
      DavWebEnd (rqptr);
      return;
   }

   StrDscThis (NULL, &XmlTextDsc, XmlTextPtr, strlen(XmlTextPtr));

   AdminPageTitle (rqptr, "WebDAV XML Parse Test Tool");

   if (rqptr->rqHeader.Method == HTTP_METHOD_POST)
   {
      /* set up the task structure for the duration of the parse */
      tkptr = rqptr->WebDavTaskPtr = VmGetHeap (rqptr, sizeof(WEBDAV_TASK));
      xmlptr = &tkptr->XmlData;

      DavXmlParseText (rqptr, &XmlTextDsc);

      NetWriteBuffered (rqptr, NULL, ReportPage1Fao, sizeof(ReportPage1Fao)-1);

      if (STR_DSC_PTR(&xmlptr->ParseErrorDsc))
         FaoToNet (rqptr, "<pre>!&;AZ</pre>",
                   STR_DSC_PTR(&xmlptr->ParseErrorDsc));
      else
         DavXmlReportOnParse (rqptr);

      NetWriteBuffered (rqptr, NULL, ReportPage2Fao, sizeof(ReportPage2Fao)-1);
      rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;

      VmFreeFromHeap (rqptr, rqptr->WebDavTaskPtr, FI_LI);
      rqptr->WebDavTaskPtr = NULL;
   }
   else
   {
      FaolToNet (rqptr, ReportFormFao, &FaoVector);
      rqptr->rqResponse.PreExpired = 0;
   }

   ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc);

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
Report on the XML elements.
*/ 
 
DavXmlReportOnParse (REQUEST_STRUCT *rqptr)

{
   STR_DSC  *sdptr;
   WEBDAV_LOCK  *lckptr;
   WEBDAV_PROP  *proptr;
   WEBDAV_TASK  *tkptr;
   WEBDAV_XML  *xmlptr;

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

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

   tkptr = rqptr->WebDavTaskPtr;
   lckptr = &tkptr->LockData;
   proptr = &tkptr->PropData;
   xmlptr = &tkptr->XmlData;

   FaoToNet (rqptr, "<ul>\n");

   if (WEBDAV_IS_PARSED(xmlptr->ParseLockInfo))
   {
      FaoToNet (rqptr, "<p><li><b>lockinfo</b>\n<ul>\n");
      if (lckptr->Type == WEBDAV_LOCK_TYPE_WRITE)
         FaoToNet (rqptr, "<li>write\n");
      if (lckptr->Scope == WEBDAV_LOCK_SCOPE_EXCLUSIVE)
         FaoToNet (rqptr, "<li>exclusive\n");
      else
      if (lckptr->Scope == WEBDAV_LOCK_SCOPE_SHARED)
         FaoToNet (rqptr, "<li>shared\n");
      if (STR_DSC_PTR(&lckptr->OwnerDsc))
         FaoToNet (rqptr, "<li>owner:<pre>!&;AZ</pre>\n",
                   STR_DSC_PTR(&lckptr->OwnerDsc));
      FaoToNet (rqptr, "</ul>\n");
   }

   if (WEBDAV_IS_PARSED(xmlptr->ParsePropFind))
   {
      FaoToNet (rqptr, "<p><li><b>propfind</b>\n<ul>\n");
      if (proptr->AllProp) FaoToNet (rqptr, "<li>allprop\n");
      if (proptr->CreationDate) FaoToNet (rqptr, "<li>creationdate\n");
      if (proptr->DisplayName) FaoToNet (rqptr, "<li>displayname\n");
      if (proptr->ResourceType) FaoToNet (rqptr, "<li>resourcetype\n");
      if (proptr->GetContentType) FaoToNet (rqptr, "<li>getcontenttype\n");
      if (proptr->GetContentLength) FaoToNet (rqptr, "<li>getcontentlength\n");
      if (proptr->GetEtag) FaoToNet (rqptr, "<li>getetag\n");
      if (proptr->GetLastModified) FaoToNet (rqptr, "<li>getlastmodified\n");
      if (proptr->LockDiscovery) FaoToNet (rqptr, "<li>lockdiscovery\n");
      if (proptr->PropName) FaoToNet (rqptr, "<li>propname\n");
      if (proptr->SupportedLock) FaoToNet (rqptr, "<li>supportedlock\n");
      if (proptr->QuotaAvailableBytes) FaoToNet (rqptr, "<li>quota-available-bytes\n");
      if (proptr->QuotaUsedBytes) FaoToNet (rqptr, "<li>quota-used-bytes\n");
      FaoToNet (rqptr, "</ul>\n");
   }

   if (WEBDAV_IS_PARSED(xmlptr->ParsePropertyBehavior))
   {
      FaoToNet (rqptr, "<p><li><b>propertybehaviour</b>\n<ul>\n");
      if (proptr->PropertyBehaviorKeepalive) FaoToNet (rqptr, "<li>keepalive\n");
      if (proptr->PropertyBehaviorOmit) FaoToNet (rqptr, "<li>omit\n");
      FaoToNet (rqptr, "</ul>\n");
   }

   STR_DSC_ITERATE (sdptr, &xmlptr->PropSetDsc)
      if (STR_DSC_LEN(sdptr))
         FaoToNet (rqptr, "<p><li><b>set&nbsp;prop</b><pre>!&;AZ</pre>\n",
                   STR_DSC_PTR(sdptr));

   STR_DSC_ITERATE (sdptr, &xmlptr->PropRemoveDsc)
      if (STR_DSC_LEN(sdptr))
         FaoToNet (rqptr, "<p><li><b>remove&nbsp;prop</b><pre>!&;AZ</pre>\n",
                   STR_DSC_PTR(sdptr));

   FaoToNet (rqptr, "</ul><p>\n");
}

/*****************************************************************************/
/*
WATCH the XML elements.
*/ 
 
DavXmlWatchProp (REQUEST_STRUCT *rqptr)

{
   int  size;
   char  buf [1024];
   STR_DSC  *sdptr;
   WEBDAV_LOCK  *lckptr;
   WEBDAV_PROP  *proptr;
   WEBDAV_TASK  *tkptr;
   WEBDAV_XML  *xmlptr;

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

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

   tkptr = rqptr->WebDavTaskPtr;
   lckptr = &tkptr->LockData;
   proptr = &tkptr->PropData;
   xmlptr = &tkptr->XmlData;

   if (WEBDAV_IS_PARSED(xmlptr->ParseLockInfo))
   {
      buf[0] = '\0';
      size = sizeof(buf);
      if (STR_DSC_LEN(&lckptr->OwnerDsc))
         FaoToBuffer (buf, sizeof(buf)/2, NULL, " owner:\"!AZ\"",
                      STR_DSC_PTR(&lckptr->OwnerDsc));
      if (lckptr->Type == WEBDAV_LOCK_TYPE_WRITE) strzcat (buf, " write", size);
      if (lckptr->Scope == WEBDAV_LOCK_SCOPE_EXCLUSIVE)
         strzcat (buf, " exclusive", size);
      else
      if (lckptr->Scope == WEBDAV_LOCK_SCOPE_SHARED)
         strzcat (buf, " shared", size);
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "LOCKINFO!AZ", buf);
   }

   if (WEBDAV_IS_PARSED(xmlptr->ParsePropFind))
   {
      buf[0] = '\0';
      size = sizeof(buf);
      if (proptr->AllProp) strzcat (buf, " allprop", size);
      if (proptr->CreationDate) strzcat (buf, " creationdate", size);
      if (proptr->DisplayName) strzcat (buf, " displayname", size);
      if (proptr->ResourceType) strzcat (buf, " resourcetype", size);
      if (proptr->GetContentType) strzcat (buf, " getcontenttype", size);
      if (proptr->GetContentLength) strzcat (buf, " getcontentlength", size);
      if (proptr->GetEtag) strzcat (buf, " getetag", size);
      if (proptr->GetLastModified) strzcat (buf, " getlastmodified", size);
      if (proptr->LockDiscovery) strzcat (buf, " lockdiscovery", size);
      if (proptr->PropName) strzcat (buf, " propname", size);
      if (proptr->SupportedLock) strzcat (buf, " supportedlock", size);
      if (proptr->QuotaAvailableBytes) strzcat (buf, " quota-available-bytes", size);
      if (proptr->QuotaUsedBytes) strzcat (buf, "quota-used-bytes ", size);
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "PROPFIND!AZ", buf);
   }

   if (WEBDAV_IS_PARSED(xmlptr->ParsePropertyBehavior))
   {
      buf[0] = '\0';
      size = sizeof(buf);
      if (proptr->PropertyBehaviorKeepalive) strzcat (buf, " keepalive", size);
      if (proptr->PropertyBehaviorOmit) strzcat (buf, " omit", size);
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "PROPERTYBEHAVIOUR!AZ", buf);
   }

   STR_DSC_ITERATE (sdptr, &xmlptr->PropSetDsc)
      if (STR_DSC_LEN(sdptr))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "SET !AZ",
                    STR_DSC_PTR(sdptr));

   STR_DSC_ITERATE (sdptr, &xmlptr->PropRemoveDsc)
      if (STR_DSC_LEN(sdptr))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "REMOVE !AZ",
                    STR_DSC_PTR(sdptr));
}

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


