/*****************************************************************************/
/*
                                   body.c

Request body transfer from client, any content processing, etc.

The functions BodyProcessUrlEncoded() and BodyProcessMultipartFormData()
process request body data once it has been read from the client.  When data
should be passed on to the calling/using function is calls the AST with the
following storage set.  Note that the 'rqBody.Data..' buffer can contain either
raw or processed data, but is always what the current function requires to
continue processing the request body.

  'rqptr->rqBody.DataStatus'
    containing the VMS status relevant to the processing

  'rqptr->rqBody.DataPtr'
    pointing to the start of virtual block(s) of data

  'rqptr->rqBody.DataCount'
    containing the number of bytes in the buffer (whole blocks or left-over)

  'rqptr->rqBody.DataVBN'
    the virtual block number (1 to whatever, if applicable)

This sort of processing using ASTs is particularly painful.  Lots of buffering
is required to allow for octets that must be processed together (e.g. %xx
URL-encoded) to be delivered split between two reads.

To allow file-system functions to perform block I/O the AST is only ever
delivered with a buffer containing a whole number of 512 bytes blocks unless it
is the final call when it may contain from 1 to 511 bytes.


Transfer-Encoding: chunked
--------------------------
Chunked transfer encoding is a HTTP/1.1 fact of life.  As the binary content of
these bodies is always smaller after decoding they are unchunked in the same
buffer space that is used to read the chunked stream from the network.  Testing
chunked upload can be performed with the WASD Bench utility (v1.2 and later). 
With a suitable POST target in the file-system merely upload files of various
sizes and contents into that file-system area and compare the upload with the
original using the DIFFERENCE utility.

  $ WB /POST=<filename> /TRANSFER=CHUNKED "http://host/test/<filename>"
  $ DIFFERENCE <filename> TEST:[000000]<filename>
  $ WB /POST=<filename> /TRANSFER=CHUNKED=300 "http://host/test/<filename>"
  $ WB /POST=<filename> /TRANSFER=CHUNKED=0 "http://host/test/<filename>"


Content-Encoding: gzip
----------------------
GZIP compression for request bodies is available in conjunction with the GZIP.C
module and ZLIB library (sharable image).  As the decompressed content is
almost invariably larger than the original raw data some form of double
buffering is required.  This keeps the network data intact while having the
ZLIB inflation function produce usually multiple, successive output buffers
that are given to the body processing function(s) independently.  Using a
similar approach as with chunked upload testing GZIP compressed upload can be
performed with the WASD Bench utility (v1.2 and later).  It is first necessary
to create a GZIPed object using the GZIP utility.  This behaves as if it a
client-generated GZIP request body.

  $ COPY <filename1> <filename2>
  $ GZIP <filename2>
  $ WB /POST=<filename2>-gz /ENCODING="gzip" "http://host/test/<filename2>"
  $ DIFFERENCE <filename1> TEST:[000000]<filename2>


Content-Type: application/x-www-form-urlencoded
-----------------------------------------------
When the processing function BodyProcessUrlEncoded() is used ...

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.

A field name (beginning with) "hexencoded" is expected to contain hexadecimal
encoded bytes (i.e. two hexadecimal digits per byte), as well as some
url-encoded characters (e.g. newlines, 0x0a).  The contents of this field are
decoded before inclusion in the file.  This was done to allow non-text files to
be copied around.


Content-Type: multipart/form-data
---------------------------------
When the processing function BodyProcessMultipartFormData() is used ...

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".
The implementation herein is very basic, a facility only to allow uploads of
files into the server administered file system.  The action= tag of the form
must specify the directory (URL format) in which the uploaded file will be
created.

Multipart/form-data Fields
--------------------------
All file names supplied within a multipart/form-data request are relative to
the path information supplied in the header request.  Hence it must supply
a directory specification.  It is an error condition if this directory is not
specified, and all file specifications are generated by appending file names
to this directory.

In addition to file uploads, specific field names within a multipart/form-data
request are detected and used as additional information or to initiate actions.
These field names are case insensistive, as is any content within them.

When uploading files, the field "UploadFileName" contents, when received
before the file contents themselves (i.e. "text" field occurs in the form
before the "file" field) provides an alternative name to the original file.

Any other value present in the action field is reported as an error.

All required file names must be specified "file.type" prior to the action
being initiated (i.e. name fields must be present in the form before the
action field).  The following (hopefully self-documenting) field names can be
used to pass file names for action:

  o  "FileName"
  o  "UploadFileName"
  o  "Protection"
  o  "PreviewOnly"
  o  "PreviewNote"
  o  "Success"
  o  "Hidden$LF"

Any other field names present in the request are reported as errors.


VERSION HISTORY
---------------
21-MAR-2017  MGD  bugfix; use rqHeader.RequestBody.. for body with header
05-SEP-2015  MGD  accomodate HTTP/2
17-JUN-2010  MGD  significant rework to function()alise common code
                  improve performance with multiblock of 127 (per JPP)
30-APR-2010  MGD  BODY.C make MultipartContentType(Ptr) a dynamic structure
                    as Microsoft endeavour to include application data
                    along with MIME content-type, see ...
                       http://msdn.microsoft.com/en-us/library/aa338205.aspx
                    and an example (no kidding!) ...
  "application/vnd.ms.powerpoint.template.macroEnabled.12application/x-font"
27-OCT-2009  MGD  bugfix; BodyReadUnEncode() unchunk all but scripts
07-JUN-2007  MGD  BodyProcessReadAll() set buffer size to content length
17-JAN-2007  MGD  bugfix; BodyReadBegin() 413 set status before declaring AST
14-NOV-2004  MGD  bugfix; BodyRead() maximum exceeded must set read
                  error status before calling AST (jpp@esme.fr)
17-OCT-2004  MGD  inflate gzip content-encoding,
                  by default script body transfers are now un-encoded
20-JUL-2004  MGD  HTTP/1.1 compliance,
                  provide "100 Continue" response before reading body,
                  decode "Transfer-encoding: chunked" request bodies
15-AUG-2003  MGD  where CDATA constraints make using &#10; entity impossible
                  use a field name of hidden$lf and &#94; substituted for it
21-JUN-2003  MGD  bugfix; data to be read needs to be the smaller of
                  remaining body or buffer size (jpp@esme.fr)
24-MAR-2003  MGD  bugfix; processing of DCL module script processing restart
05-DEC-2002  MGD  allow for white-space in multipart file names
25-AUG-2002  MGD  introduce fab$b_rfm and fab$b_rat as fields to allow
                  PUT.C to specifically set these attributes as required,
                  bugfix; restart MIME boundary matching algorithm using
                  the current character (to allow for <CR><LF><CR><LF>)
01-AUG-2002  MGD  bugfix; when discarding via BodyReadBegin() use BodyRead()
                  to queue a network read only if data is outstanding
30-JUN-2002  MGD  allow BodyReadBegin() to reset ASTs for RequestBodyDiscard()
07-JUN-2002  MGD  bugfix; sanity check where there shouldn't be one
02-FEB-2002  MGD  couldn't avoid doing something about it any longer
*/
/*****************************************************************************/

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

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

#include "wasd.h"

#define WASD_MODULE "BODY"

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

int  BodyMultiBlockCount = PUT_MULTIBLOCK_COUNT_DEFAULT;

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

extern BOOL  GzipAccept;

extern int  NetReadBufferSize;

extern int  ToLowerCase[],
            ToUpperCase[];

extern char  ErrorSanityCheck[];

extern CONFIG_STRUCT  Config;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Initialize and initiate the first request body read from the client.  If
request body data arrived with the request header a pseudo-read occurs, with
explicit AST delivery.  Otherwise just read from the network.
*/ 
 
void BodyReadBegin
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction,
REQUEST_AST ProcessFunction
)
{
   int  status,
        ContentLength,
        DataSize,
        MaxKbytes;
   char  *cptr;
   char  String [256];

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

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
"BodyReadBegin() chunked:!&B gzip:!&B length:!UL expected:!UL \
ast:!&A process:!&A",
                 rqptr->rqHeader.TransferEncodingChunked,
                 rqptr->rqHeader.ContentEncodingGzip,
                 rqptr->rqHeader.ContentLength,
                 rqptr->rqHeader.XExpectedEntityLength,
                 AstFunction, ProcessFunction);

   if (rqptr->rqBody.AstFunction)
   {
      if (AstFunction == (REQUEST_AST)&DclHttpInput &&
          rqptr->rqBody.AstFunction == (REQUEST_AST)&DclHttpInput)
      {
         /************************************************/
         /* special case - restarting DCL script process */
         /************************************************/

         /* provide the same data that was previously supplied */
         SysDclAst (&DclHttpInput, rqptr);
         return;
      }
      /* this shouldn't happen */
      ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_BUGCHECK;
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
      return;
   }

   if (!(ContentLength = rqptr->rqHeader.ContentLength))
      /* a WebDAVFS/3.0.0 and/or OS X (Apple) quirk */
      ContentLength = rqptr->rqHeader.XExpectedEntityLength;

   if (ContentLength)
   {
      /* before the "100 Continue" - request body allowed to be this big? */
      if (!(MaxKbytes = rqptr->rqPathSet.PutMaxKbytes))
         MaxKbytes = Config.cfMisc.PutMaxKbytes;
      if (!rqptr->ProxyTaskPtr &&
          ContentLength >> 10 > MaxKbytes)
       {
         cptr = MsgFor(rqptr,MSG_REQUEST_BODY_MAX);
         status = FaoToBuffer (String, sizeof(String), NULL, cptr,
                               rqptr->rqHeader.MethodName,
                               ContentLength >> 10, MaxKbytes);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         rqptr->PersistentRequest = rqptr->PersistentResponse = false;
         rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ABORT;
         rqptr->rqResponse.HttpStatus = 413;
         ErrorGeneral (rqptr, String, FI_LI);
         /* RequestBodyDiscard() uses this for progress determination */
         SysDclAst (rqptr->rqBody.AstFunction = AstFunction, rqptr);
         return;
      }
   }

   rqptr->rqBody.AstFunction = AstFunction;
   rqptr->rqBody.ProcessFunction = ProcessFunction;
   rqptr->rqBody.DataCount = rqptr->rqBody.ContentCount = 0;
   rqptr->rqBody.ContentLength = ContentLength;
   rqptr->rqBody.DataVBN = 1;

   if (!ContentLength &&
       !rqptr->rqHeader.TransferEncodingChunked)
   {
      /* no body to be supplied */
      rqptr->rqBody.DataPtr = "";
      rqptr->rqBody.DataSize = 0;
      rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_NONE;
      rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ENDOFFILE;
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
      return;
   }

   if (rqptr->Http2Stream.Http2Ptr)
   {
      /* HTTP/2 allocate now that it is needed */
      rqptr->rqNet.ReadBufferSize =
         rqptr->Http2Stream.Http2Ptr->ServerMaxFrameSize;
      rqptr->rqNet.ReadBufferPtr =
         VmGetHeap (rqptr, rqptr->rqNet.ReadBufferSize);
   }
   else
   {
      /*  HTTP/1.n allow for any content received along with the header */
      rqptr->NetIoPtr->ReadCount =
         rqptr->BytesRx[0] - rqptr->rqHeader.RequestHeaderLength;
   }

   rqptr->rqBody.DataPtr = rqptr->rqNet.ReadBufferPtr;
   rqptr->rqBody.DataSize = rqptr->rqNet.ReadBufferSize;

   if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_1)
   {
      rqptr->rqBody.UnEncodeStream = BodyReadUnEncode (rqptr);

      if (WATCHMOD (rqptr, WATCH_MOD_BODY))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
                    "Unencode stream: !&B", rqptr->rqBody.UnEncodeStream);

      /* is a "100 Continue" interim response required at this point? */
      if ((rqptr->rqHeader.Expect100Continue ||
           rqptr->rqHeader.Method == HTTP_METHOD_PUT ||
           rqptr->rqHeader.Method == HTTP_METHOD_POST) &&
          !rqptr->rqResponse.HeaderSent &&
          !rqptr->DclTaskPtr &&
          !rqptr->DECnetTaskPtr &&
          !rqptr->ProxyTaskPtr)
         ResponseHeader100Continue (rqptr);
   }

   /* once we start reading into the buffer the request header is kaput */
   rqptr->rqHeader.RequestHeaderPtrInvalid = true;

   /* may be reset by using ..UrlEncoded() or ..MultipartFormData() */
   if (ConfigSameContentType (rqptr->rqHeader.ContentTypePtr, "text/", 5))
      rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_TEXT;
   else
      rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_OTHER;

   /* ensure this does not exceed any specified content length */
   if (rqptr->rqBody.ContentLength &&
       rqptr->NetIoPtr->ReadCount > rqptr->rqBody.ContentLength)
      rqptr->NetIoPtr->ReadCount = rqptr->rqBody.ContentLength;

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
         "Length:!UL BytesRx:!@SQ HeaderLength:!UL (!UL) IOsb.Count:!UL",
         rqptr->rqBody.ContentLength, &rqptr->BytesRx,
         rqptr->rqHeader.RequestHeaderLength,
         rqptr->BytesRx[0] - rqptr->rqHeader.RequestHeaderLength,
         rqptr->NetIoPtr->ReadCount);

   if (rqptr->rqHeader.RequestBodyCount)
   {
      /* data arrived with the request header, provide that */
      rqptr->rqBody.DataPtr = rqptr->rqHeader.RequestBodyPtr;
      rqptr->NetIoPtr->ReadCount = rqptr->rqHeader.RequestBodyCount;
      rqptr->NetIoPtr->ReadStatus = SS$_NORMAL;
      SysDclAst (&BodyReadAst, rqptr);
      rqptr->rqHeader.RequestBodyPtr = NULL;
      rqptr->rqHeader.RequestBodyCount = 0;
      return;
   }

   if (rqptr->rqHeader.TransferEncodingChunked)
   {
      /* don't know at this stage, so just read as much as possible */
      DataSize = rqptr->rqNet.ReadBufferSize;
   }
   else
   {
      /* data to be read is the smaller of remaining body or buffer size */
      DataSize = rqptr->rqBody.ContentLength - rqptr->rqBody.ContentCount;
      if (DataSize > rqptr->rqBody.DataSize) DataSize = rqptr->rqBody.DataSize;
   }
   NetRead (rqptr, &BodyReadAst, rqptr->rqBody.DataPtr, DataSize);
}

/*****************************************************************************/
/*
Should the request body be unencoded or be delivered as raw stream?
Return true or false.
*/

BOOL BodyReadUnEncode (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyReadUnEncode()");

   if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_1)
   {
      if (rqptr->ProxyTaskPtr)
         return (false);
      else
      if (rqptr->rqHeader.TransferEncodingChunked)
      {
         if (rqptr->ScriptName[0] &&
             !rqptr->rqPathSet.ScriptBodyDecode) return (false);
         /* all other chunked */
         return (true);
      }
      else
      if (rqptr->rqHeader.ContentEncodingGzip)
      {
         if (GzipAccept && rqptr->rqPathSet.ScriptBodyDecode)
            return (true);
         else
            return (false);
      }
      else
         return (false);
   }
   return (false);
}

/*****************************************************************************/
/*
If there is still more to be read of the request body (as indicated by a
difference between what has been read and the content-length indicated in the
request header) the queue another read.  If the entire request body has been
read generate an end-of-file status.
*/

void BodyRead (REQUEST_STRUCT *rqptr)

{
   int  status,
        DataSize,
        MaxKbytes;
   char  *cptr;
   char  String [256];

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

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyRead() !&B !UL !UL/!UL",
                 rqptr->rqBody.UnEncodeStream, rqptr->rqBody.ChunkState,
                 rqptr->rqBody.ContentCount, rqptr->rqBody.ContentLength);

   if (rqptr->RequestState == REQUEST_STATE_ABORT)
   {
      rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ABORT;
      rqptr->rqBody.DataCount = rqptr->NetIoPtr->ReadCount = 0;
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
      return;
   }

   /* ongoing check - request body allowed to be this big? */
   if (!(MaxKbytes = rqptr->rqPathSet.PutMaxKbytes))
      MaxKbytes = Config.cfMisc.PutMaxKbytes;
   if (!rqptr->DclTaskPtr &&
       !rqptr->ProxyTaskPtr &&
       (rqptr->rqBody.ContentCount >> 10) > MaxKbytes)
    {
      cptr = MsgFor(rqptr,MSG_REQUEST_BODY_MAX);
      status = FaoToBuffer (String, sizeof(String), NULL, cptr,
                         rqptr->rqHeader.MethodName,
                         rqptr->rqBody.ContentCount >> 10, MaxKbytes);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      rqptr->rqResponse.HttpStatus = 413;
      ErrorGeneral (rqptr, String, FI_LI);
      rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ABORT;
      rqptr->rqBody.DataCount = rqptr->NetIoPtr->ReadCount = 0;
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
      return;
   }

   if (rqptr->rqBody.UnEncodeStream &&
       rqptr->rqHeader.ContentEncodingGzip &&
       (rqptr->NetIoPtr->ReadCount ||
        rqptr->GzipCompress.InflateAvailIn))
   {
      /* there is still data that can be fed into the inflate function */
      BodyReadProcess (rqptr);
      return;
   }

   if (rqptr->rqHeader.TransferEncodingChunked)
   {
      /********************/
      /* transfer chunked */
      /********************/

      if (rqptr->rqBody.UnEncodeStream)
      {
         /* being unencoded (therefore we can detect the end of it) */
         if (rqptr->rqBody.ChunkState != CHUNK_STATE_END)
         {
            /* not the end of the chunked body just yet! */
            rqptr->rqBody.DataPtr = rqptr->rqNet.ReadBufferPtr;
            rqptr->rqBody.DataSize = rqptr->rqNet.ReadBufferSize;
            NetRead (rqptr, &BodyReadAst, rqptr->rqBody.DataPtr, 
                                          rqptr->rqBody.DataSize);
            return;
         }
         /* fall through as end-of-body */
      }
      else
      {
         /* not being un-encoded (keep reading 'till the bitter end) */
         rqptr->rqBody.DataPtr = rqptr->rqNet.ReadBufferPtr;
         rqptr->rqBody.DataSize = rqptr->rqNet.ReadBufferSize;
         NetRead (rqptr, &BodyReadAst, rqptr->rqBody.DataPtr, 
                                       rqptr->rqBody.DataSize);
         return;
      }
   }

   if (!rqptr->rqHeader.TransferEncodingChunked &&
       rqptr->rqBody.ContentCount < rqptr->rqBody.ContentLength)
   {
      /*************/
      /* read more */
      /*************/

      rqptr->rqBody.DataPtr = rqptr->rqNet.ReadBufferPtr;
      rqptr->rqBody.DataSize = rqptr->rqNet.ReadBufferSize;
      /* data to be read is the smaller of remaining body or buffer size */
      DataSize = rqptr->rqBody.ContentLength - rqptr->rqBody.ContentCount;
      if (DataSize > rqptr->rqBody.DataSize) DataSize = rqptr->rqBody.DataSize;
      NetRead (rqptr, &BodyReadAst, rqptr->rqBody.DataPtr, DataSize);
      return;
   }

   /***************/
   /* end of body */
   /***************/

   rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ENDOFFILE;
   rqptr->rqBody.DataCount = rqptr->NetIoPtr->ReadCount = 0;

   if (rqptr->rqHeader.ContentLength)
   {
      if (rqptr->rqHeader.ContentLength != rqptr->rqBody.ContentCount)
      {
         if (WATCHING (rqptr, WATCH_REQUEST_BODY))
            WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY,
            "Content-Length: !UL !!= !UL",
            rqptr->rqHeader.ContentLength, rqptr->rqBody.ContentCount);
         rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ABORT;
      }
   }
   else
   if (rqptr->rqHeader.XExpectedEntityLength)
   {
      /* an OSX (Apple) WebDAV quirk */
      if (rqptr->rqHeader.XExpectedEntityLength != rqptr->rqBody.ContentCount)
      {
         if (WATCHING (rqptr, WATCH_REQUEST_BODY))
            WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY,
            "X-Expected-Entity-Length: !UL !!= !UL",
            rqptr->rqHeader.XExpectedEntityLength, rqptr->rqBody.ContentCount);
         rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ABORT;
      }
   }

   if (rqptr->rqBody.ProcessFunction)
      SysDclAst (rqptr->rqBody.ProcessFunction, rqptr);
   else
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
} 

/*****************************************************************************/
/*
Network read has completed.  This function will never be called for ENDOFFILE,
only success or legitimate error status.  This function accepts data specified
by the IO status block (NetIoPtr->ReadStatus and NetIoPtr->ReadCount).
*/ 

void BodyReadAst (REQUEST_STRUCT *rqptr)

{
   int  status;

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

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
                 "BodyReadAst() !&F !&S !UL !UL !UL", &BodyReadAst,
                 rqptr->NetIoPtr->ReadStatus,
                 rqptr->NetIoPtr->ReadCount, rqptr->rqBody.ContentLength,
                 rqptr->rqBody.ContentCount);

   if (VMSnok (rqptr->NetIoPtr->ReadStatus))
   {
      /* error */
      rqptr->rqBody.DataCount = 0;
      rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus;

      if (!rqptr->rqResponse.HttpStatus)
      {
         rqptr->rqResponse.HttpStatus = 400;
         rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ);
         ErrorVmsStatus (rqptr, rqptr->NetIoPtr->ReadStatus, FI_LI);
      }

      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
      return;
   }

   if (rqptr->rqBody.UnEncodeStream &&
       rqptr->rqHeader.TransferEncodingChunked)
   {
      /********************/
      /* transfer chunked */
      /********************/

      BodyTransferChunked (rqptr);
      if (VMSok (rqptr->NetIoPtr->ReadStatus))
      {
         if (!rqptr->NetIoPtr->ReadCount)
         {
            /* need to read more of the body from the client */
            BodyRead (rqptr);
            return;
         }
         /* otherwise drop thru to further process */
      }
      else
      {
         /* an error was reported */
         SysDclAst (rqptr->rqBody.AstFunction, rqptr);
         return;
      }
   }
   else
   {
      /* keep track of how much raw data has been read of the body */
      rqptr->rqBody.ContentCount += rqptr->NetIoPtr->ReadCount;
   }

   BodyReadProcess (rqptr);
}

/*****************************************************************************/
/*
This function called after a real network read has completed or after a GZIP
encoded body chunk has been directly identified in BodyRead() (bypassing
BodyReadAst()).  This function accepts data specified by the IO status block
(NetIoPtr->ReadStatus and NetIoPtr->ReadCount) and provides data identified
by the body data structure (rqBody.DataStatus and rqptr->rqBody.DataCount).
*/ 

void BodyReadProcess (REQUEST_STRUCT *rqptr)

{
   int  status;

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

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
                 "BodyReadProcess() !&F !&S !UL !UL !UL", &BodyReadProcess,
                 rqptr->NetIoPtr->ReadStatus, rqptr->NetIoPtr->ReadCount,
                 rqptr->rqBody.ContentLength, rqptr->rqBody.ContentCount);

   if (rqptr->rqBody.UnEncodeStream &&
       rqptr->rqHeader.ContentEncodingGzip)
   {
      /******************/
      /* content GZIPed */
      /******************/

      BodyContentGzip (rqptr);

      if (WATCHMOD (rqptr, WATCH_MOD_BODY))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyContentGzip() !&S !UL",
                    rqptr->rqBody.DataStatus, rqptr->rqBody.DataCount);

      if (VMSok (rqptr->rqBody.DataStatus))
      {
         if (!rqptr->rqBody.DataCount)
         {
            /* need to provide more of the body from the client */
            BodyRead (rqptr);
            return;
         }
         /* otherwise drop thru to further process */
      }
      else
      {
         /* an error was reported */
         SysDclAst (rqptr->rqBody.AstFunction, rqptr);
         return;
      }
   }
   else
   {
      rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus;
      rqptr->rqBody.DataCount = rqptr->NetIoPtr->ReadCount; 
   }

   if (WATCHING (rqptr, WATCH_REQUEST_BODY))
   {
      if (rqptr->rqHeader.ContentEncodingGzip)
         WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "BODY gzip !UL/!UL bytes",
                    rqptr->rqBody.DataCount, rqptr->rqBody.ContentCount);
      else
      if (rqptr->rqBody.ContentLength)
         WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "BODY !UL/!UL/!UL bytes",
                    rqptr->rqBody.DataCount, rqptr->rqBody.ContentCount,
                    rqptr->rqBody.ContentLength);
      else
         WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "BODY !UL/!UL bytes",
                    rqptr->rqBody.DataCount, rqptr->rqBody.ContentCount);
      WatchDataDump (rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount);
   }

   if (rqptr->rqBody.ProcessFunction)
      (*rqptr->rqBody.ProcessFunction)(rqptr);
   else
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
}

/*****************************************************************************/
/*
Processes an RFC2068 (HTTP/1.1) chunked body.
*/ 

void BodyTransferChunked (REQUEST_STRUCT *rqptr)

{
   int  cnt, tbcnt, status,
        ChunkCount,
        ChunkSize,
        ChunkState;
   char  *bptr, *cptr, *sptr, *zptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
                 "BodyTransferChunked() !&B !UL !&X !UL !&S !UL !UL",
                 rqptr->rqBody.UnEncodeStream,
                 rqptr->rqBody.ChunkState, rqptr->rqBody.DataPtr,
                 rqptr->NetIoPtr->ReadCount, rqptr->NetIoPtr->ReadStatus,
                 rqptr->rqBody.ChunkCount, rqptr->rqBody.ChunkSize);

   if (rqptr->rqBody.ChunkState == CHUNK_STATE_BEGIN)
   {
      /**************/
      /* initialize */
      /**************/

      /* initialize the chunk specific storage */
      rqptr->rqBody.ChunkCount = rqptr->rqBody.ChunkSize = 0;
      rqptr->rqBody.ChunkSizeString[0] = '\0';
      rqptr->rqBody.ChunkState = CHUNK_STATE_SIZE;
   }

   /****************/
   /* process data */
   /****************/

   bptr = cptr = rqptr->rqBody.DataPtr;
   cnt = rqptr->NetIoPtr->ReadCount;

   ChunkCount = rqptr->rqBody.ChunkCount;
   ChunkSize = rqptr->rqBody.ChunkSize;
   ChunkState = rqptr->rqBody.ChunkState;

   while (ChunkState != CHUNK_STATE_END && cnt)
   {
      if (ChunkState == CHUNK_STATE_SIZE && cnt)
      {
         /**********************/
         /* getting chunk size */
         /**********************/

         zptr = rqptr->rqBody.ChunkSizeString +
                sizeof(rqptr->rqBody.ChunkSizeString)-1;
         /* step to the start of any partly read hex number */
         for (sptr = rqptr->rqBody.ChunkSizeString; *sptr; sptr++);

         if (!rqptr->rqBody.ChunkSizeString[0] && !isxdigit(*cptr))
         {
            BodyTransferChunkedError (rqptr, FI_LI);
            return;
         }

         while (cnt && isxdigit(*cptr) && sptr < zptr)
         {
            /* getting hex number string */
            *sptr++ = *cptr++;
            cnt--;
         }
         *sptr = '\0';

         if (cnt)
         {
            /* some constraints on the potential hex size string */
            if ((*cptr != ';' && !ISLWS(*cptr) && NOTEOL(*cptr)))
            {
               BodyTransferChunkedError (rqptr, FI_LI);
               return;
            }
            if (!rqptr->rqBody.ChunkSizeString[0])
            {
               BodyTransferChunkedError (rqptr, FI_LI);
               return;
            }
            if (strlen(rqptr->rqBody.ChunkSizeString) > 8)
            {
               BodyTransferChunkedError (rqptr, FI_LI);
               return;
            }

            /* line format looks acceptable */
            ChunkSize = strtol (rqptr->rqBody.ChunkSizeString, NULL, 16);
            ChunkState = CHUNK_STATE_EOL;

            rqptr->rqBody.ContentCount += ChunkSize;

            if (WATCHING (rqptr, WATCH_REQUEST_BODY))
               WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY,
                          "CHUNK (0x!AZ) !UL bytes",
                          rqptr->rqBody.ChunkSizeString, ChunkSize);
         }
      }

      if (ChunkState == CHUNK_STATE_EOL && cnt)
      {
         /***********************/
         /* finding end-of-line */
         /***********************/

         while (cnt && !ISEOL(*cptr))
         {
            cptr++;
            cnt--;
         }
         if (cnt && ISEOL(*cptr))
         {
            /* reached end-of-line, step to the beginning of the data */
            if (*cptr == '\r') { cptr++; cnt--; }
            if (cnt && *cptr == '\n')
            {
               cptr++;
               cnt--;
               ChunkState = CHUNK_STATE_DATA;
            }
            else
            {
               BodyTransferChunkedError (rqptr, FI_LI);
               return;
            }
         }

         if (!ChunkSize)
         {
            /*****************/
            /* end of chunks */
            /*****************/

            ChunkState = CHUNK_STATE_TRAILER;
            if (cnt && ISEOL(*cptr))
            {
               /* trailing terminating empty line follows immediately */
               if (*cptr == '\r') { cptr++; cnt--; }
               if (cnt && *cptr == '\n') { cptr++; cnt--; }
               ChunkState = CHUNK_STATE_END;
               if (WATCHING (rqptr, WATCH_REQUEST_BODY))
                  WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY,
                             "CHUNK trailer 0 bytes");
               break;
            }
         }
      }

      if (ChunkState == CHUNK_STATE_DATA && cnt)
      {
         /*******************/
         /* un-encode chunk */
         /*******************/

         if (bptr == cptr)
         {
            /* start of buffer, no need to move contents */
            if (cnt <= ChunkSize - ChunkCount)
            {
               /* full buffer if less or equal to that outstanding */
               cptr += cnt;
               bptr += cnt;
               ChunkCount += cnt;
               cnt = 0;
            }
            else
            {
               /* less than full buffer will complete that outstanding */
               cptr += ChunkSize - ChunkCount;
               bptr += ChunkSize - ChunkCount;
               cnt -= ChunkSize - ChunkCount;
               ChunkCount = ChunkSize;
            }
         }
         else
         {
            /* move the chunk data forward in the buffer */
            while (cnt && ChunkCount < ChunkSize)
            {
               *bptr++ = *cptr++;
               ChunkCount++;
               cnt--;
            }
         }

         if (WATCHING (rqptr, WATCH_REQUEST_BODY))
            WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY,
                       "CHUNK !UL/!UL bytes", ChunkCount, ChunkSize);

         if (ChunkCount >= ChunkSize)
         {
            /* copied all of this chunk */
            ChunkCount = ChunkSize = 0;
            rqptr->rqBody.ChunkSizeString[0] = '\0';
            ChunkState = CHUNK_STATE_EOD;
         }
      }

      if (ChunkState == CHUNK_STATE_EOD && cnt)
      {
         /***********************/
         /* finding end-of-data */
         /***********************/

         /* basically just the trailing <CR><LF> */
         if (*cptr == '\r') { cptr++; cnt--; }
         if (cnt && *cptr == '\n')
         {
            cptr++;
            cnt--;
            ChunkState = CHUNK_STATE_SIZE;
         }
         else
         {
            BodyTransferChunkedError (rqptr, FI_LI);
            return;
         }
      }

      if (ChunkState == CHUNK_STATE_TRAILER && cnt)
      {
         /********************/
         /* trailing trailer */
         /********************/

         ChunkState = BodyTransferChunkedTrailer (rqptr, cptr, cnt);

         if (!ChunkState)
         {
            rqptr->NetIoPtr->ReadStatus = SS$_ENDOFFILE;
            rqptr->NetIoPtr->ReadCount = 0;
            return;
         }
      }

      /* adjust to account for any encoding overhead from the buffer */
      rqptr->NetIoPtr->ReadCount = bptr - rqptr->rqBody.DataPtr;
   }

   rqptr->rqBody.ChunkCount = ChunkCount;
   rqptr->rqBody.ChunkSize = ChunkSize;
   rqptr->rqBody.ChunkState = ChunkState;

   rqptr->NetIoPtr->ReadStatus = SS$_NORMAL;
}

/*****************************************************************************/
/*
Processes an RFC2068 (HTTP/1.1) chunked body trailer.
Returns the chunk processing state or zero to indicate a fatal error.
*/ 

int BodyTransferChunkedTrailer
(
REQUEST_STRUCT *rqptr,
char *DataPtr,
int DataCount
)
{
   int  cnt, retval, tbcnt,
        ChunkState;
   char  *cptr, *sptr, *zptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
   {
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
                 "BodyTransferChunkedTrailer() !UL !UL",
                 rqptr->rqBody.ChunkedTrailerBufferCount, DataCount);
      WatchDataDump (DataPtr, DataCount);
   }

   if (!rqptr->rqBody.ChunkedTrailerBufferPtr)
   {
      /* create the trailer buffer */
      rqptr->rqBody.ChunkedTrailerBufferCount =
          rqptr->rqBody.ChunkedTrailerNewLineCount = 0;
      /* allow for a little 'elbow room' in the buffer */
      rqptr->rqBody.ChunkedTrailerBufferSize = NetReadBufferSize - 4;
      rqptr->rqBody.ChunkedTrailerBufferPtr =
         VmGetHeap (rqptr, NetReadBufferSize);
   }

   cptr = DataPtr;
   cnt = DataCount;

   tbcnt = rqptr->rqBody.ChunkedTrailerBufferCount;
   sptr = rqptr->rqBody.ChunkedTrailerBufferPtr;
   zptr = sptr + rqptr->rqBody.ChunkedTrailerBufferSize;
   sptr += tbcnt;

   while (cnt && sptr < zptr)
   {
      if (*cptr == '\n')
         rqptr->rqBody.ChunkedTrailerNewLineCount++;
      else
      if (*cptr != '\r')
         rqptr->rqBody.ChunkedTrailerNewLineCount = 0;
      *sptr++ = *cptr++;
      tbcnt++;
      cnt--;
      if (rqptr->rqBody.ChunkedTrailerNewLineCount == 2)
         break;
   }
   if (sptr >= zptr)
   {
      BodyTransferChunkedError (rqptr, FI_LI);
      return (0);
   }
   *sptr = '\0';

   rqptr->rqBody.ChunkedTrailerBufferCount = tbcnt;

   if (rqptr->rqBody.ChunkedTrailerNewLineCount == 2)
      ChunkState = CHUNK_STATE_END;
   else
      ChunkState = CHUNK_STATE_TRAILER;

   if (ChunkState == CHUNK_STATE_END)
   {
      if (WATCHING (rqptr, WATCH_REQUEST_BODY))
      {
         WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "CHUNK trailer !UL bytes",
                    rqptr->rqBody.ChunkedTrailerBufferCount);
         WatchDataDump (rqptr->rqBody.ChunkedTrailerBufferPtr,
                        rqptr->rqBody.ChunkedTrailerBufferCount);
      }
      if (rqptr->rqBody.ChunkedTrailerBufferCount)
      {
         retval = RequestFields (rqptr, rqptr->rqBody.ChunkedTrailerBufferPtr,
                                 rqptr->rqBody.ChunkedTrailerBufferCount);
         if (retval < 0)
         {
            BodyTransferChunkedError (rqptr, FI_LI);
            return (0);
         }
      }
   }

   return (ChunkState);
}

/*****************************************************************************/
/*
Generate an error and set the read status block to indicate.
*/ 

BodyTransferChunkedError
(
REQUEST_STRUCT *rqptr,
char *SourceModuleName,
int SourceLineNumber
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyTransferChunkedError()");

   rqptr->rqResponse.HttpStatus = 400;
   ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_BODY_READ),
                 SourceModuleName, SourceLineNumber);

   /* set the read status block to indicate an error */
   rqptr->NetIoPtr->ReadStatus = SS$_ABORT;
   rqptr->NetIoPtr->ReadCount = 0;
}

/*****************************************************************************/
/*
ZLIB inflate GZIP compressed data.  Of course a single network read buffer full
can provide multiple data buffer fulls.  This requires that this function be
called multiple times until the output buffer, sourced from the network read
buffer, is emptied.  Essentially this is done by using the data specified by
the IO status block (NetIoPtr->ReadStatus and NetIoPtr->ReadCount) and what
is left in the ZLIB input buffer (gzptr->InflateAvailIn dereived from ZLIB's
avail_in).
*/ 

BodyContentGzip (REQUEST_STRUCT *rqptr)

{
   int  DataCount;
   char  *DataPtr;
   GZIP_COMPRESS  *gzptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyContentGzip() !&X !UL",
                 rqptr->rqBody.DataPtr, rqptr->NetIoPtr->ReadCount);

   DataPtr = rqptr->rqBody.DataPtr;
   DataCount = rqptr->NetIoPtr->ReadCount;

   gzptr = &rqptr->GzipCompress;

   /* if the input stream is empty the read buffer will be fully consumed */
   if (!gzptr->InflateAvailIn) rqptr->NetIoPtr->ReadCount = 0;

   if (!gzptr->InflateStartStream)
   {
      /* initialization */
      if (GzipInflateBegin (rqptr, gzptr, rqptr->rqNet.ReadBufferSize))
      {
         rqptr->rqBody.DataStatus = SS$_NORMAL;
         if (!DataCount) return;
         /* otherwise drop thru to begin processing the gzipped content */
      }
      else
      {
         rqptr->rqBody.DataStatus = SS$_ABORT;
         rqptr->rqBody.DataCount = 0;
         return;
      }
   }

   if (GzipInflate (rqptr, gzptr, &DataPtr, &DataCount))
   {
      rqptr->rqBody.DataStatus = SS$_NORMAL;
      rqptr->rqBody.DataCount = DataCount;
      rqptr->rqBody.DataPtr = DataPtr;
   }
   else
   {
      rqptr->rqBody.DataStatus = SS$_ABORT;
      rqptr->rqBody.DataCount = 0;
   }

}

/*****************************************************************************/
/*
This is a special case.  It reads the entire request body into a buffer.
Needless-to-say the body length must be less than the allocated buffer space. 
It is intended only for the HTADMIN.C and UPD.C modules which like to get small
request query strings as POSTs.  This approach is a bit of a kludge in some
ways but simplifies the parsing of these POSTed query strings significantly.
*/ 

void BodyProcessReadAll (REQUEST_STRUCT *rqptr)

{
   BODY_PROCESS  *prptr;

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

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

   if (!(prptr = rqptr->rqBody.ProcessPtr))
   {
      /**************/
      /* initialize */
      /**************/

      prptr = rqptr->rqBody.ProcessPtr =
         VmGetHeap (rqptr, sizeof(BODY_PROCESS));

      rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_OTHER;

      /* just body buffer size */
      prptr->BlockBufferSize = rqptr->rqBody.ContentLength;
      prptr->BlockBufferPtr = VmGetHeap (rqptr, prptr->BlockBufferSize);
      prptr->BlockBufferCount = 0;
   }

   if (VMSnok (rqptr->rqBody.DataStatus))
   {
      /*****************************/
      /* network error/end-of-file */
      /*****************************/

      if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE)
      {
         rqptr->rqBody.DataPtr = prptr->BlockBufferPtr;
         rqptr->rqBody.DataCount = prptr->BlockBufferCount;
         /* as this will be considered null-terminated we'd better */
         rqptr->rqBody.DataPtr[rqptr->rqBody.DataCount] = '\0';
         SysDclAst (rqptr->rqBody.AstFunction, rqptr);
         return;
      }

      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ);
      ErrorVmsStatus (rqptr, rqptr->rqBody.DataStatus, FI_LI);
      rqptr->rqBody.DataStatus = SS$_ABORT;
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
      return;
   }

   if (prptr->BlockBufferCount + rqptr->rqBody.DataCount >
       prptr->BlockBufferSize)
   {
      /* woops, not big enough! */
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ);
      ErrorVmsStatus (rqptr, SS$_BUFFEROVF, FI_LI);
      rqptr->rqBody.DataStatus = SS$_ABORT;
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
      return;
   }

   /* add what has just been received */
   memcpy (prptr->BlockBufferPtr + prptr->BlockBufferCount,
           rqptr->rqBody.DataPtr,
           rqptr->rqBody.DataCount);
   prptr->BlockBufferCount += rqptr->rqBody.DataCount;

   BodyRead (rqptr);
}

/*****************************************************************************/
/*
Supply the body data in whole 512 byte virtual blocks so it can be written to
disk using block I/O (used by PUT.C module).
*/ 

void BodyProcessByVirtualBlock (REQUEST_STRUCT *rqptr)

{
   int  cnt, status;
   char  *bptr;
   BODY_PROCESS  *prptr;

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

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

   if (!(prptr = rqptr->rqBody.ProcessPtr))
   {
      /**************/
      /* initialize */
      /**************/

      prptr = rqptr->rqBody.ProcessPtr =
         VmGetHeap (rqptr, sizeof(BODY_PROCESS));

      rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_VIRTUALBLOCK;

      BodyBufferAllocate (rqptr);
   }

   bptr = prptr->BlockBufferPtr + prptr->BlockBufferCount;

   if (prptr->BlockLeftOverCount)
   {
      /**********************************/
      /* remains of previous processing */
      /**********************************/

      memcpy (bptr, prptr->BlockLeftOverPtr, prptr->BlockLeftOverCount);
      bptr += prptr->BlockLeftOverCount;
      prptr->BlockLeftOverCount = 0;
      prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr;
   }

   if (VMSnok (rqptr->rqBody.DataStatus))
   {
      /*****************************/
      /* network error/end-of-file */
      /*****************************/

      if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE)
      {
         BodyBufferEndOfFile (rqptr);
         SysDclAst (rqptr->rqBody.AstFunction, rqptr);
         return;
      }

      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ);
      ErrorVmsStatus (rqptr, rqptr->rqBody.DataStatus, FI_LI);
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
      return;
   }

   /*********************************/
   /* populate virtual block buffer */
   /*********************************/

   /* add what has just been received */
   memcpy (bptr, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount);
   prptr->BlockBufferCount += rqptr->rqBody.DataCount;

   BodyBufferWrite (rqptr);
}

/*****************************************************************************/
/*
Decode URL-encoded byte stream.  Eliminates form field names, field equal
symbols and field-separating ampersands.  Converts '+' characters in field
content into spaces and hexadecimal, URL-encoded characters into the respective
character.

A field named "hexencoded" is expected to contain hexadecimal-encoded bytes,
and has these two-digit numbers converted back into bytes before inclusion in
the file.

The presence of a non-null field named "previewonly" causes a temporary,
delete-on-close file name to be generated (for previewing the POSTed file :^). 
A field "previewnote" contains text placed at the very beginning of a previewed
file. This can either be left in place or eliminated by pointer manipulation
depending on the value of "previewonly".  Also see UPD.C module.

Calls 'rqptr->rqBody.AstFunction' with storage set as described above in the
prologue to this module.  When all data has been processed and passed on it
returns SS$_ENDOFFILE.  Provides data in 512 byte virtual blocks as described
in the prologue.
*/ 

void BodyProcessUrlEncoded (REQUEST_STRUCT *rqptr)

{
   int  cnt, status;
   unsigned char  ch;   
   char  *bptr, *cptr, *sptr, *zptr;
   BODY_PROCESS  *prptr;

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

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

   if (!(prptr = rqptr->rqBody.ProcessPtr))
   {
      /**************/
      /* initialize */
      /**************/

      prptr = rqptr->rqBody.ProcessPtr =
         VmGetHeap (rqptr, sizeof(BODY_PROCESS));

      rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_URLENCODED;

      prptr->UrlEncodedFieldNameSize = sizeof(prptr->UrlEncodedFieldName);
      prptr->UrlEncodedFieldValueSize = sizeof(prptr->UrlEncodedFieldValue);
      prptr->UrlEncodedFieldNameCount = prptr->UrlEncodedFieldValueCount = 0;
      prptr->UrlEncodedFieldName[0] = prptr->UrlEncodedFieldValue[0] = '\0';

      /* this is to kick off the field name processing, it's ignored */
      prptr->UrlEncodedFieldName[prptr->UrlEncodedFieldNameCount++] = '!';

      BodyBufferAllocate (rqptr);
   }

   bptr = prptr->BlockBufferPtr + prptr->BlockBufferCount;

   if (prptr->BlockLeftOverCount)
   {
      /**********************************/
      /* remains of previous processing */
      /**********************************/

      memcpy (bptr, prptr->BlockLeftOverPtr, prptr->BlockLeftOverCount);
      bptr += prptr->BlockLeftOverCount;
      prptr->BlockLeftOverCount = 0;
      prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr;
   }

   if (VMSnok (rqptr->rqBody.DataStatus))
   {
      /*****************************/
      /* network error/end-of-file */
      /*****************************/

      if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE)
      {
         BodyBufferEndOfFile (rqptr);
         SysDclAst (rqptr->rqBody.AstFunction, rqptr);
         return;
      }

      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ);
      ErrorVmsStatus (rqptr, rqptr->rqBody.DataStatus, FI_LI);
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
      return;
   }

   /******************/
   /* decode content */
   /******************/

   cptr = rqptr->rqBody.DataPtr;
   cnt = rqptr->rqBody.DataCount;

   while (cnt)
   {
      if (prptr->UrlDecodeIdx &&
          prptr->UrlDecodeIdx < 3)
      {
         prptr->UrlDecode[prptr->UrlDecodeIdx++] = *cptr++;
         cnt--;
         if (prptr->UrlDecodeIdx < 3) continue;
      }
      else
      if (*cptr == '%')
      {
         prptr->UrlDecode[prptr->UrlDecodeIdx++] = *cptr++;
         cnt--;
         continue;
      }
      else
      if (*cptr == '=')
      {
         prptr->UrlEncodedFieldNameCount = 0;
         /* this is to kick off the field value processing, it's ignored */
         prptr->UrlEncodedFieldValue[prptr->UrlEncodedFieldValueCount++] = '!';
         cnt--;
         cptr++;
         continue;
      }
      else
      if (*cptr == '&')
      {
         prptr->UrlEncodedFieldValueCount = 0;
         prptr->UrlEncodedFieldHexEncoded = prptr->UrlEncodedHiddenLF = false;
         /* this is to kick off the field name processing, it's ignored */
         prptr->UrlEncodedFieldName[prptr->UrlEncodedFieldNameCount++] = '!';
         cnt--;
         cptr++;
         continue;
      }
      else
      {
         cnt--;
         ch = *cptr++;
      }

      if (prptr->UrlDecodeIdx)
      {
         ch = 0;
         if (prptr->UrlDecode[1] >= '0' &&
             prptr->UrlDecode[1] <= '9')
            ch = (prptr->UrlDecode[1] - (int)'0') << 4;
         else
         if (TOUP(prptr->UrlDecode[1]) >= 'A' &&
             TOUP(prptr->UrlDecode[1]) <= 'F')
            ch = (TOUP(prptr->UrlDecode[1]) - (int)'A' + 10) << 4;
         else
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI);
            rqptr->rqBody.DataStatus = SS$_ABORT;
            SysDclAst (rqptr->rqBody.AstFunction, rqptr);
            return;
         }
         if (prptr->UrlDecode[2] >= '0' &&
             prptr->UrlDecode[2] <= '9')
            ch += (prptr->UrlDecode[2] - (int)'0');
         else
         if (TOUP(prptr->UrlDecode[2]) >= 'A' &&
             TOUP(prptr->UrlDecode[2]) <= 'F')
            ch += (TOUP(prptr->UrlDecode[2]) - (int)'A' + 10);
         else
         {
            if (WATCHMOD (rqptr, WATCH_MOD_BODY))
               WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
                 "URL-ENC-ERROR: !3AZ !&Z", prptr->UrlDecode, cptr);
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI);
            rqptr->rqBody.DataStatus = SS$_ABORT;
            SysDclAst (rqptr->rqBody.AstFunction, rqptr);
            return;
         }
         prptr->UrlDecode[prptr->UrlDecodeIdx=0] = '\0';
      }
      else
      if (ch == '+')
         ch = ' ';

      /* absorb carriage returns for a stream-LF file */
      if (ch == '\r') continue;

      /*******************/
      /* process content */
      /*******************/

      if (prptr->UrlEncodedFieldNameReserved &&
          prptr->UrlEncodedFieldNameCount)
      {
         /* starting a new field name after processing a reserved name/value */
         if (WATCHMOD (rqptr, WATCH_MOD_BODY))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "!&Z !&Z",
                       prptr->UrlEncodedFieldName,
                       prptr->UrlEncodedFieldValue);

         if (strsame (prptr->UrlEncodedFieldName+1, "success", -1))
            ResponseLocation (rqptr, prptr->UrlEncodedFieldValue+1,
                                     prptr->UrlEncodedFieldValueCount); 
         else
         if (strsame (prptr->UrlEncodedFieldName+1, "protection", -1))
         {
            /* file protection */
            strzcpy (prptr->ProtectionHexString,
                     prptr->UrlEncodedFieldValue+1,
                     sizeof(prptr->ProtectionHexString));
         }
         else
         if (strsame (prptr->UrlEncodedFieldName+1, "previewonly", -1))
         {
            /* if value is non-empty then preview, otherwise ignore */
            if (prptr->UrlEncodedFieldValue[1]) prptr->PreviewOnly = true;
         }
         else
         if (strsame (prptr->UrlEncodedFieldName+1, "previewnote", -1))
         {
            /* preview note (can be a maximum of 254 bytes) */
            if (prptr->PreviewOnly)
            {
               for (sptr = prptr->UrlEncodedFieldValue+1; *sptr; *sptr++)
                  if (*sptr == '^')  /* &#94; */
                     *bptr++ = '\n';
                  else
                     *bptr++ = *sptr;
            }
         }
         else
         if (strsame (prptr->UrlEncodedFieldName+1, "fab$b_rat", -1))
         {
            prptr->UrlEncodedFabRat = atoi(prptr->UrlEncodedFieldValue+1);
            if (WATCHMOD (rqptr, WATCH_MOD_BODY))
               WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "FAB$B_RAT 0x!2XL",
                          prptr->UrlEncodedFabRat);
         }
         else
         if (strsame (prptr->UrlEncodedFieldName+1, "fab$b_rfm", -1))
         {
            prptr->UrlEncodedFabRfm = atoi(prptr->UrlEncodedFieldValue+1);
            if (WATCHMOD (rqptr, WATCH_MOD_BODY))
               WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "FAB$B_RFM 0x!2XL",
                          prptr->UrlEncodedFabRfm);
         }

         /* no longer processing the reserved name/value */
         prptr->UrlEncodedFieldNameReserved = false;
      }

      if (prptr->UrlEncodedFieldNameCount)
      {
         /* currently buffering a field name */
         zptr = prptr->UrlEncodedFieldName + prptr->UrlEncodedFieldNameSize;
         sptr = prptr->UrlEncodedFieldName + prptr->UrlEncodedFieldNameCount;
         if (sptr < zptr) *sptr++ = ch;
         if (sptr >= zptr)
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneralOverflow (rqptr, FI_LI);
            rqptr->rqBody.DataStatus = SS$_ABORT;
            SysDclAst (rqptr->rqBody.AstFunction, rqptr);
            return;
         }
         *sptr = '\0';
         prptr->UrlEncodedFieldNameCount = sptr - prptr->UrlEncodedFieldName;
         continue;
      }

      if (prptr->UrlEncodedFieldValueCount == 1)
      {
         /* just finished buffering a field name, starting on a value */
         prptr->UrlEncodedFieldHexEncoded = false;
         if (strsame (prptr->UrlEncodedFieldName+1, "success", -1))
            prptr->UrlEncodedFieldNameReserved = true;
         else
         if (strsame (prptr->UrlEncodedFieldName+1, "protection", -1))
            prptr->UrlEncodedFieldNameReserved = true;
         else
         if (strsame (prptr->UrlEncodedFieldName+1, "previewonly", -1))
            prptr->UrlEncodedFieldNameReserved = true;
         else
         if (strsame (prptr->UrlEncodedFieldName+1, "previewnote", -1))
            prptr->UrlEncodedFieldNameReserved = true;
         else
         if (strsame (prptr->UrlEncodedFieldName+1, "hexencoded", 10))
            prptr->UrlEncodedFieldHexEncoded = true;
         else
         if (strsame (prptr->UrlEncodedFieldName+1, "fab$b_rat", -1))
            prptr->UrlEncodedFieldNameReserved = true;
         else
         if (strsame (prptr->UrlEncodedFieldName+1, "fab$b_rfm", -1))
            prptr->UrlEncodedFieldNameReserved = true;
         else
            prptr->UrlEncodedFieldNameReserved = false;

         /*
            With constraints on CDATA &#10; entities for field values this
            contains a '^' (&#94;) substituted for any required line-feed.
         */
         if (strsame (prptr->UrlEncodedFieldName+1, "hidden$lf", -1))
            prptr->UrlEncodedHiddenLF = true;

         if (WATCHMOD (rqptr, WATCH_MOD_BODY))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "!&Z !&B",
                       prptr->UrlEncodedFieldName,
                       prptr->UrlEncodedFieldNameReserved);

         if (!prptr->UrlEncodedFieldNameReserved)
            prptr->UrlEncodedFieldNameCount =
               prptr->UrlEncodedFieldValueCount = 0;
      }

      if (prptr->UrlEncodedFieldNameReserved)
      {
         /* currently buffering a reserved field value */
         zptr = prptr->UrlEncodedFieldValue + prptr->UrlEncodedFieldValueSize;
         sptr = prptr->UrlEncodedFieldValue + prptr->UrlEncodedFieldValueCount;
         if (sptr < zptr) *sptr++ = ch;
         if (sptr >= zptr)
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneralOverflow (rqptr, FI_LI);
            rqptr->rqBody.DataStatus = SS$_ABORT;
            SysDclAst (rqptr->rqBody.AstFunction, rqptr);
            return;
         }
         *sptr = '\0';
         prptr->UrlEncodedFieldValueCount = sptr - prptr->UrlEncodedFieldValue;
         continue;
      }

      if (prptr->UrlEncodedHiddenLF && ch == '^')  /* &#94; */
         ch = '\n';
      else
      if (prptr->UrlEncodedFieldHexEncoded)
      {
         prptr->HexDecode[prptr->HexDecodeIdx++] = ch;
         if (prptr->HexDecodeIdx == 1) continue;
         prptr->HexDecodeIdx =  0;

         ch = 0;
         if (prptr->HexDecode[0] >= '0' &&
             prptr->HexDecode[0] <= '9')
            ch = (prptr->HexDecode[0] - (int)'0') << 4;
         else
         if (TOUP(prptr->HexDecode[0]) >= 'A' &&
             TOUP(prptr->HexDecode[0]) <= 'F')
            ch = (TOUP(prptr->HexDecode[0]) - (int)'A' + 10) << 4;
         else
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI);
            rqptr->rqBody.DataStatus = SS$_ABORT;
            SysDclAst (rqptr->rqBody.AstFunction, rqptr);
            return;
         }

         if (prptr->HexDecode[1] >= '0' &&
             prptr->HexDecode[1] <= '9')
            ch += (prptr->HexDecode[1] - (int)'0');
         else
         if (TOUP(prptr->HexDecode[1]) >= 'A' &&
             TOUP(prptr->HexDecode[1]) <= 'F')
            ch += (TOUP(prptr->HexDecode[1]) - (int)'A' + 10);
         else
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI);
            rqptr->rqBody.DataStatus = SS$_ABORT;
            SysDclAst (rqptr->rqBody.AstFunction, rqptr);
            return;
         }
      }

      /* write the character into the virtual block buffer */
      *bptr++ = ch;
   }

   /****************/
   /* post process */
   /****************/

   /* save the current buffer count */
   prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr;

   BodyBufferWrite (rqptr);
}

/*****************************************************************************/
/*
Decode a "multipart/form-data" content-type request body.  This will (or at
least should) comprise one of more MIME parts with data of various types.

Returns SS$_BADPARAM if it can't understand the stream format, SS$_RESULTOVF if
buffer space is exhausted at any time.  Otherwise a normal status.

Calls 'rqptr->rqBody.AstFunction' with storage set as described above in the
prologue to this module.  When all data has been processed and passed on it
returns SS$_ENDOFFILE.  Provides data in 512 byte virtual blocks as described
in the prologue.
*/ 

void BodyProcessMultipartFormData (REQUEST_STRUCT *rqptr)

{
   int  cnt, idx, status;
   char  *bptr, *cptr, *sptr, *zptr;
   BODY_PROCESS  *prptr;

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

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

   if (!(prptr = rqptr->rqBody.ProcessPtr))
   {
      /**************/
      /* initialize */
      /**************/

      if (!ConfigSameContentType (rqptr->rqHeader.ContentTypePtr,
                                  "multipart/form-data", -1))
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PUT_MULTIPART_FORMDATA), FI_LI);
         rqptr->rqBody.DataStatus = SS$_ABORT;
         SysDclAst (rqptr->rqBody.AstFunction, rqptr);
         return;
      }

      prptr = rqptr->rqBody.ProcessPtr =
         VmGetHeap (rqptr, sizeof(BODY_PROCESS));

      rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_MULTIPART_FORMDATA;

      BodyBufferAllocate (rqptr);

      if (!BodyGetMimeBoundary (rqptr))
      {
         /* error message generated by the above function */
         rqptr->rqBody.DataStatus = SS$_ABORT;
         SysDclAst (rqptr->rqBody.AstFunction, rqptr);
         return;
      }

      /* prepend these for boundary matching convenience only */
      prptr->BoundaryBuffer[prptr->BoundaryIdx++] = '\r';
      prptr->BoundaryBuffer[prptr->BoundaryIdx++] = '\n';
   }

   bptr = prptr->BlockBufferPtr + prptr->BlockBufferCount;

   if (prptr->BlockLeftOverCount)
   {
      /**********************************/
      /* remains of previous processing */
      /**********************************/

      memcpy (bptr, prptr->BlockLeftOverPtr, prptr->BlockLeftOverCount);
      bptr += prptr->BlockLeftOverCount;
      prptr->BlockLeftOverCount = 0;
      prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr;
   }

   if (VMSnok (rqptr->rqBody.DataStatus))
   {
      /*****************************/
      /* network error/end-of-file */
      /*****************************/

      if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE)
      {
         BodyBufferEndOfFile (rqptr);
         SysDclAst (rqptr->rqBody.AstFunction, rqptr);
         return;
      }

      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ);
      ErrorVmsStatus (rqptr, rqptr->rqBody.DataStatus, FI_LI);
      rqptr->rqBody.DataStatus = SS$_ABORT;
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
      return;
   }

   /******************/
   /* decode content */
   /******************/

   cptr = rqptr->rqBody.DataPtr;
   cnt = rqptr->rqBody.DataCount;

   while (cnt)
   {
      if (prptr->MimeHeaderIdx)
      {
         /***************************/
         /* buffering a MIME header */
         /***************************/

         if (prptr->MimeHeaderIdx >= sizeof(prptr->MimeHeaderBuffer))
         {
            rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART);
            ErrorVmsStatus (rqptr, SS$_BUFFEROVF, FI_LI);
            rqptr->rqBody.DataStatus = SS$_ABORT;
            SysDclAst (rqptr->rqBody.AstFunction, rqptr);
            return;
         }
         if (*cptr == '\n')
         {
            /* check for the blank line terminating the MIME header */
            if (prptr->MimeHeaderIdx >= 1 &&
                prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx-1] == '\n')
            {
               /* end of MIME header */
               cptr++;
               cnt--;
               prptr->MimeHeaderIdx--;
               prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx] = '\0';
               prptr->MimeHeaderCount = prptr->MimeHeaderIdx;
               prptr->MimeHeaderIdx = 0;
               continue;
            }
            if (prptr->MimeHeaderIdx >= 3 &&
                prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx-1] == '\r' &&
                prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx-2] == '\n' &&
                prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx-3] == '\r')
            {
               /* end of MIME header */
               cptr++;
               cnt--;
               prptr->MimeHeaderIdx -= 3;
               prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx] = '\0';
               prptr->MimeHeaderCount = prptr->MimeHeaderIdx;
               prptr->MimeHeaderIdx = 0;
               continue;
            }
         }
         prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx++] = *cptr++;
         cnt--;
         continue;
      }

      if (prptr->MimeHeaderBuffer[0])
      {
         /****************************/
         /* process MIME part header */
         /****************************/

         if (WATCHING (rqptr, WATCH_REQUEST_BODY))
         {
            int  Length;
            sptr = prptr->MimeHeaderBuffer;
            Length = prptr->MimeHeaderCount;
            if (SAME2(sptr,'\r\n'))
            {
               sptr += 2;
               Length -= 2;
            }
            WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "MULTIPART");
            WatchData (sptr, Length);
         }

         status = BodyProcessMultipartMimeHeader (rqptr);
         if (VMSnok (status))
         {
            /* error message generated by the above function */
            rqptr->rqBody.DataStatus = SS$_ABORT;
            SysDclAst (rqptr->rqBody.AstFunction, rqptr);
            return;
         }

         /* finished with this part header */
         prptr->MimeHeaderCount = 0;
         prptr->MimeHeaderBuffer[0] = '\0';
         /* continue on to buffer the data */
      }

      if (!prptr->BoundaryIdx)
      {
         /*********************************/
         /* looking for start of boundary */
         /*********************************/

         if (*cptr == '\r')
         {
            prptr->BoundaryBuffer[prptr->BoundaryIdx++] = *cptr++;
            cnt--;
            continue;
         }
         /* just drop out the bottom as another octet */
      }
      else
      {
         /*******************************/
         /* still matching for boundary */
         /*******************************/

         if (!prptr->MultipartBoundary[prptr->BoundaryIdx])
         {
            /*******************/
            /* boundary match! */
            /*******************/

            /* reached end of the content-type boundary */
            prptr->BoundaryIdx = 0;
            prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx++] = *cptr++;
            cnt--;

            /* new boundary, post-process any required form data field */
            if (prptr->MultipartFormDataPtr)
            {
               if (WATCHMOD (rqptr, WATCH_MOD_BODY))
               {
                  WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "FORM-DATA");
                  WatchDataFormatted ("!&Z\n", prptr->MultipartFormDataPtr);
               }

               if (prptr->MultipartFormDataPtr == prptr->MultipartSuccessUrl)
                  ResponseLocation (rqptr, prptr->MultipartFormDataPtr,
                                           prptr->MultipartFormDataCount); 
            }

            /* set these to NULL and empty to stop data copying */
            prptr->MultipartContentTypePtr =
               prptr->MultipartFormDataPtr =
               prptr->MultipartFormDataCurrentPtr = NULL;

            continue;
         }

         if (*cptr == prptr->MultipartBoundary[prptr->BoundaryIdx])
         {
            /* still matching */
            prptr->BoundaryBuffer[prptr->BoundaryIdx++] = *cptr++;
            prptr->BoundaryBuffer[prptr->BoundaryIdx] = '\0';
            cnt--;
            continue;
         }

         /********************/
         /* match has failed */
         /********************/

         if (prptr->MultipartFormDataCurrentPtr)
         {
            /* restore match buffer to form data buffer */
            for (idx = 0; idx < prptr->BoundaryIdx; idx++)
            {
               if (prptr->MultipartFormDataCount >
                   prptr->MultipartFormDataSize)
               {
                  rqptr->rqResponse.ErrorTextPtr =
                     MsgFor(rqptr,MSG_PUT_MULTIPART);
                  ErrorVmsStatus (rqptr, SS$_BUFFEROVF, FI_LI);
                  rqptr->rqBody.DataStatus = SS$_ABORT;
                  SysDclAst (rqptr->rqBody.AstFunction, rqptr);
                  return;
               } 
               if (isprint(prptr->BoundaryBuffer[idx]) &&
                   NOTEOL(prptr->BoundaryBuffer[idx]))
                  *prptr->MultipartFormDataCurrentPtr++ =
                     prptr->BoundaryBuffer[idx];
               prptr->MultipartFormDataCount++;
            }
            *prptr->MultipartFormDataCurrentPtr = '\0';
         }
         else
         {
            /* restore match buffer to content buffer */
            if (prptr->MultipartContentTypePtr)
               for (idx = 0; idx < prptr->BoundaryIdx; idx++)
                  *bptr++ = prptr->BoundaryBuffer[idx];
         }
         prptr->BoundaryIdx = 0;
         prptr->BoundaryBuffer[0] = '\0';
         /*
            If a <CR> then restart the matching algorithm using the current
            character. This will allow for correct behaviour with the likes
            of <CR><LF><CR><LF>---..boundary sequences (yup, got caught!)
            If not a <CR> then drop through to treat as just another octet.
         */
         if (*cptr == '\r') continue;
      }

      /**********************/
      /* just another octet */
      /**********************/

      if (prptr->MultipartFormDataCurrentPtr)
      {
         if (prptr->MultipartFormDataCount > prptr->MultipartFormDataSize)
         {
            rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART);
            ErrorVmsStatus (rqptr, SS$_BUFFEROVF, FI_LI);
            rqptr->rqBody.DataStatus = SS$_ABORT;
            SysDclAst (rqptr->rqBody.AstFunction, rqptr);
            return;
         } 
         if (isprint(*cptr) && NOTEOL(*cptr))
            *prptr->MultipartFormDataCurrentPtr++ = *cptr;
      }
      else
      /* if a content-type has been resolved then we're buffering data */
      if (prptr->MultipartContentTypePtr)
         *bptr++ = *cptr;

      cptr++;
      cnt--;
   }

   /****************/
   /* post process */
   /****************/

   /* save the current buffer count */
   prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr;

   BodyBufferWrite (rqptr);
}

/*****************************************************************************/
/*
A "multipart/form-data" MIME part header has been read into a buffer.  This can
comprose serveral lines of header-type information termiated by a NUL
character.  Scan through this text isolating field of interest.  In particular
'uploadfilename', 'protection' and 'name'.  These are used by the PUT.C and
PROXYFTP.C modules.
*/

int BodyProcessMultipartMimeHeader (REQUEST_STRUCT *rqptr)

{
   char  *cptr, *sptr, *zptr,
         *NamePtr;
   BODY_PROCESS  *prptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
                 "BodyProcessMultipartMimeHeader()");

   prptr = rqptr->rqBody.ProcessPtr;

   cptr = prptr->MimeHeaderBuffer;
   if (!SAME2(cptr,'\r\n'))
   {
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART);
      ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI);
      return (SS$_BUGCHECK);
   }
   cptr += 2;

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchDataFormatted ("!&Z\n", cptr);

   while (*cptr)
   {
      while (*cptr && ISLWS(*cptr) && NOTEOL(*cptr)) cptr++;
      if (strsame (cptr, "Content-Disposition:", 20))
      {
         if (WATCHMOD (rqptr, WATCH_MOD_BODY))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "Content-Disposition:");

         /* set these to NULL and empty to stop data copying */
         prptr->MultipartContentTypePtr =
            prptr->MultipartFormDataPtr =
            prptr->MultipartFormDataCurrentPtr = NULL;
         prptr->MultipartFormDataSize = prptr->MultipartFormDataCount = 0;

         cptr += 20;
         while (*cptr && NOTEOL(*cptr))
         {
            if (strsame (cptr, "name=", 5))
            {
               cptr += 5;
               if (*cptr == '\"')
               {
                  cptr++;
                  NamePtr = cptr;
                  while (*cptr && *cptr != '\"' && NOTEOL(*cptr)) cptr++;
                  if (*cptr != '\"')
                  {
                     rqptr->rqResponse.ErrorTextPtr =
                        MsgFor(rqptr,MSG_PUT_MULTIPART);
                     ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI);
                     return (SS$_BUGCHECK);
                  }
               }
               else
               {
                  NamePtr = cptr;
                  while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr)) cptr++;
                  if (!ISLWS(*cptr))
                  {
                     rqptr->rqResponse.ErrorTextPtr =
                        MsgFor(rqptr,MSG_PUT_MULTIPART);
                     ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI);
                     return (SS$_BUGCHECK);
                  }
               }
               *cptr++ = '\0';
               if (WATCHMOD (rqptr, WATCH_MOD_BODY))
                  WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "!&Z", NamePtr);

               if (strsame (NamePtr, "name", -1))
               {
                  /* handled by the storing the associated 'filename=".."' */
               }
               else
               if (strsame (NamePtr, "protection", -1))
               {
                  prptr->MultipartFormDataPtr = prptr->ProtectionHexString;
                  prptr->MultipartFormDataSize =
                     sizeof(prptr->ProtectionHexString);
               }
               else
               if (strsame (NamePtr, "success", -1))
               {
                  prptr->MultipartFormDataPtr = prptr->MultipartSuccessUrl;
                  prptr->MultipartFormDataSize =
                     sizeof(prptr->MultipartSuccessUrl);
               }
               else
               if (strsame (NamePtr, "type", -1))
               {
                  prptr->MultipartFormDataPtr = prptr->FtpFileType;
                  prptr->MultipartFormDataSize = sizeof(prptr->FtpFileType);
               }
               else
               if (strsame (NamePtr, "uploadfilename", -1))
               {
                  prptr->MultipartFormDataPtr = prptr->MultipartUploadFileName;
                  prptr->MultipartFormDataSize =
                     sizeof(prptr->MultipartUploadFileName);
               }
               /* anything else is just ignored */

               prptr->MultipartFormDataCurrentPtr = prptr->MultipartFormDataPtr;
               prptr->MultipartFormDataCount = 0;
            }
            else
            if (strsame (cptr, "filename=", 9))
            {
               zptr = (sptr = prptr->MultipartFileName) +
                      sizeof(prptr->MultipartFileName);

               cptr += 9;
               if (*cptr == '\"')
               {
                  cptr++;
                  while (*cptr && *cptr != '\"' && NOTEOL(*cptr) && sptr < zptr)
                     *sptr++ = *cptr++;
                  if (*cptr != '\"')
                  {
                     rqptr->rqResponse.ErrorTextPtr =
                        MsgFor(rqptr,MSG_PUT_MULTIPART);
                     ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI);
                     return (SS$_BUGCHECK);
                  }
               }
               else
               {
                  while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr)
                     *sptr++ = *cptr++;
                  if (!ISLWS(*cptr))
                  {
                     rqptr->rqResponse.ErrorTextPtr =
                        MsgFor(rqptr,MSG_PUT_MULTIPART);
                     ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI);
                     return (SS$_BUGCHECK);
                  }
               }
               *sptr = '\0';

               prptr->MultipartFormDataFileNameCount++;
               if (WATCHMOD (rqptr, WATCH_MOD_BODY))
                  WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "!&Z (!UL)",
                             prptr->MultipartFileName,
                             prptr->MultipartFormDataFileNameCount);

               /* only one file per upload please! */
               if (prptr->MultipartFormDataFileNameCount > 1)
               {
                  rqptr->rqResponse.ErrorTextPtr =
                     MsgFor(rqptr,MSG_PUT_MULTIPART);
                  rqptr->rqResponse.ErrorOtherTextPtr =
                     "Only one file name per upload please!";
                  ErrorVmsStatus (rqptr, SS$_BADPARAM, FI_LI);
                  return (SS$_BADPARAM);
               }
            }
            while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr)) cptr++;
            while (*cptr && ISLWS(*cptr) && NOTEOL(*cptr)) cptr++;
         }
      }
      else             
      if (strsame (cptr, "Content-Type:", 13))
      {
         if (WATCHMOD (rqptr, WATCH_MOD_BODY))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "Content-Type:");
         cptr += 13;
         while (*cptr && ISLWS(*cptr) && NOTEOL(*cptr)) cptr++;
         /* find length of content-type */
         for (sptr = cptr; *sptr && !ISLWS(*sptr) && NOTEOL(*sptr); sptr++);
         prptr->MultipartContentTypePtr = sptr = VmGetHeap (rqptr, sptr-cptr+1);
         while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr)) *sptr++ = *cptr++;
         *sptr = '\0';
         if (WATCHMOD (rqptr, WATCH_MOD_BODY))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
                       "!&Z", prptr->MultipartContentTypePtr);
      }
      else
      if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      {
         for (sptr = cptr; *sptr && NOTEOL(*sptr); sptr++);
         WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
                    "{!UL}!-!#AZ", sptr-cptr, cptr);
      }

      /* scan to start of next "line" */
      while (*cptr && NOTEOL(*cptr)) cptr++;
      while (*cptr && ISEOL(*cptr)) cptr++;
   }

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Scan through the request's "Content-Type:" header field looking for a MIME
"boundary=" string.
*/

int BodyGetMimeBoundary (REQUEST_STRUCT *rqptr)

{
   char  *cptr, *sptr, *zptr;
   BODY_PROCESS  *prptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
                 "BodyGetMimeBoundary() !&Z", rqptr->rqHeader.ContentTypePtr);

   prptr = rqptr->rqBody.ProcessPtr;

   prptr->MultipartBoundary[0] = '\0';
   prptr->MultipartBoundaryLength = 0;

   cptr = rqptr->rqHeader.ContentTypePtr;
   while (*cptr)
   {
      while (*cptr && *cptr != ';') cptr++;
      if (!*cptr) break;
      cptr++;
      while (*cptr && ISLWS(*cptr)) cptr++;
      if (strsame (cptr, "boundary=", 9))
      {
         cptr += 9;
         zptr = (sptr = prptr->MultipartBoundary) +
                sizeof(prptr->MultipartBoundary);
         /* prepend these only for matching convenience */
         *sptr++ = '\r';
         *sptr++ = '\n';
         *sptr++ = '-';
         *sptr++ = '-';
         while (*cptr && *cptr != ';' && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr) sptr = prptr->MultipartBoundary;
         *sptr = '\0';
         prptr->MultipartBoundaryLength = sptr - prptr->MultipartBoundary;
      }
   }

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
                 "!&Z", prptr->MultipartBoundary + 4);

   if (!prptr->MultipartBoundaryLength)
   {
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART);
      rqptr->rqResponse.ErrorOtherTextPtr = "MIME boundary not found!";
      ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI);
   }

   return (prptr->MultipartBoundaryLength);
}

/*****************************************************************************/
/*
Allocate a multi-block buffer.
*/ 

void BodyBufferAllocate (REQUEST_STRUCT *rqptr)

{
   BODY_PROCESS  *prptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyBufferAllocate()");

   prptr = rqptr->rqBody.ProcessPtr;

   prptr->MultiBlockCount = BodyMultiBlockCount;

   /* should never, ever happen but we need at least this much space */
   if (prptr->MultiBlockCount * 512 < rqptr->rqBody.DataSize)
      prptr->MultiBlockCount = (rqptr->rqBody.DataSize / 512) + 1;

   /* allow sufficient extra buffer to a full network read */
   prptr->BlockBufferSize = (prptr->MultiBlockCount * 512) +
                             rqptr->rqBody.DataSize;
   prptr->BlockBufferPtr = VmGetHeap (rqptr, prptr->BlockBufferSize);
   prptr->BlockBufferCount = prptr->BlockLeftOverCount = 0;
   prptr->BlockBufferVBN = 1;

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                 "MULTIBLOCK !UL", prptr->MultiBlockCount);
}

/*****************************************************************************/
/*
The file buffer is larger than the network data buffer and so with larger files
fills incrementally over multiple reads.  Check if the file buffer is at
capacity by calculating the number of virtual blocks contained in it.  If less
than the multiblock number read more.  When enough virtual blocks have
accumulated for an efficient write, calculate what remains and note that. 
Write the number of virtual blocks which then ASTs to read more.  Each time the
read AST delivers that routine copies into the file buffer the data that
remained before copying the just-read data.
*/ 

void BodyBufferWrite (REQUEST_STRUCT *rqptr)

{
   int  BytesLeftOver,
        VirtualBlocks;
   BODY_PROCESS  *prptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY,
                 "BodyBufferWrite() !UL !UL",
                 rqptr->rqBody.ProcessPtr->BlockBufferCount,
                 rqptr->rqBody.ProcessPtr->BlockBufferCount / 512);

   prptr = rqptr->rqBody.ProcessPtr;

   VirtualBlocks = prptr->BlockBufferCount / 512;
   if (VirtualBlocks >= prptr->MultiBlockCount)
   {
      VirtualBlocks = prptr->MultiBlockCount;
      BytesLeftOver = prptr->BlockBufferCount - VirtualBlocks * 512;
      prptr->BlockBufferCount = VirtualBlocks * 512;
      if (prptr->BlockLeftOverCount = BytesLeftOver)
         prptr->BlockLeftOverPtr = prptr->BlockBufferPtr +
                                   prptr->BlockBufferCount;
   }
   else
      VirtualBlocks = 0;

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "!UL !UL !UL !UL",
                 prptr->BlockBufferCount, prptr->BlockBufferVBN,
                 VirtualBlocks, BytesLeftOver);

   if (VirtualBlocks)
   {
      rqptr->rqBody.DataVBN = prptr->BlockBufferVBN;
      rqptr->rqBody.DataPtr = prptr->BlockBufferPtr;
      rqptr->rqBody.DataCount = prptr->BlockBufferCount;
      rqptr->rqBody.DataStatus = SS$_NORMAL;

      prptr->ProcessedByteCount += prptr->BlockBufferCount;
      prptr->BlockBufferVBN += VirtualBlocks;

      /* now the buffer count can be reset */
      prptr->BlockBufferCount = 0;

      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
   }
   else
      BodyRead (rqptr);
}

/*****************************************************************************/
/*
Handle still-buffered data when end-of-file is encountered.
*/ 

void BodyBufferEndOfFile (REQUEST_STRUCT *rqptr)

{
   BODY_PROCESS  *prptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_BODY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyBufferEndOfFile()");

   prptr = rqptr->rqBody.ProcessPtr;

   if (prptr->BlockBufferCount)
   {
      rqptr->rqBody.DataVBN = prptr->BlockBufferVBN;
      rqptr->rqBody.DataPtr = prptr->BlockBufferPtr;
      rqptr->rqBody.DataCount = prptr->BlockBufferCount;
      rqptr->rqBody.DataStatus = SS$_NORMAL;
      prptr->ProcessedByteCount += prptr->BlockBufferCount;
      prptr->BlockBufferCount = 0;
   }
   else
      rqptr->rqBody.DataStatus = SS$_ENDOFFILE;
}

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

