/*****************************************************************************/
/*
                                  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.


VERSION HISTORY
---------------
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_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#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;

/*****************************************************************************/
/*
*/ 
 
PutBegin
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
char *SpecifiedDirectory
)
{
   int  status,
        ReadSize;
   char  *cptr, *sptr, *zptr;
   PUT_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PUT))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PUT, "PutBegin() !&A !UL !&Z !&Z",
                 NextTaskFunction, rqptr->rqHeader.ContentLength,
                 rqptr->ParseOds.NamDevicePtr, SpecifiedDirectory);

   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;

   if (SpecifiedDirectory)
   {
      /* the server is providing a specific file name */
      zptr = (sptr = tkptr->SpecifiedDirectory) +
             sizeof(tkptr->SpecifiedDirectory)-1;
      for (cptr = SpecifiedDirectory; *cptr && sptr < zptr; *sptr++ = *cptr++);
      *sptr = '\0';
      tkptr->SpecifiedDirectoryLength = sptr - tkptr->SpecifiedDirectory;
   }

   tkptr->ProtectionMask = PUT_DEFAULT_FILE_PROTECTION;

   /**********************************************/
   /* delete (or file/directory deletion kludge) */
   /**********************************************/

   if ((rqptr->rqHeader.Method == HTTP_METHOD_DELETE) ||
       (rqptr->ParseOds.NamVersionPtr[0] == ';' &&
        rqptr->ParseOds.NamVersionPtr[1] == '*'))
   {
      /* 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_CATEGORY(WATCH_RESPONSE))
         WatchThis (rqptr, FI_LI, 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_CATEGORY(WATCH_RESPONSE))
            WatchThis (rqptr, FI_LI, 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_CATEGORY(WATCH_RESPONSE))
      WatchThis (rqptr, FI_LI, 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
      BodyReadBegin (rqptr, &PutWriteFile, &BodyProcessByVirtualBlock);
}

/*****************************************************************************/
/*
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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PUT))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PUT, "PutEnd()");

   tkptr = rqptr->PutTaskPtr;
   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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PUT))
      WatchThis (rqptr, FI_LI, 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 */
   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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PUT))
      WatchThis (rqptr, FI_LI, 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,
        RequestTotalCount;
   char  *cptr, *sptr, *zptr;
   char  FileName [256];
   BODY_PROCESS  *prptr;
   PUT_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PUT))
      WatchThis (rqptr, FI_LI, 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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PUT))
         WatchThis (rqptr, FI_LI, 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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PUT))
         WatchThis (rqptr, FI_LI, WATCH_MOD_PUT, "!&Z",
                    rqptr->ParseOds.NamDevicePtr);
   }

   tkptr->FileContentTypePtr = rqptr->rqHeader.ContentTypePtr;
   zptr = (sptr = tkptr->FileName) + sizeof(tkptr->FileName);

   /* put the device and directory into the file specification */
   if (tkptr->SpecifiedDirectory[0])
   {
      /* place into server-specified directory */
      for (cptr = tkptr->SpecifiedDirectory;
           cptr && sptr < zptr;
           *sptr++ = *cptr++);
   }
   else
   {
      /* 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++;
      }
      MapUrl_UrlToVms (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->MultipartContentType;
   }
   else
   if (prptr && (tkptr->PreviewOnly = prptr->PreviewOnly))
   {
      /* use request total count to add a *unique* value to the file name */
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      RequestTotalCount = AccountingPtr->RequestTotalCount;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
      /* file name to indicate it's to be deleted on request rundown */
      WriteFao (FileName, sizeof(FileName), NULL,
                "-!4ZL!2ZL!2ZL!2ZL!2ZL!2ZL!2ZL!4ZL-",
                rqptr->rqTime.VmsVector[0], rqptr->rqTime.VmsVector[1],
                rqptr->rqTime.VmsVector[2], rqptr->rqTime.VmsVector[3],
                rqptr->rqTime.VmsVector[4], rqptr->rqTime.VmsVector[5],
                rqptr->rqTime.VmsVector[6], RequestTotalCount % 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_CATEGORY(WATCH_RESPONSE))
      WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "!AZ", tkptr->FileName);

   /* check server's access to this file */
   if (VMSnok (status = AuthVmsCheckWriteAccess (rqptr, tkptr->FileName, 0)))
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      PutEnd (rqptr);
      return (status);
   }

   tkptr->FileOpen = false;

   tkptr->FileOds.Fab = cc$rms_fab;
   tkptr->FileOds.Fab.fab$b_fac = FAB$M_PUT | FAB$M_BIO;
   tkptr->FileOds.Fab.fab$l_fop = FAB$M_SQO;
   tkptr->FileOds.Fab.fab$l_nam = &tkptr->FileOds.Nam;
   tkptr->FileOds.Fab.fab$b_rat = 0;

   if (prptr->UrlEncodedFabRfm &&
       prptr->UrlEncodedFabRat)
   {
      tkptr->FileOds.Fab.fab$b_rfm = prptr->UrlEncodedFabRfm;
      tkptr->FileOds.Fab.fab$b_rat = prptr->UrlEncodedFabRat;
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE))
         WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "RFM:0x!2XL RAT:0x!2XL",
                    tkptr->FileOds.Fab.fab$b_rfm, tkptr->FileOds.Fab.fab$b_rat);
   }
   else
   if (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 (*(unsigned short*)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;
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE))
            WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "RFM: stream (DOS)");
      }
      else
      {
         /* STREAM-LF (Unix-style) */
         tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STMLF;
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE))
            WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "RFM: stream-LF");
      }
      tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR;
   }
   else
   if (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_CATEGORY(WATCH_RESPONSE))
         WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "RFM: stream-LF");
   }
   else
   {
      /* undefined for binary ... everything else */
      tkptr->FileOds.Fab.fab$b_rfm = FAB$C_UDF;
      tkptr->FileOds.Fab.fab$b_rat = 0;
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE))
         WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "RFM: undefined (binary)");
   }

   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 (prptr &&
       isxdigit(prptr->ProtectionHexString[0]))
      tkptr->ProtectionMask = strtol (prptr->ProtectionHexString, NULL, 16);

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PUT))
      WatchThis (rqptr, FI_LI, 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 creation of file (provided protection is S:RWED!) */
   sys$setprv (1, &SysPrvMask, 0, 0);

   status = sys$create (&tkptr->FileOds.Fab, 0, 0);

   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 */
      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;

   /* 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;

   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)

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

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PUT))
      WatchThis (rqptr, FI_LI, 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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PUT))
         WatchThis (rqptr, FI_LI, WATCH_MOD_PUT, "sys$erase() !&S", status);

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

      return;
   }
   else
   {
      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 */
         cptr = MapVmsPath (tkptr->FileName, rqptr);
         LocationSize = strlen(cptr)+128;
         rqptr->rqResponse.LocationPtr = VmGetHeap (rqptr, LocationSize);
         /* the leading space indicates that it's a change of HTTP method */
         WriteFao (rqptr->rqResponse.LocationPtr,
                   LocationSize, NULL, " GET !&%AZ", cptr);
         if (VMSnok (status)) ErrorNoticed (status, "WriteFao()", FI_LI);
         return;
      }

      if (rqptr->rqResponse.LocationPtr &&
          !rqptr->rqResponse.LocationPtr[0] &&
          rqptr->rqResponse.LocationPtr[1])
      {
         /********************/
         /* success redirect */
         /********************/

         /* null then non-null - dead give-away, make it useful */
         cptr = (sptr = rqptr->rqResponse.LocationPtr) + 1;
         while (*cptr) *sptr++ = *cptr++;
         *sptr = '\0';
         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);

      /* ensure any trailing version number is eliminated */
      cptr = rqptr->rqHeader.PathInfoPtr;
      sptr = cptr + rqptr->rqHeader.PathInfoLength - 1;
      while (sptr > cptr && isdigit(*sptr)) sptr--;
      if (*sptr == ';') *sptr = '\0'; else sptr = NULL;

      /* 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);

      if (sptr) *sptr = ';';

      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,
        DirectoryFileLength;
   unsigned short  Length;
   char  DirectoryFile [ODS_MAX_FILE_NAME_LENGTH+1];
   char  *dptr;
   PUT_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PUT))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PUT, "PutCreateDirectory()");

   tkptr = rqptr->PutTaskPtr;

   if (tkptr->SpecifiedDirectory[0])
   {
      if (!rqptr->ParseOds.NamNameLength &&
          !rqptr->ParseOds.NamTypeLength)
      {
         /* cannot create a directory in a specified directory */
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PUT_DIR_NAME), FI_LI);
         return (STS$K_ERROR);
      }
   }

   dptr = rqptr->ParseOds.NamDevicePtr;

   /* ultra-cautious, check server's access to this file (using parsed NAM) */
   OdsNameOfDirectoryFile (dptr, 0, DirectoryFile, &DirectoryFileLength);
   if (VMSnok (status = AuthVmsCheckWriteAccess (rqptr, DirectoryFile, 0)))
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = dptr;
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

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

   /* SYSPRV to ensure creation of file (provided protection is S:RWED) */
   sys$setprv (1, &SysPrvMask, 0, 0);

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

   sys$setprv (0, &SysPrvMask, 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,
        DirFileLength;
   char  *cptr, *sptr, *zptr;
   char  DirFile [ODS_MAX_FILE_NAME_LENGTH+1],
         DeleteFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         DirectoryFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         ScratchFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         SearchFileName [ODS_MAX_FILE_NAME_LENGTH+1];
   PUT_TASK  *tkptr;
   ODS_STRUCT  DeleteOds,
               FileOds,
               SearchOds;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PUT))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PUT, "PutDelete()");

   tkptr = rqptr->PutTaskPtr;

   zptr = (sptr = ScratchFileName) + sizeof(ScratchFileName)-1;
   /* put the device and directory into the file specification */
   if (tkptr->SpecifiedDirectory[0])
   {
      if (!rqptr->ParseOds.NamNameLength &&
          !rqptr->ParseOds.NamTypeLength)
      {
         /* cannot delete a directory from a specified directory */
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PUT_DELETE_NO_FILENAME), FI_LI);
         return (STS$K_ERROR);
      }
      /* delete file from server-specified directory */
      for (cptr = tkptr->SpecifiedDirectory;
           cptr && sptr < zptr;
           *sptr++ = *cptr++);
   }
   else
   {
      /* use the device and directory derived from the request path */
      for (cptr = rqptr->ParseOds.NamDevicePtr;
           cptr < rqptr->ParseOds.NamNamePtr && sptr < zptr;
           *sptr++ = *cptr++);
   }
   /* use the file name and type derived from the request path */
   for (cptr = rqptr->ParseOds.NamNamePtr;
        cptr < rqptr->ParseOds.NamVersionPtr && sptr < zptr;
        *sptr++ = *cptr++);
   *sptr = '\0';

   OdsParse (&SearchOds, ScratchFileName, 0, ";*", 2, 0, NULL, rqptr);

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

   if (DeletingDirectory =
       (!SearchOds.NamNameLength && SearchOds.NamTypeLength == 1))
   {
      /* a directory is being specified, terminate at beginning of name */
      SearchOds.NamNamePtr[0] = '\0'; 

      OdsNameOfDirectoryFile (SearchOds.ExpFileName, 0,
                              DirFile, &DirFileLength);

      OdsParse (&SearchOds, DirFile, DirFileLength, NULL, 0, 0, NULL, rqptr);
      if (VMSnok (status = SearchOds.Fab.fab$l_sts))
      {
         rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
         rqptr->rqResponse.ErrorOtherTextPtr = DirFile;
         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++;

      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 = ScratchFileName;
         ErrorVmsStatus (rqptr, status, FI_LI);
         return (status);
      }

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

      /* ultra-cautious, check server's access to parent directory */
      if (VMSnok (status =
          AuthVmsCheckWriteAccess (rqptr, DeleteOds.ExpFileName, 0)))
      {
         rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
         rqptr->rqResponse.ErrorOtherTextPtr = ScratchFileName;
         ErrorVmsStatus (rqptr, status, FI_LI);
         return (status);
      }

      /* SYSPRV to ensure deletion of file (provided protection is S:RWED) */
      sys$setprv (1, &SysPrvMask, 0, 0);

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

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

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

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

   if (status == RMS$_NMF)
   {
      SearchOds.ParseInUse = false;
      status = SS$_NORMAL;
   }

   if (SearchOds.ParseInUse) 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 = ScratchFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }
}

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

