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


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

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

#include "wasd.h"

#define WASD_MODULE "BODY"

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

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

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

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.
*/ 
 
BodyReadBegin
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction,
REQUEST_AST ProcessFunction
)
{
   int  status,
        DataSize;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_BODY,
                 "BodyReadBegin() !UL", rqptr->rqHeader.ContentLength);

   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 (SS$_BUGCHECK, "BodyReadBegin()", FI_LI);
      rqptr->rqBody.DataStatus = rqptr->rqNet.ReadIOsb.Status = SS$_BUGCHECK;
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
      return;
   }

   rqptr->rqBody.AstFunction = AstFunction;
   rqptr->rqBody.ProcessFunction = ProcessFunction;
   rqptr->rqBody.DataPtr = rqptr->rqNet.ReadBufferPtr;
   rqptr->rqBody.DataSize = rqptr->rqNet.ReadBufferSize;
   rqptr->rqBody.DataCount = rqptr->rqBody.ContentCount = 0;
   rqptr->rqBody.ContentLength = rqptr->rqHeader.ContentLength;
   rqptr->rqBody.DataVBN = 1;

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

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

   /*  allow for any content received along with the header */
   rqptr->rqNet.ReadIOsb.Count = rqptr->BytesRx -
                                 rqptr->rqHeader.RequestHeaderLength;

   /* ensure this does not exceed the specified content length */
   if (rqptr->rqNet.ReadIOsb.Count > rqptr->rqBody.ContentLength)
       rqptr->rqNet.ReadIOsb.Count = rqptr->rqBody.ContentLength;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_BODY,
         "Length:!UL BytesRx:!UL HeaderLength:!UL (!UL) IOsb.Count:!UL",
         rqptr->rqBody.ContentLength, rqptr->BytesRx,
         rqptr->rqHeader.RequestHeaderLength,
         rqptr->BytesRx - rqptr->rqHeader.RequestHeaderLength,
         rqptr->rqNet.ReadIOsb.Count);

   if (rqptr->rqNet.ReadIOsb.Count)
   {
      /* data arrived with the request header, provide that */
      rqptr->rqBody.DataPtr = rqptr->rqHeader.RequestHeaderPtr +
                              rqptr->rqHeader.RequestHeaderLength;
      rqptr->rqNet.ReadIOsb.Status = SS$_NORMAL;
      SysDclAst (&BodyReadAst, rqptr);
      return;
   }

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

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

BodyRead (REQUEST_STRUCT *rqptr)

{
   int  DataSize;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_BODY, "BodyRead() !UL !UL",
                 rqptr->rqBody.ContentCount, rqptr->rqBody.ContentLength);

   /* if there is still outstanding body to be read */
   if (rqptr->rqBody.ContentCount < rqptr->rqBody.ContentLength)
   {
      /* body read buffer needs resetting after data supplied with header */
      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;
   }

   /* it's all been received */
   rqptr->rqBody.DataStatus = rqptr->rqNet.ReadIOsb.Status = SS$_ENDOFFILE;
   rqptr->rqBody.DataCount = rqptr->rqNet.ReadIOsb.Count = 0;
   if (rqptr->rqBody.ProcessFunction)
      SysDclAst (rqptr->rqBody.ProcessFunction, rqptr);
   else
      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
} 

/*****************************************************************************/
/*
Some browsers seem unhappy if an error is reported before at least some of the
body is read using network I/O (remember some can be supplied with the request
header).  Therefore the size limit check is placed in here after the first read
has completed.  This function will never be called for ENDOFFILE, only success
or legitimate error status.
*/ 

BodyReadAst (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr;
   char  String [256];

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_BODY,
                 "BodyReadAst() !&F !&S !UL !UL !UL", &BodyReadAst,
                 rqptr->rqNet.ReadIOsb.Status, rqptr->rqNet.ReadIOsb.Count,
                 rqptr->rqBody.ContentLength, rqptr->rqBody.ContentCount);

   if (VMSok (rqptr->rqNet.ReadIOsb.Status))
   {
      /* if first call, request body allowed to be this big? */
      if (!rqptr->rqBody.ContentCount &&
          !rqptr->ProxyTaskPtr &&
          Config.cfMisc.PutMaxKbytes &&
          rqptr->rqHeader.ContentLength / 1000 > Config.cfMisc.PutMaxKbytes)
      {
         cptr = MsgFor(rqptr,MSG_REQUEST_BODY_MAX);
         status = WriteFao (String, sizeof(String), NULL, cptr,
                            rqptr->rqHeader.MethodName,
                            rqptr->rqHeader.ContentLength / 1000,
                            Config.cfMisc.PutMaxKbytes);
         if (VMSnok (status)) ErrorNoticed (status, "sys$fao()", FI_LI);
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, String, FI_LI);
         rqptr->rqNet.ReadIOsb.Status = SS$_ABORT;
      }
   }
   else
   {
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ);
      ErrorVmsStatus (rqptr, rqptr->rqNet.ReadIOsb.Status, FI_LI);
   }

   if (VMSok (rqptr->rqBody.DataStatus = rqptr->rqNet.ReadIOsb.Status))
   {
      rqptr->rqBody.DataCount = rqptr->rqNet.ReadIOsb.Count; 
      rqptr->rqBody.ContentCount += rqptr->rqNet.ReadIOsb.Count;
         
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST_BODY))
      {
         WatchThis (rqptr, FI_LI, WATCH_REQUEST_BODY, "BODY !UL/!UL bytes",
                    rqptr->rqBody.ContentCount, rqptr->rqBody.ContentLength);
         WatchDataDump (rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount);
      }
   }

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

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

BodyProcessReadAll (REQUEST_STRUCT *rqptr)

{
   BODY_PROCESS  *prptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, 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.DataSize;
      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)
      {
         /* legitimate end of request body */
         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).
*/ 

BodyProcessByVirtualBlock (REQUEST_STRUCT *rqptr)

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

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, 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;

      /* body buffer size plus two RMS blocks for elbow-room */
      prptr->BlockBufferSize = rqptr->rqBody.DataSize + (512 * 2);
      prptr->BlockBufferPtr = VmGetHeap (rqptr, prptr->BlockBufferSize);
      prptr->BlockBufferCount = prptr->BlockLeftOverCount = 0;
      prptr->BlockBufferVBN = prptr->BlockBufferNextVBN = 1;
   }

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

      if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE)
      {
         /* legitimate end of request body */
         if (prptr->BlockLeftOverCount)
         {
            /* anything left over from last processing */
            memcpy (prptr->BlockBufferPtr,
                    prptr->BlockLeftOverPtr,
                    prptr->BlockLeftOverCount);
            /* sweep any detritus from the back of the block */
            memset (prptr->BlockBufferPtr + prptr->BlockLeftOverCount, 0,
                    512 - prptr->BlockLeftOverCount);

            rqptr->rqBody.DataVBN = prptr->BlockBufferNextVBN;
            rqptr->rqBody.DataPtr = prptr->BlockBufferPtr;
            rqptr->rqBody.DataCount = prptr->BlockLeftOverCount;
            rqptr->rqBody.DataStatus = SS$_NORMAL;
            prptr->BlockLeftOverCount = prptr->BlockBufferVBN = 0;
         }
         else
            rqptr->rqBody.DataStatus = SS$_ENDOFFILE;

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

   bptr = prptr->BlockBufferPtr;

   /* anything left over from previous processing */
   if (prptr->BlockLeftOverCount)
   {
      memcpy (bptr, prptr->BlockLeftOverPtr, prptr->BlockLeftOverCount);
      bptr += prptr->BlockLeftOverCount;
      prptr->BlockLeftOverCount = 0;
   }

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

   prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr;
   VirtualBlocks = prptr->BlockBufferCount / 512;
   BytesLeftOver = prptr->BlockBufferCount % 512;
   prptr->BlockBufferCount = VirtualBlocks * 512;
   if (prptr->BlockLeftOverCount = BytesLeftOver)
      prptr->BlockLeftOverPtr = prptr->BlockBufferPtr +
                                prptr->BlockBufferCount;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_BODY, "!UL !UL !UL !UL",
                 prptr->BlockBufferCount, prptr->BlockBufferVBN,
                 VirtualBlocks, BytesLeftOver);

   if (VirtualBlocks)
   {
      prptr->BlockBufferVBN = prptr->BlockBufferNextVBN;
      prptr->ProcessedByteCount += prptr->BlockBufferCount;
      prptr->BlockBufferNextVBN += VirtualBlocks;

      rqptr->rqBody.DataVBN = prptr->BlockBufferVBN;
      rqptr->rqBody.DataPtr = prptr->BlockBufferPtr;
      rqptr->rqBody.DataCount = prptr->BlockBufferCount;
      rqptr->rqBody.DataStatus = SS$_NORMAL;

      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
   }
   else
      BodyRead (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.
*/ 

BodyProcessUrlEncoded (REQUEST_STRUCT *rqptr)

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

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, 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++] = '!';

      /* body buffer size plus two RMS blocks for elbow-room */
      prptr->BlockBufferSize = rqptr->rqBody.DataSize + (512 * 2);
      prptr->BlockBufferPtr = VmGetHeap (rqptr, prptr->BlockBufferSize);
      prptr->BlockBufferCount = prptr->BlockLeftOverCount = 0;
      prptr->BlockBufferVBN = prptr->BlockBufferNextVBN = 1;
   }

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

      if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE)
      {
         /* legitimate end of request body */
         if (prptr->BlockLeftOverCount)
         {
            /* anything left over from last processing */
            memcpy (prptr->BlockBufferPtr,
                    prptr->BlockLeftOverPtr,
                    prptr->BlockLeftOverCount);
            /* sweep any detritus from the back of the block */
            memset (prptr->BlockBufferPtr + prptr->BlockLeftOverCount, 0,
                    512 - prptr->BlockLeftOverCount);

            rqptr->rqBody.DataVBN = prptr->BlockBufferNextVBN;
            rqptr->rqBody.DataPtr = prptr->BlockBufferPtr;
            rqptr->rqBody.DataCount = prptr->BlockLeftOverCount;
            rqptr->rqBody.DataStatus = SS$_NORMAL;
            prptr->BlockLeftOverCount = prptr->BlockBufferVBN = 0;
         }
         else
            rqptr->rqBody.DataStatus = SS$_ENDOFFILE;

         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;
   }

   /******************************/
   /* setup virtual block buffer */
   /******************************/

   bptr = prptr->BlockBufferPtr;

   /* anything left over from previous processing */
   if (prptr->BlockLeftOverCount)
   {
      memcpy (bptr, prptr->BlockLeftOverPtr, prptr->BlockLeftOverCount);
      bptr += prptr->BlockLeftOverCount;
      prptr->BlockLeftOverCount = 0;
   }

   /******************/
   /* 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 (toupper(prptr->UrlDecode[1]) >= 'A' &&
             toupper(prptr->UrlDecode[1]) <= 'F')
            ch = (toupper(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 (toupper(prptr->UrlDecode[2]) >= 'A' &&
             toupper(prptr->UrlDecode[2]) <= 'F')
            ch += (toupper(prptr->UrlDecode[2]) - (int)'A' + 10);
         else
         {
            if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
               WatchThis (rqptr, FI_LI, 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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
            WatchThis (rqptr, FI_LI, WATCH_MOD_BODY, "!&Z !&Z",
                       prptr->UrlEncodedFieldName,
                       prptr->UrlEncodedFieldValue);
         if (strsame (prptr->UrlEncodedFieldName+1, "success", -1))
         {
            /* put this URL into the redirection pointer with leading null!! */
            rqptr->rqResponse.LocationPtr =
               VmGetHeap (rqptr, prptr->UrlEncodedFieldValueCount+2);
            rqptr->rqResponse.LocationPtr[0] = '\0';
            strcpy (rqptr->rqResponse.LocationPtr+1,
                    prptr->UrlEncodedFieldValue+1);
         }
         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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
               WatchThis (rqptr, FI_LI, 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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
               WatchThis (rqptr, FI_LI, 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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
            WatchThis (rqptr, FI_LI, 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 (toupper(prptr->HexDecode[0]) >= 'A' &&
             toupper(prptr->HexDecode[0]) <= 'F')
            ch = (toupper(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 (toupper(prptr->HexDecode[1]) >= 'A' &&
             toupper(prptr->HexDecode[1]) <= 'F')
            ch += (toupper(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 */
   /****************/

   prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr;
   VirtualBlocks = prptr->BlockBufferCount / 512;
   BytesLeftOver = prptr->BlockBufferCount % 512;
   prptr->BlockBufferCount = VirtualBlocks * 512;
   if (prptr->BlockLeftOverCount = BytesLeftOver)
      prptr->BlockLeftOverPtr = prptr->BlockBufferPtr +
                                prptr->BlockBufferCount;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_BODY, "!UL !UL !UL !UL",
                 prptr->BlockBufferCount, prptr->BlockBufferVBN,
                 VirtualBlocks, BytesLeftOver);

   if (VirtualBlocks)
   {
      prptr->BlockBufferVBN = prptr->BlockBufferNextVBN;
      prptr->ProcessedByteCount += prptr->BlockBufferCount;
      prptr->BlockBufferNextVBN += VirtualBlocks;

      rqptr->rqBody.DataVBN = prptr->BlockBufferVBN;
      rqptr->rqBody.DataPtr = prptr->BlockBufferPtr;
      rqptr->rqBody.DataCount = prptr->BlockBufferCount;
      rqptr->rqBody.DataStatus = SS$_NORMAL;

      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
   }
   else
      BodyRead (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.
*/ 

BodyProcessMultipartFormData (REQUEST_STRUCT *rqptr)

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

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, 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;

      /* body buffer size plus two RMS blocks for "leftovers" and elbow-room */
      prptr->BlockBufferSize = rqptr->rqBody.DataSize + (512 * 2);
      prptr->BlockBufferPtr = VmGetHeap (rqptr, prptr->BlockBufferSize);
      prptr->BlockBufferCount = prptr->BlockLeftOverCount = 0;
      prptr->BlockBufferVBN = prptr->BlockBufferNextVBN = 1;

      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';
   }

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

      if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE)
      {
         /* legitimate end of request body */
         if (prptr->BlockLeftOverCount)
         {
            /* anything left over from last processing */
            memcpy (prptr->BlockBufferPtr,
                    prptr->BlockLeftOverPtr,
                    prptr->BlockLeftOverCount);
            /* sweep any detritus from the back of the block */
            memset (prptr->BlockBufferPtr + prptr->BlockLeftOverCount, 0,
                    512 - prptr->BlockLeftOverCount);

            rqptr->rqBody.DataVBN = prptr->BlockBufferNextVBN;
            rqptr->rqBody.DataPtr = prptr->BlockBufferPtr;
            rqptr->rqBody.DataCount = prptr->BlockLeftOverCount;
            rqptr->rqBody.DataStatus = SS$_NORMAL;
            prptr->BlockLeftOverCount = prptr->BlockBufferVBN = 0;
         }
         else
            rqptr->rqBody.DataStatus = SS$_ENDOFFILE;

         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;
   }

   /******************************/
   /* setup virtual block buffer */
   /******************************/

   bptr = prptr->BlockBufferPtr;

   /* anything left over from previous processing */
   if (prptr->BlockLeftOverCount)
   {
      memcpy (bptr, prptr->BlockLeftOverPtr, prptr->BlockLeftOverCount);
      bptr += prptr->BlockLeftOverCount;
      prptr->BlockLeftOverCount = 0;
   }

   /******************/
   /* 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_CATEGORY(WATCH_REQUEST_BODY))
         {
            int  Length;
            sptr = prptr->MimeHeaderBuffer;
            Length = prptr->MimeHeaderCount;
            if (*(unsigned short*)sptr == '\r\n')
            {
               sptr += 2;
               Length -= 2;
            }
            WatchThis (rqptr, FI_LI, 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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
               {
                  WatchThis (rqptr, FI_LI, WATCH_MOD_BODY, "FORM-DATA");
                  WatchDataFormatted ("!&Z\n", prptr->MultipartFormDataPtr);
               }

               if (prptr->MultipartFormDataPtr == prptr->MultipartSuccessUrl)
               {
                  /* put into the redirection pointer with leading null!! */
                  rqptr->rqResponse.LocationPtr =
                     VmGetHeap (rqptr, prptr->MultipartFormDataCount+2);
                  rqptr->rqResponse.LocationPtr[0] = '\0';
                  strcpy (rqptr->rqResponse.LocationPtr+1,
                          prptr->MultipartFormDataPtr);
               }
            }

            /* set these to NULL and empty to stop data copying */
            prptr->MultipartFormDataPtr =
               prptr->MultipartFormDataCurrentPtr = NULL;
            prptr->MultipartContentType[0] = '\0';

            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->MultipartContentType[0])
               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->MultipartContentType[0])
         *bptr++ = *cptr;

      cptr++;
      cnt--;
   }

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

   prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr;
   VirtualBlocks = prptr->BlockBufferCount / 512;
   BytesLeftOver = prptr->BlockBufferCount % 512;
   prptr->BlockBufferCount = VirtualBlocks * 512;
   if (prptr->BlockLeftOverCount = BytesLeftOver)
      prptr->BlockLeftOverPtr = prptr->BlockBufferPtr +
                                prptr->BlockBufferCount;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_BODY, "BUFFER !UL !UL !UL !UL",
                 prptr->BlockBufferCount, prptr->BlockBufferVBN,
                 VirtualBlocks, BytesLeftOver);

   if (VirtualBlocks)
   {
      prptr->BlockBufferVBN = prptr->BlockBufferNextVBN;
      prptr->ProcessedByteCount += prptr->BlockBufferCount;
      prptr->BlockBufferNextVBN += VirtualBlocks;

      rqptr->rqBody.DataVBN = prptr->BlockBufferVBN;
      rqptr->rqBody.DataPtr = prptr->BlockBufferPtr;
      rqptr->rqBody.DataCount = prptr->BlockBufferCount;
      rqptr->rqBody.DataStatus = SS$_NORMAL;

      SysDclAst (rqptr->rqBody.AstFunction, rqptr);
   }
   else
      BodyRead (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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_BODY,
                 "BodyProcessMultipartMimeHeader()");

   prptr = rqptr->rqBody.ProcessPtr;

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

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

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

         /* set these to NULL and empty to stop data copying */
         prptr->MultipartFormDataPtr =
            prptr->MultipartFormDataCurrentPtr = NULL;
         prptr->MultipartFormDataSize = prptr->MultipartFormDataCount = 0;
         prptr->MultipartContentType[0] = '\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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
                  WatchThis (rqptr, FI_LI, 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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
                  WatchThis (rqptr, FI_LI, 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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
            WatchThis (rqptr, FI_LI, WATCH_MOD_BODY, "Content-Type:");
         cptr += 13;
         zptr = (sptr = prptr->MultipartContentType) +
                sizeof(prptr->MultipartContentType);
         while (*cptr && ISLWS(*cptr) && NOTEOL(*cptr)) cptr++;
         while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr)
            *sptr++ = *cptr++;
         if (sptr >= zptr)
         {
            rqptr->rqResponse.ErrorTextPtr =
               MsgFor(rqptr,MSG_PUT_MULTIPART);
            ErrorVmsStatus (rqptr, SS$_BUFFEROVF, FI_LI);
            return (SS$_BUFFEROVF);
         }
         *sptr = '\0';
         if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
            WatchThis (rqptr, FI_LI, WATCH_MOD_BODY,
                       "!&Z", prptr->MultipartContentType);
      }
      else
      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      {
         for (sptr = cptr; *sptr && NOTEOL(*sptr); sptr++);
         WatchThis (rqptr, FI_LI, WATCH_MOD_BODY,
                    "{!UL}!-!#AZ", sptr-cptr, cptr);
      }

      /* scan to start of next "line" */
      while (*cptr && NOTEOL(*cptr)) cptr++;
      while (*cptr && EOL(*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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, 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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_BODY))
      WatchThis (rqptr, FI_LI, 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);
}

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

