/*****************************************************************************/
/*
                                  Put.c

PUT, POST or DELETE a document.  Although the HTTP/1.1 specification
differentiates between the PUT and POST functionality this module does not.
Allows documents (files) and directories (i.e. specification ending in a slash)
to be created.  Either the DELETE method or a kludge allows these to be
deleted.  The kludge: if a wildcard version (";*") is included with the
specification the respective file or directory is deleted with either of the
non-DELETE methods.


Access Control
--------------

Relies on HTTPd authentication.  If a remote username has not been verified an
automatic no-privilege error is generated.

Access to create or delete documents is also controlled by any
permissions/protections/ACLs, etc. on the parent directory controlling access
by the HTTPd server account (usually HTTP$SERVER).  The explicit action
required to grant the server account access to a directory it needs to write
into is a bit of a nuisance, but deemed a good double check, preventing access
due to flawed authentication/authorization configuration (and hopefully even
a range of possible design problems or coding errors within this server :^)

Setting ACLs is preferable to granting world write access (obviously):

This ACE would grant PUT, POST or DELETE access to the server:

   $ SET ACL directory.DIR /ACL=(IDENT=HTTP$SERVER,ACCESS=READ+WRITE)

This ACE would explcitly deny POST access to the server:

   $ SET ACL directory.DIR /ACL=(IDENT=HTTP$SERVER,ACCESS=READ)

This ACE would explcitly deny ALL access to the server:

   $ SET ACL directory.DIR /ACL=(IDENT=HTTP$SERVER,ACCESS=NONE)

BE ULTRA-CAUTIOUS ... check access to directory before undertaking any action
that will alter anything within it, even if doing that potentially involves
some redundant processesing/checking!  In this way any programming oversights
will hopefully be ameliorated.

File Record Format
------------------

Text files are created with a STREAM_LF format and CR implied carriage-control. 
Binary files are created in undefined (UDF) record format with no record
attributes (carriage-control).  File writing is done using block I/O for
efficiency and record independence.


Content-Type: application/x-www-form-urlencoded
-----------------------------------------------

If the document 'Content-Type:' is "application/x-www-form-urlencoded" (i.e.
generated from an HTML form) all field names and delimiting symbols are
eliminated and the field(s) content only converted into plain text.  Hence a
text document can be POSTed using an HTML form with only the content ending up
in the file.  This processing is  performed by BodProcessUrlEncoded().


Content-Type: multipart/form-data
---------------------------------

This module can process a request body according to RFC-1867, "Form-based File
Upload in HTML".  As yet it is not a full implementation.  It will not process
"multipart/mixed" subsections.  The 'Content-Type:' is "multipart/form-data".
in the file.  This processing is performed by BodProcessMultipartFormData().


Other Considerations
--------------------

PUT/POSTed files automatically have a three version limit imposed.

If an error occurs (message generated) a file created by the request is
deleted.


WebDAV Support
--------------
The PUT module was essentially suitable for WebDAV support.  Only minor
modifications were made to support WebDAV's authorization model, agent
idiosyncracies, etc, and to shield WebDAV requests from the historical
idiosyncracies of WASD's PUT method support :-)


VERSION HISTORY
---------------
01-NOV-2018  MGD  bugfix; PutWriteFileOpen() override incompatible existing
                    file characteristics by first erasing the file
06-JUL-2014  MGD  PutWebDavBegin() migrate into DavWebPutBegin()
                  bugfix; PutWriteFileOpen() WebDAV should not use default
                    protection mask and instead propagate from profile
17-AUG-2013  MGD  PutWriteFileOpen() support FAB$C_STM and FAB$C_STMCR
10-MAY-2010  JPP  bugfix; PutWriteFileOpen() ensure SYSPRV enabled before
                  $ERASE() if not WebDAV request (for access and ownership)
24-MAR-2010  MGD  bugfix; PutWriteFileOpen() ensure SYSPRV enabled before
                  $CREATE() if not WebDAV request (for access and ownership)
21-JUN-2009  MGD  PutWriteFileOpen() allow record format of binary files
                  to be specified by global configuration and path setting
01-MAY-2007  MGD  WebDAV considerations,
                  "If-Match:.." and "If-Not-Match:.." conditional processing,
                  pre-allocate space if the size is fixed (viz. not a form),
                  FAB and RAB flags to enable truncate-on-put,
                  PutBegin() remove 'SpecificDirectory' parameter
30-OCT-2004  MGD  bugfix; PutWriteFileOpen() always check 'prptr' non-NULL
24-JUL-2004  MGD  bugfix; (potential anyway) PutWriteFileClose()/PutEnd(),
                  in the absence of a content-type assume bag-o'-bytes
10-JAN-2004  MGD  PutWriteFileOpen() 'delete-on-close' file specification
                  extended to include a four digit global 'uniquifier'
                  (with faster systems and multiple instances it was entirely
                  possible that purely a time-based name might be inadequite)
23-AUG-2002  MGD  set fab$b_rfm and fab$b_rat (is supplied with body) to
                  establish file attributes (falling back to type analysis)
02-FEB-2002  MGD  rework file processing for request body processing changes
04-AUG-2001  MGD  support module WATCHing
04-JUL-2000  MGD  redirect from POST (success=)
04-JAN-2000  MGD  support ODS-2 and ODS-5 using ODS module
12-MAR-1998  MGD  file protection may now be specified (as hexadecimal value)
25-SEP-1997  MGD  bugfix; PutProcessText() removed CRLF munging
17-AUG-1997  MGD  message database,
                  SYSUAF-authenticated users security-profile
27-MAR-1997  MGD  provide file edit preview (see UPD.C)
01-FEB-1997  MGD  HTTPd version 4
01-SEP-1996  MGD  provide "Content-Type: multipart/form-data" for file upload
06-APR-1996  MGD  initial development
*/
/*****************************************************************************/

#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 <stdio.h>
#include <string.h>

/* VMS related header files */
#include <acldef.h>
#include <armdef.h>
#include <chpdef.h>
#include <descrip.h>
#include <iodef.h>
#include <jpidef.h>
#include <prvdef.h>
#include <rms.h>
#include <rmsdef.h>
#include <ssdef.h>
#include <stsdef.h>

/* application header files */
#include "wasd.h"

#define WASD_MODULE "PUT"

/***************/
/* definitions */
/***************/

#define PUT_FILE 0x01
#define PUT_DIRECTORY 0x02
#define PUT_UPLOAD 0x04
#define PUT_CREATED 0x10
#define PUT_DELETED 0x20
#define PUT_SUPERCEDED 0x40

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

BOOL  PutOnlyTextFilesStreamLf = true;

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

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

extern BOOL  OdsExtended;

extern unsigned long  SysPrvMask[];

extern char  SoftwareID[],
             ErrorSanityCheck[];

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

/*****************************************************************************/
/*
Begin PUT processing.
*/ 
 
PutBegin
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction
)
{
   PUT_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PUT))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutBegin() !&A !UL !&Z",
                 NextTaskFunction, rqptr->rqHeader.ContentLength,
                 rqptr->ParseOds.NamDevicePtr);

   if (ERROR_REPORTED (rqptr))
   {
      /* previous error, cause threaded processing to unravel */
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   if (!rqptr->AccountingDone++)
      InstanceGblSecIncrLong (&AccountingPtr->DoPutCount);

   /* authentication is mandatory for a PUT, DELETE or POST */
   if (!rqptr->RemoteUser[0] ||
       !((rqptr->rqAuth.RequestCan & HTTP_METHOD_PUT) ||
         (rqptr->rqAuth.RequestCan & HTTP_METHOD_POST) ||
         (rqptr->rqAuth.RequestCan & HTTP_METHOD_DELETE)))
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      ErrorVmsStatus (rqptr, SS$_NOPRIV, FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   /* set up the task structure (only ever one per request!) */
   rqptr->PutTaskPtr = tkptr = (PUT_TASK*) VmGetHeap (rqptr, sizeof(PUT_TASK));
   tkptr->NextTaskFunction = NextTaskFunction;

   OdsStructInit (&tkptr->SearchOds, false);

   if (rqptr->WebDavTaskPtr)
      tkptr->ProtectionMask = 0;
   else
      tkptr->ProtectionMask = PUT_DEFAULT_FILE_PROTECTION;

   if (rqptr->rqHeader.IfMatchPtr ||
       rqptr->rqHeader.IfNoneMatchPtr)
      PutIfBegin (rqptr);
   else
   if (rqptr->WebDavTaskPtr)
      DavWebPutBegin (rqptr);
   else
      PutWasdBegin (rqptr);
}

/*****************************************************************************/
/*
Begin the WASD-idiosyncratic PUT processing.
*/ 
 
PutWasdBegin (REQUEST_STRUCT *rqptr)

{
   PUT_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PUT))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutWasdBegin()");

   tkptr = rqptr->PutTaskPtr;

   if ((rqptr->rqHeader.Method == HTTP_METHOD_DELETE) ||
       (rqptr->ParseOds.NamVersionPtr[0] == ';' &&
        rqptr->ParseOds.NamVersionPtr[1] == '*'))
   {
      /**********************************************/
      /* delete (or file/directory deletion kludge) */
      /**********************************************/

      /* terminate on either the directory or file name */
      if (!rqptr->ParseOds.NamNameLength &&
          !rqptr->ParseOds.NamTypeLength)
         rqptr->ParseOds.NamNamePtr[0] = '\0';
      else
         rqptr->ParseOds.NamVersionPtr[0] = '\0';

      if (WATCHING (rqptr, WATCH_RESPONSE))
         WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                    "PUT !AZ/delete !AZ",
                    rqptr->rqHeader.MethodName,
                    rqptr->ParseOds.NamDevicePtr);

      PutDelete (rqptr);
      PutEnd (rqptr);
      return;
   }

   if (!rqptr->ParseOds.NamNameLength &&
       !rqptr->ParseOds.NamTypeLength)
   {
      /********************/
      /* create directory */
      /********************/

      /* multipart/form-data supplies the parent directory as the path */
      if (!ConfigSameContentType (rqptr->rqHeader.ContentTypePtr,
                                  "multipart/", 10))
      {
         if (WATCHING (rqptr, WATCH_RESPONSE))
            WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                       "PUT !AZ/create !AZ",
                       rqptr->rqHeader.MethodName,
                       rqptr->ParseOds.NamDevicePtr);

         PutCreateDirectory (rqptr);
         PutEnd (rqptr);
         return;
      }
   }

   /* terminate on version for file name, name for directory */
   if (!rqptr->ParseOds.NamNameLength &&
       rqptr->ParseOds.NamTypeLength == 1 &&
       rqptr->ParseOds.NamVersionLength == 1)
      *rqptr->ParseOds.NamNamePtr = '\0';
   else
      *rqptr->ParseOds.NamVersionPtr = '\0';

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                 "PUT !AZ/create !AZ as \"!AZ\"",
                 rqptr->rqHeader.MethodName,
                 rqptr->ParseOds.NamDevicePtr,
                 rqptr->rqHeader.ContentTypePtr ?
                    rqptr->rqHeader.ContentTypePtr :
                    "(unspecified content-type)");

   if (ConfigSameContentType (rqptr->rqHeader.ContentTypePtr,
                              "application/x-www-form-urlencoded", -1))
      BodyReadBegin (rqptr, &PutWriteFile, &BodyProcessUrlEncoded);
   else
   if (ConfigSameContentType (rqptr->rqHeader.ContentTypePtr,
                              "multipart/", 10))
      BodyReadBegin (rqptr, &PutWriteFile, &BodyProcessMultipartFormData);
   else
   {
      tkptr->ContentLengthFixed = true;
      BodyReadBegin (rqptr, &PutWriteFile, &BodyProcessByVirtualBlock);
   }
}

/*****************************************************************************/
/*
If "If-Match: <entity>" or "If-Not-Match: <entity>" conditional processing has
been requested then get the (possible) file details, generate entity data from
those details, and continue processing (or not) according to the match.
This function (re)calls itself multiple times during processing.
*/ 
 
PutIfBegin (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  EntityTag [32];
   PUT_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PUT))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutIfBegin()");

   tkptr = rqptr->PutTaskPtr;

   if (!tkptr->SearchOds.Fab.fab$l_sts)
   {
      /* use SYSPRV to ensure access */
      sys$setprv (1, &SysPrvMask, 0, 0);
      OdsParse (&tkptr->SearchOds,
                rqptr->ParseOds.ExpFileName,
                rqptr->ParseOds.ExpFileNameLength,
                NULL, 0, 0, &PutIfBegin, rqptr);
      sys$setprv (0, &SysPrvMask, 0, 0);
      return;
   }

   if (VMSnok(status = tkptr->SearchOds.Fab.fab$l_sts))
   {
      OdsParseRelease (&tkptr->SearchOds);
      if (rqptr->WebDavTaskPtr)
         DavWebResponse (rqptr, 0, status, "entity tag", FI_LI);
      else
         ErrorVmsStatus (rqptr, status, FI_LI);
      PutEnd (rqptr);
      return;
   }

   if (!tkptr->SearchOds.FileQio.IOsb.Status)
   {
      /* use SYSPRV to ensure access */
      sys$setprv (1, &SysPrvMask, 0, 0);
      OdsFileAcpInfo (&tkptr->SearchOds, &PutIfBegin, rqptr);
      sys$setprv (0, &SysPrvMask, 0, 0);
      return;
   }

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

   if (VMSok(status = tkptr->SearchOds.FileQio.IOsb.Status))
   {
      /* file exists, generate entity tag from the data */
      OdsParseRelease (&tkptr->SearchOds);
      FileGenerateEntityTag (EntityTag, &tkptr->SearchOds.FileQio);
   }
   else
   {
      /* error accessing file */
      OdsParseRelease (&tkptr->SearchOds);
      if (rqptr->WebDavTaskPtr)
         DavWebResponse (rqptr, 0, status, "entity tag", FI_LI);
      else
         ErrorVmsStatus (rqptr, status, FI_LI);
      PutEnd (rqptr);
      return;
   }

   if (!ResponseEntityMatch (rqptr, EntityTag))
   {
      PutEnd (rqptr);
      return;
   }

   if (rqptr->WebDavTaskPtr)
      DavWebPutBegin (rqptr);
   else
      PutWasdBegin (rqptr);
}

/*****************************************************************************/
/*
Conclude processing the request.  If a temporary file name/path was generated
then it's a preview only, turn the POST into a GET of the temporary file path.
*/

PutEnd (REQUEST_STRUCT *rqptr)

{
   int  status;
   PUT_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PUT))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutEnd()");

   tkptr = rqptr->PutTaskPtr;

   /* indicate the PUT task has concluded correctly */
   rqptr->PutTaskPtr = NULL;

   if (tkptr->FileOpen) PutWriteFileClose (rqptr);
   SysDclAst (tkptr->NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Check the status result of the preceding request body network read or content
processing using 'rqptr->rqBody.DataStatus'.  Write (i.e. block I/O) the data
represented by 'rqptr->rqBody.DataPtr' and 'rqptr->rqBody.DataCount'.  For all
writes except the possible final one this should be a number of complete
virtual blocks (512 bytes) beginning at 'rqptr->rqBody.DataVBN'.  The final one
may be any number of bytes between 1 and 511.
*/ 

PutWriteFile (REQUEST_STRUCT *rqptr)

{
   int  status;
   PUT_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PUT))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT,
                 "PutWriteFile() !&F !&X !UL !UL !&S", &PutWriteFile,
                 rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount,
                 rqptr->rqBody.DataVBN, rqptr->rqBody.DataStatus);

   tkptr = rqptr->PutTaskPtr;

   if (VMSok (rqptr->rqBody.DataStatus))
   {
      if (!tkptr->FileOpen)
      {
         status = PutWriteFileOpen (rqptr);
         if (VMSnok (status)) return;
      }
      tkptr->FileSizeBytes += rqptr->rqBody.DataCount;
      tkptr->FileOds.Rab.rab$l_rbf = rqptr->rqBody.DataPtr;
      tkptr->FileOds.Rab.rab$w_rsz = rqptr->rqBody.DataCount;
      tkptr->FileOds.Rab.rab$l_bkt = rqptr->rqBody.DataVBN;
      sys$write (&tkptr->FileOds.Rab, &PutWriteFileAst, &PutWriteFileAst);
      return;
   }

   if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE)
   {
      /* body is exhausted (aren't we all?) */
      if (!tkptr->FileOpen)
      {
         status = PutWriteFileOpen (rqptr);
         if (VMSnok (status)) return;
      }
      PutWriteFileClose (rqptr);
      PutEnd (rqptr);
      return;
   }

   /* error reading or processing request body */
   if (rqptr->WebDavTaskPtr)
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "PUT write !&S !AZ",
                    rqptr->rqBody.DataStatus, tkptr->FileOds.ExpFileName);

   rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ);
   ErrorVmsStatus (rqptr, rqptr->rqBody.DataStatus, FI_LI);
   PutWriteFileClose (rqptr);
   PutEnd (rqptr);
}

/*****************************************************************************/
/*
After each block I/O written this AST function is called to either write more
data, or on conlusion  to call the end file function.  If an error is reported
from the write the end file function is called, with the presence of the error
message causing the file to be deleted.
*/ 

PutWriteFileAst (struct RAB *RabPtr)

{
   int  status;
   REQUEST_STRUCT *rqptr;
   PUT_TASK  *tkptr;

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

   rqptr = RabPtr->rab$l_ctx;

   if (WATCHMOD (rqptr, WATCH_MOD_PUT))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT,
                 "PutWriteFileAst() !&F sts:!&X stv:!&X rsz:!UL",
                 &PutWriteFileAst,
                 RabPtr->rab$l_sts, RabPtr->rab$l_stv, RabPtr->rab$w_rsz);

   tkptr = rqptr->PutTaskPtr;

   if (VMSok (tkptr->FileOds.Rab.rab$l_sts))
   {
      /* read more form the request body */
      BodyRead (rqptr);
      return;
   }

   rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
   rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileName;
   ErrorVmsStatus (rqptr, tkptr->FileOds.Rab.rab$l_sts, FI_LI);
   PutWriteFileClose (rqptr);
   PutEnd (rqptr);
}

/*****************************************************************************/
/*
Create a file using '->FileName'.  Fill with '->ContentFileLength' bytes
from '->ContentFilePtr'.  Return to processing at '->NextFunction'
(providing there was no problem!)
*/

PutWriteFileOpen (REQUEST_STRUCT *rqptr)

{
   int  status,
        EraseCount,
        PutRFM,
        TotalCount;
   char  *cptr, *sptr, *zptr,
         *ContentTypePtr;
   char  FileName [256];
   CONTENT_TYPE  ContType;
   BODY_PROCESS  *prptr;
   PUT_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PUT))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutWriteFileOpen()");

   tkptr = rqptr->PutTaskPtr;
   prptr = rqptr->rqBody.ProcessPtr;

   if (rqptr->rqBody.ProcessedAs == BODY_PROCESSED_AS_MULTIPART_FORMDATA)
   {
      if (!prptr)
      {
         /* must have had the body processed before we can store it! */
         ErrorInternal (rqptr, 0, ErrorSanityCheck, FI_LI);
         PutEnd (rqptr);
         return (SS$_BUGCHECK);
      }
      if (!prptr->MultipartFileName[0])
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PUT_MULTIPART_FILENAME), FI_LI);
         PutEnd (rqptr);
         return (SS$_BADPARAM);
      }

      if (WATCHMOD (rqptr, WATCH_MOD_PUT))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "!&Z !&Z !&Z !&B",
                    rqptr->ParseOds.NamDevicePtr,
                    prptr->MultipartUploadFileName,
                    prptr->MultipartFileName, prptr->PreviewOnly);
   }
   else
   {
      if (!rqptr->ParseOds.NamNameLength &&
          !rqptr->ParseOds.NamTypeLength)
      {
         /* must have the parsed file name */
         ErrorInternal (rqptr, 0, ErrorSanityCheck, FI_LI);
         PutEnd (rqptr);
         return (SS$_BUGCHECK);
      }
      if (WATCHMOD (rqptr, WATCH_MOD_PUT))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "!&Z",
                    rqptr->ParseOds.NamDevicePtr);
   }

   if (rqptr->ParseOds.NamTypeLength)
      cptr = rqptr->ParseOds.NamTypePtr;
   else
   if (prptr && prptr->MultipartUploadFileName[0])
   {
      /* try to conjure up a fit-for-purpose file type */
      for (cptr = prptr->MultipartUploadFileName; *cptr; cptr++);
      while (*cptr != '.' && cptr > prptr->MultipartUploadFileName) cptr--;
      if (*cptr != '.')
      {
         for (cptr = prptr->MultipartFileName; *cptr; cptr++);
         while (*cptr != '.' && cptr > prptr->MultipartFileName) cptr--;
         if (*cptr != '.') cptr = "";
      }
   }
   else
      cptr = "";

   if (rqptr->rqHeader.ContentTypePtr)
   {
      tkptr->FileContentTypePtr = rqptr->rqHeader.ContentTypePtr;
      ConfigContentType (&ContType, cptr);
   }
   else
   {
      /*
         If no MIME content-type is supplied with the request, as
         commonly occurs with Microsoft's WevDAV redirector, then
         attempt to come up with a plausable content-type from the
         file type (extension).  This is useful information when a
         little later setting record characteristics on the file.
      */
      tkptr->FileContentTypePtr = ConfigContentType (&ContType, cptr);
   }

   zptr = (sptr = tkptr->FileName) + sizeof(tkptr->FileName);

   /* use directory from path and form field 'uploadfilename' */
   for (cptr = rqptr->ParseOds.NamDevicePtr;
        cptr < rqptr->ParseOds.NamNamePtr && sptr < zptr;
        *sptr++ = *cptr++);

   if (prptr && (prptr->MultipartFileName[0] ||
                 prptr->MultipartUploadFileName[0]))
   {
      if (!*(cptr = prptr->MultipartUploadFileName))
      {
         cptr = prptr->MultipartFileName;
         while (*cptr) cptr++;
         while (cptr > prptr->MultipartFileName &&
                *cptr != '/' && *cptr != '\\' && *cptr != ']') cptr--;
         if (*cptr == '/' || *cptr == '\\' || *cptr == ']') cptr++;
      }
      MapOdsUrlToVms (cptr, FileName, sizeof(FileName), 0,
                      rqptr->rqPathSet.MapEllipsis, rqptr->PathOds);
      for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = *cptr++);
      /* update the content-type with whatever is specified in the part */
      tkptr->FileContentTypePtr = prptr->MultipartContentTypePtr;
   }
   else
   if (prptr && (tkptr->PreviewOnly = prptr->PreviewOnly))
   {
      /* use request total count to add a *unique* value to the file name */
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      TotalCount = AccountingPtr->ProcessingTotalCount[HTTP12];
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
      /* file name to indicate it's to be deleted on request rundown */
      FaoToBuffer (FileName, sizeof(FileName), NULL,
                   "-!4ZL!2ZL!2ZL!2ZL!2ZL!2ZL!2ZL!4ZL-",
                   rqptr->rqTime.BeginNumTime[0], rqptr->rqTime.BeginNumTime[1],
                   rqptr->rqTime.BeginNumTime[2], rqptr->rqTime.BeginNumTime[3],
                   rqptr->rqTime.BeginNumTime[4], rqptr->rqTime.BeginNumTime[5],
                   rqptr->rqTime.BeginNumTime[6], TotalCount % 1000);
      for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = *cptr++);
      /* append the request file type to it */
      for (cptr = rqptr->ParseOds.NamTypePtr;
           cptr < rqptr->ParseOds.NamVersionPtr && sptr < zptr;
           *sptr++ = *cptr++);
   }
   else
   {
      /* use the file name and type derived from the request path */
      for (cptr = rqptr->ParseOds.NamNamePtr;
           cptr < rqptr->ParseOds.NamVersionPtr && sptr < zptr;
           *sptr++ = *cptr++);
   }

   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      PutEnd (rqptr);
      return (SS$_RESULTOVF);
   }
   *sptr = '\0';
   tkptr->FileNameLength = sptr - tkptr->FileName;

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "!AZ", tkptr->FileName);

   tkptr->FileOpen = false;

   tkptr->FileOds.Fab = cc$rms_fab;
   tkptr->FileOds.Fab.fab$b_fac = FAB$M_PUT | FAB$M_BIO | FAB$M_TRN;
   tkptr->FileOds.Fab.fab$l_fop = FAB$M_SQO | FAB$M_TEF;

   /* pre-allocate space if the final size is fixed */
   if (tkptr->ContentLengthFixed)
   {
      tkptr->FileOds.Fab.fab$l_alq = (rqptr->rqBody.ContentLength >> 9) + 1;

      if (rqptr->WebDavTaskPtr &&
          rqptr->WebDavTaskPtr->MicrosoftAgent)
      {
         /*
             Microsoft's file creation sequence appears to be to PUT 
             zero bytes, then to PROPPATCH some win32 attributes, and
             then finally PUT lotsa bytes; as three independent requests.
             Propagate the allocation quantity so that the originally
             created zero-length file gets extended to the full size 
             required upon the first write.
         */
         tkptr->FileOds.Fab.fab$w_deq = (short)tkptr->FileOds.Fab.fab$l_alq;
      }
   }

   /* if it's a WebDAV PUT then confine to a single version */
   if (rqptr->WebDavTaskPtr)
      tkptr->FileOds.Fab.fab$l_fop |= FAB$M_CIF;

   tkptr->FileOds.Fab.fab$l_nam = &tkptr->FileOds.Nam;
   tkptr->FileOds.Fab.fab$b_rat = 0;

   if (prptr && prptr->UrlEncodedFabRfm &&
                prptr->UrlEncodedFabRat)
   {
      tkptr->FileOds.Fab.fab$b_rfm = prptr->UrlEncodedFabRfm;
      tkptr->FileOds.Fab.fab$b_rat = prptr->UrlEncodedFabRat;
      if (WATCHING (rqptr, WATCH_RESPONSE))
         WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "RFM:0x!2XL RAT:0x!2XL",
                    tkptr->FileOds.Fab.fab$b_rfm, tkptr->FileOds.Fab.fab$b_rat);
   }
   else
   /* the [AddType] RFM will always override identification as text */
   if (!ContType.PutRFM &&
       ConfigSameContentType (tkptr->FileContentTypePtr, "text/", 5))
   {
      /* "textual" content-type, guess about the _best_ RMS record format! */
      int  CrLfCount, LfCount;
      CrLfCount = LfCount = 0;
      if (rqptr->rqBody.DataCount > 1024)
         zptr = rqptr->rqBody.DataPtr + 1024;
      else
         zptr = rqptr->rqBody.DataPtr + rqptr->rqBody.DataCount;
      for (cptr = rqptr->rqBody.DataPtr; cptr < zptr; cptr++)
      {
         if (SAME2(cptr,'\r\n'))
         {
            CrLfCount++;
            cptr++;
         }
         else
         if (*cptr == '\n')
            LfCount++;
      }
      if (CrLfCount >= LfCount)
      {
         /* more CR+LFs than just LFs (DOS-style), STREAM */
         tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STM;
         tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR;
         if (WATCHING (rqptr, WATCH_RESPONSE))
            WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "RFM: stream (DOS)");
      }
      else
      {
         /* STREAM-LF (Unix-style) */
         tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STMLF;
         tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR;
         if (WATCHING (rqptr, WATCH_RESPONSE))
            WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "RFM: stream-LF");
      }
      tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR;
   }
   else
   /* the [AddType] RFM will always override identification as text */
   if (!ContType.PutRFM &&
       ConfigSameContentType (tkptr->FileContentTypePtr,
                              "application/x-www-form-urlencoded", -1))
   {
      /* STREAM-LF (Unix-style) */
      tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STMLF;
      tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR;
      if (WATCHING (rqptr, WATCH_RESPONSE))
         WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "RFM: stream-LF");
   }
   else
   {
      /* binary - precedence: SET path then [AddType] then global config */       
      if (!(PutRFM = rqptr->rqPathSet.PutRFM))
         if (!(PutRFM = ContType.PutRFM))
            PutRFM = Config.cfMisc.PutBinaryRFM;

      if (PutRFM == PUT_RFM_FIX512)
      {
         tkptr->FileOds.Fab.fab$b_rfm = FAB$C_FIX;
         tkptr->FileOds.Fab.fab$w_mrs = 512;
         tkptr->FileOds.Fab.fab$b_rat = 0;
         cptr = "fixed-512";
      }
      else
      if (PutRFM == PUT_RFM_STM)
      {
         tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STM;
         tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR;
         cptr = "stream";
      }
      else
      if (PutRFM == PUT_RFM_STMCR)
      {
         tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STMCR;
         tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR;
         cptr = "stream-CR";
      }
      else
      if (PutRFM == PUT_RFM_STMLF)
      {
         tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STMLF;
         tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR;
         cptr = "stream-LF";
      }
      else
      {
         tkptr->FileOds.Fab.fab$b_rfm = FAB$C_UDF;
         tkptr->FileOds.Fab.fab$b_rat = 0;
         cptr = "undefined";
      }
      if (WATCHING (rqptr, WATCH_RESPONSE))
         WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "RFM: !AZ (non-text)", cptr);
   }

   tkptr->FileOds.Fab.fab$b_shr = FAB$M_NIL;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      tkptr->FileOds.Fab.fab$l_fna = -1;
      tkptr->FileOds.Fab.fab$b_fns = 0;
      tkptr->FileOds.Fab.fab$l_nam = &tkptr->FileOds.Naml;

      tkptr->FileOds.NamlInUse = true;
      ENAMEL_RMS_NAML(tkptr->FileOds.Naml)
      tkptr->FileOds.Naml.naml$l_long_filename = tkptr->FileName;
      tkptr->FileOds.Naml.naml$l_long_filename_size = tkptr->FileNameLength;
      tkptr->FileOds.Naml.naml$l_filesys_name = tkptr->FileOds.SysFileName;
      tkptr->FileOds.Naml.naml$l_filesys_name_alloc =
         sizeof(tkptr->FileOds.SysFileName)-1;
      tkptr->FileOds.Naml.naml$l_long_expand = tkptr->FileOds.ExpFileName;
      tkptr->FileOds.Naml.naml$l_long_expand_alloc =
         sizeof(tkptr->FileOds.ExpFileName)-1;
      tkptr->FileOds.Naml.naml$l_long_result = tkptr->FileOds.ResFileName;
      tkptr->FileOds.Naml.naml$l_long_result_alloc =
         sizeof(tkptr->FileOds.ResFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      tkptr->FileOds.Fab.fab$l_fna = tkptr->FileName;
      tkptr->FileOds.Fab.fab$b_fns = tkptr->FileNameLength;
      tkptr->FileOds.Fab.fab$l_nam = &tkptr->FileOds.Nam;

      tkptr->FileOds.NamlInUse = false;
      tkptr->FileOds.Nam = cc$rms_nam;
      tkptr->FileOds.Nam.nam$l_esa = tkptr->FileOds.ExpFileName;
      tkptr->FileOds.Nam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
      tkptr->FileOds.Nam.nam$l_rsa = tkptr->FileOds.ResFileName;
      tkptr->FileOds.Nam.nam$b_rss = ODS2_MAX_FILE_NAME_LENGTH;
   }

   if (rqptr->WebDavTaskPtr)
   {
      /* propagate the profile security */
      AuthAccessEnable (rqptr, tkptr->FileName, AUTH_ACCESS_WRITE);
   }
   else
   {
      if (prptr && isxdigit(prptr->ProtectionHexString[0]))
         tkptr->ProtectionMask = strtol (prptr->ProtectionHexString, NULL, 16);

      if (WATCHMOD (rqptr, WATCH_MOD_PUT))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "0x!4XL",
                    tkptr->ProtectionMask);

      /* initialize the protection extended attribute block */
      tkptr->FileOds.Fab.fab$l_xab = &tkptr->FileOds.XabPro;
      tkptr->FileOds.XabPro = cc$rms_xabpro;
      tkptr->FileOds.XabPro.xab$w_pro = tkptr->ProtectionMask;

      /* use SYSPRV to ensure appropriate access/ownership */
      sys$setprv (1, &SysPrvMask, 0, 0);
   }

   tkptr->FileOds.Fab.fab$l_ctx = &tkptr->FileOds;
   if (VMSok (status = sys$parse (&tkptr->FileOds.Fab, 0, 0)))
      OdsNamBlockAst (&tkptr->FileOds.Fab);

   /* override any (block I/O) incompatible existing file characteristics */
   if (VMSok (status = OdsFileAcpInfo (&tkptr->FileOds, NULL, rqptr)))
   {
      if ((tkptr->FileOds.FileQio.RecAttr.fat$b_rtype !=
           tkptr->FileOds.Fab.fab$b_rfm) ||
          (tkptr->FileOds.FileQio.RecAttr.fat$b_rattrib !=
           tkptr->FileOds.Fab.fab$b_rat))
      {
         if (WATCHING (rqptr, WATCH_RESPONSE))
            WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                       "INCOMPATIBLE existing!AZ!AZ modified",
                       (tkptr->FileOds.FileQio.RecAttr.fat$b_rtype !=
                        tkptr->FileOds.Fab.fab$b_rfm) ? " RFM" : "",
                       (tkptr->FileOds.FileQio.RecAttr.fat$b_rattrib,
                        tkptr->FileOds.Fab.fab$b_rat) ? " RAT" : "");

         for (EraseCount = 0;
              VMSok (status = sys$erase (&tkptr->FileOds.Fab, 0, 0));
              EraseCount++);
         if (status == RMS$_FNF && EraseCount) status = SS$_NORMAL;

         if (WATCHMOD (rqptr, WATCH_MOD_PUT))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT,
                       "sys$erase() !&S", status);
      }
   }

   if (VMSok (status) || status == SS$_NOSUCHFILE)
      status = sys$create (&tkptr->FileOds.Fab, 0, 0);

   if (rqptr->WebDavTaskPtr)
      AuthAccessEnable (rqptr, 0, 0);
   else
      sys$setprv (0, &SysPrvMask, 0, 0);

   if (VMSnok (status) && tkptr->FileOds.Fab.fab$l_stv)
      status = tkptr->FileOds.Fab.fab$l_stv;

   if (VMSnok (status))
   {
      /* sys$create() error */
      if (rqptr->WebDavTaskPtr)
         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "PUT create !&S !AZ",
                       status, tkptr->FileOds.ExpFileName);

      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileName;
      ErrorVmsStatus (rqptr, status, FI_LI);

      PutWriteFileClose (rqptr);

      PutEnd (rqptr);

      return (status);
   }

   tkptr->FileOpen = true;
   tkptr->SysCreateStatus = status;

   /* set up the generic information from the NAM(L) block */
   tkptr->FileOds.Fab.fab$l_ctx = &tkptr->FileOds;
   OdsNamBlockAst (&tkptr->FileOds.Fab);

   tkptr->FileOds.Rab = cc$rms_rab;
   tkptr->FileOds.Rab.rab$l_fab = &tkptr->FileOds.Fab;
   tkptr->FileOds.Rab.rab$l_ctx = rqptr;
   tkptr->FileOds.Rab.rab$l_rop = RAB$M_BIO | RAB$M_ASY | RAB$M_TPT;

   if (VMSnok (status = sys$connect (&tkptr->FileOds.Rab, 0, 0)))
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      PutWriteFileClose (rqptr);
      PutEnd (rqptr);
   }

   return (status);
}

/*****************************************************************************/
/*
Called when the file has been completely written or an error has been detected.
The presence of an error message results in the file being deleted. If OK the
file attributes are changed to limit versions and to stream-LF is "textual".

There is a small window between closing the file and changing the attributes
where it could conceivably be opened for write by another process and interfere
with that change.  Not a problem within this one server because all this
processing is occuring at user-AST-delivery level.  Don't know what to do about
it for the moment so I'll just say it's a low-risk scenario and live with it
for now!
*/ 

PutWriteFileClose (REQUEST_STRUCT *rqptr)

{
   static int  SingleVersion = 1;

   int  status;
   char  *cptr, *sptr, *tptr;
   char  ProtectionString [32];
   PUT_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PUT))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutWriteFileClose()");

   tkptr = rqptr->PutTaskPtr;

   if (!tkptr->FileOpen) return;
   tkptr->FileOpen = false;

   if (tkptr->FileOds.Fab.fab$w_ifi) sys$close (&tkptr->FileOds.Fab, 0, 0);

   if (ERROR_REPORTED (rqptr))
   {
      /*********************************************/
      /* an error has occured, delete created file */
      /*********************************************/

      /* use SYSPRV to ensure erasure of file */
      sys$setprv (1, &SysPrvMask, 0, 0);

      tkptr->FileOds.Fab.fab$l_fop = FAB$M_NAM;
      status = sys$erase (&tkptr->FileOds.Fab, 0, 0);
      if (WATCHMOD (rqptr, WATCH_MOD_PUT))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "sys$erase() !&S", status);

      sys$setprv (0, &SysPrvMask, 0, 0);

      return;
   }
   else
   {
      if (rqptr->WebDavTaskPtr)
      {
         /**************/
         /* WebDAV PUT */
         /**************/

         if (tkptr->SysCreateStatus == RMS$_CREATED)
         {
            DavWebHref (rqptr, rqptr->ParseOds.ExpFileName, 0);
            DavWebResponse201 (rqptr);
         }
         else
            DavWebResponse (rqptr, 204, 0, "existing, overwrite", FI_LI);

         return;
      }

      /**************/
      /* non-WebDAV */
      /**************/

      if (Config.cfMisc.PutVersionLimit)
      {
         /* use SYSPRV to ensure modification of file */
         sys$setprv (1, &SysPrvMask, 0, 0);

         status = OdsFileAcpModify (&tkptr->FileOds, NULL,
                                    &Config.cfMisc.PutVersionLimit,
                                    NULL, rqptr);

         sys$setprv (0, &SysPrvMask, 0, 0);

         if (VMSnok (status))
         {
            /* as an error has occured terminate the PUT processing */
            rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
            rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileName;
            ErrorVmsStatus (rqptr, status, FI_LI);
            return;
         }
      }

      if (tkptr->PreviewOnly)
      {
         /****************/
         /* preview only */
         /****************/

         /* redirect to temporary file */
         int  size;
         cptr = MapVmsPath (tkptr->FileName, rqptr);
         size = strlen(cptr) + 256;
         sptr = ResponseLocation (rqptr, NULL, size);
         /* the leading space indicates that it's a change of HTTP method */
         FaoToBuffer (sptr, size, NULL, " GET !&%AZ", cptr);
         return;
      }

      if (rqptr->rqResponse.LocationPtr)
      {
         /********************/
         /* success redirect */
         /********************/

         return;
      }

      if (ConfigSameContentType (tkptr->FileContentTypePtr, "text/", 5) ||
          ConfigSameContentType (tkptr->FileContentTypePtr,
                                 "application/x-www-form-urlencoded", -1))
         tptr = MsgFor(rqptr,MSG_GENERAL_DOCUMENT);
      else
         tptr = MsgFor(rqptr,MSG_GENERAL_FILE);

      FormatProtection (tkptr->ProtectionMask, ProtectionString);

      cptr = MapVmsPath (tkptr->FileName, rqptr);

      /* ReportSuccess() will convert this 201 back to a 200 */
      rqptr->rqResponse.HttpStatus = 201;
      rqptr->rqResponse.PreExpired = PRE_EXPIRE_PUT;
      ReportSuccess (rqptr,
"!AZ &nbsp;<a href=\"!&%AZ\">!&;AZ</a>&nbsp; !AZ (!UL bytes)&nbsp; (!AZ)",
                     tptr, cptr, cptr,
                     tkptr->FileOds.Nam_fnb & NAM$M_LOWVER ?
                        MsgFor(rqptr,MSG_PUT_SUPERCEDED) :
                        MsgFor(rqptr,MSG_PUT_CREATED),
                     tkptr->FileSizeBytes, ProtectionString);

      return;
   }
}

/*****************************************************************************/
/*
Create a directory!
*/ 
 
int PutCreateDirectory (REQUEST_STRUCT *rqptr)

{
   static unsigned short  ProtectionEnable = 0xffff, /* alter all S,O,G,W */ 
                          EnsureSystemAccessMask = 0xfff0; /* S:RWED */
   static $DESCRIPTOR (DirectoryDsc, "");

   int  status;
   unsigned short  Length;
   char  *dptr;
   PUT_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PUT))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutCreateDirectory()");

   tkptr = rqptr->PutTaskPtr;

   dptr = rqptr->ParseOds.NamDevicePtr;

   DirectoryDsc.dsc$a_pointer = dptr;
   DirectoryDsc.dsc$w_length = strlen(dptr);
   tkptr->ProtectionMask &= EnsureSystemAccessMask;

   AuthAccessEnable (rqptr, DirectoryDsc.dsc$a_pointer, AUTH_ACCESS_WRITE);

   status = lib$create_dir (&DirectoryDsc, 0, &ProtectionEnable,
                            &tkptr->ProtectionMask, 0, 0);
   if (WATCHMOD (rqptr, WATCH_MOD_PUT))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "lib$create_dir() !&S", status);

   AuthAccessEnable (rqptr, 0, 0);

   if (status == SS$_CREATED)
   {
      /* ReportSuccess() will convert this 201 back to a 200 */
      rqptr->rqResponse.HttpStatus = 201;
      rqptr->rqResponse.PreExpired = PRE_EXPIRE_PUT;
      ReportSuccess (rqptr,
"!AZ&nbsp; <a href=\"!&%AZ\">!&;&_AZ</a>&nbsp; !AZ",
                     MsgFor(rqptr,MSG_GENERAL_DIRECTORY),
                     rqptr->rqHeader.PathInfoPtr,
                     rqptr->rqHeader.PathInfoPtr,
                     MsgFor(rqptr,MSG_PUT_CREATED));
   }
   else
   if (status == SS$_NORMAL)
   {
      rqptr->rqResponse.HttpStatus = 409;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PUT_DIR_EXISTS), FI_LI);
   }
   else
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = dptr;
      ErrorVmsStatus (rqptr, status, FI_LI);
   }
   return (status);
}

/*****************************************************************************/
/*
Deletes both files and directories.
*/ 
 
PutDelete (REQUEST_STRUCT *rqptr)

{
   BOOL  DeletingDirectory;
   int  status,
        EraseCount,
        FileCount,
        DirFileNameLength;
   char  *cptr, *sptr, *zptr;
   char  DirFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         SearchFileName [ODS_MAX_FILE_NAME_LENGTH+1];
   PUT_TASK  *tkptr;
   ODS_STRUCT  DeleteOds,
               SearchOds;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PUT))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutDelete()");

   tkptr = rqptr->PutTaskPtr;

   /* bit of a shonky, remove trailing ';*' from path */
   cptr = rqptr->rqHeader.PathInfoPtr + rqptr->rqHeader.PathInfoLength;
   if (*(cptr-1) == '*') cptr--;
   if (*(cptr-1) == ';') cptr--;
   *cptr = '\0';
   rqptr->rqHeader.PathInfoLength = cptr - rqptr->rqHeader.PathInfoPtr;

   DeletingDirectory = !rqptr->ParseOds.NamNameLength &&
                       !rqptr->ParseOds.NamTypeLength;

   if (DeletingDirectory)
   {
      OdsNameOfDirectoryFile (rqptr->ParseOds.ExpFileName,
                              rqptr->ParseOds.ExpFileNameLength,
                              DirFileName, &DirFileNameLength);

      AuthAccessEnable (rqptr, DirFileName, AUTH_ACCESS_READ);

      OdsStructInit (&SearchOds, true);
      OdsParse (&SearchOds, DirFileName,
                            DirFileNameLength,
                            NULL, 0, 0, NULL, rqptr);
      AuthAccessEnable (rqptr, 0, 0);
   }
   else
   {
      AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_READ);

      OdsParse (&SearchOds, rqptr->ParseOds.ExpFileName,
                            rqptr->ParseOds.ExpFileNameLength,
                            ";*", 2, 0, NULL, rqptr);

      AuthAccessEnable (rqptr, 0, 0);
   }

   if (VMSnok (status = SearchOds.Fab.fab$l_sts))
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

   /*******************/
   /* delete the file */
   /*******************/

   FileCount = 0;
   for (;;)
   {
      /* SYSPRV to ensure search (provided protection is S:RWED) */
      sys$setprv (1, &SysPrvMask, 0, 0);
      status = OdsSearch (&SearchOds, NULL, rqptr);
      sys$setprv (0, &SysPrvMask, 0, 0);

      if (VMSnok (status)) break;

      FileCount++;

      OdsStructInit (&DeleteOds, true);
      OdsParse (&DeleteOds,
                SearchOds.ResFileName, SearchOds.ResFileNameLength,
                NULL, 0,
                NAM$M_SYNCHK, NULL, rqptr);

      if (VMSnok (status = SearchOds.Fab.fab$l_sts))
      {
         rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
         rqptr->rqResponse.ErrorOtherTextPtr = SearchOds.ExpFileName;
         ErrorVmsStatus (rqptr, status, FI_LI);
         return (status);
      }

      DeleteOds.NamVersionPtr[0] = '\0'; 
      if (WATCHMOD (rqptr, WATCH_MOD_PUT))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "!&Z", DeleteOds.ExpFileName);

      if (rqptr->WebDavTaskPtr)
         AuthAccessEnable (rqptr, DeleteOds.ExpFileName, AUTH_ACCESS_WRITE);
      else
         /* use SYSPRV to ensure appropriate access/ownership */
         sys$setprv (1, &SysPrvMask, 0, 0);

      EraseCount = 0;
      while (VMSok (status = sys$erase (&DeleteOds.Fab, 0, 0)))
      {
         EraseCount++;
         if (WATCHMOD (rqptr, WATCH_MOD_PUT))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT,
                       "!UL sys$erase() !&S", EraseCount, status);
      }
      if (status == RMS$_FNF && EraseCount) status = SS$_NORMAL;

      if (rqptr->WebDavTaskPtr)
         AuthAccessEnable (rqptr, 0, 0);
      else
         sys$setprv (0, &SysPrvMask, 0, 0);

      if (VMSnok (status) && DeleteOds.Fab.fab$l_stv)
         status = DeleteOds.Fab.fab$l_stv;

      OdsParseRelease (&DeleteOds);

      if (VMSnok (status))
      {
         rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
         rqptr->rqResponse.ErrorOtherTextPtr = SearchOds.ExpFileName;
         ErrorVmsStatus (rqptr, status, FI_LI);
         return (status);
      }
   }

   if (status == RMS$_NMF) status = SS$_NORMAL;

   OdsParseRelease (&SearchOds);

   if (VMSok (status))
   {
      rqptr->rqResponse.PreExpired = PRE_EXPIRE_PUT;
      if (DeletingDirectory)
         sptr = MsgFor(rqptr,MSG_GENERAL_DIRECTORY);
      else
         sptr = MsgFor(rqptr,MSG_GENERAL_FILE);
      ReportSuccess (rqptr, "!AZ&nbsp; !&;&_AZ&nbsp; !AZ",
                     sptr, rqptr->rqHeader.PathInfoPtr,
                     MsgFor(rqptr,MSG_PUT_DELETED)); 
      return (status);
   }
   else
   {
      if (DeletingDirectory && status == RMS$_FNF) status = RMS$_DNF;
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = SearchOds.ExpFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }
}

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

