/*****************************************************************************/
/*
                                   CGI.c

Provide generalized CGI scripting support.

Creates a buffer containing either, the DCL commands to create CGI variables at
the VMS command line (e.g. for standard CGI scripts), or a stream of
'name=value' pairs for the CGI variables (e.g. for CGIplus scripts). 

The buffer is dynamically allocated and contains a series of sequential records
comprising a word (16 bits) with the length of a following (varying)
null-terminated string (including the null character).  The end of the series
is indicated by a zero length record.

This buffer is scanned from from first to last and the strings passed to the
script 'input' stream as appropriate.


CGI RESPONSE HEADER
-------------------
In common with other implementations, if the first line of the response header
begins "HTTP/1." then it is considered to be a full HTTP response (non-parsed
header output), the server does no more header processing and it is up to the
script to provide a fully compliant HTTP response.

Handling of the CGI response header is pretty standard.  It should begin with
"Content-Type:", "Status:", or "Location:".  One of more of these should be
received before the end-of-header blank line or other header lines.  As soon as
any other line is received the server considered the CGI header complete.

The CGI header can be received record-by-record, with or without any
carriage-control, or as a block of lines with correct carriage-control (only
complete lines will be processed correctly).

The "X-vms-record-mode:" field is a VMS Apacheism for indicating to the server
whether VMS script output must be checked for correct carriage-control and
corrected if necessary.  It seems as if 0 is "not record mode", 1 is.

Current "Script-Control:" Directives
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
no-abort                     CGI/1.2, do not abort script because of timeout
X-buffer-records             buffer records before sending to client
X-cache-max=<integer>        kBytes maximum
X-cache-expires=<param>      "day", "hour", "min", 'hh:mm:ss' or minutes
X-content-handler=<param>    "SSI", content should be processed by SSI engine
X-crlf-mode                  ensure all records have a trailing <CR><LF>
X-error-line=<integer>       source line error noted (optional)
X-error-module=<string>      source code module error noted (optional)
X-error-text=<string>        textual explanation of error (required)
X-error-vms-status=<integer> if generated by a VMS status error (optional)
X-error-vms-text=<string>    VMS explanation (often a file spec, optional)
X-filter-stream              remove all non-printable characters
X-lifetime=<int>             "none" or integer, set script process lifetime
X-record-mode                ensure all records have at least trailing <LF>
X-stream-mode                do not alter records at all, WSSIWCG!
X-timeout-output=<param>     "none" or integer, set request output timer
X-timeout-noprogress=<param> "none" or integer, set request no-progress timer
(all times are in minutes)

The "Script-Control:" is a proposed CGI/1.2 header field intended for script
control ;^). By default WASD writes each record to the client as it is received
from the script (in case of slow or intermittent output).  For script's where
this is known not to be an issue this directive can be used to have the server
buffer the records, outputting the block when the buffer fills or at script
conclusion.  This is often *much* more efficient.  The record mode forces each
record received to be checked for correct carriage-control (a trailing <LF>)
and if not present to be appended.


SCRIPT REQUESTED ERROR MESSAGE
------------------------------
Using the script control error fields a script can request the server to
generate an error response on it's behalf.  This has the advantage of
generating error messages conforming to the configured components and layout of
server error responses.  The following show examples of the response header
lines required to use this facility.  The 'X-error-text' field is what triggers
the error generation and is the only mandatory field.

Vanilla error message

  |Status: 400<LF>
  |Script-Control: X-error-text="Object not found."<LF>
  |<LF>

VMS status error message

  |Status: 403<LF>
  |Script-Control: X-error-text="opening file"<LF>
  |Script-Control: X-error-vms-status=36<LF>
  |Script-Control: X-error-vms-text="HT_ROOT:[SRC]EXAMPLE.TXT"<LF>
  |<LF>

or using a VMS hexadecimal string (with module name and line information)

  |Status: 403<LF>
  |Script-Control: X-error-text="opening file"<LF>
  |Script-Control: X-error-vms-status=%X00000024<LF>
  |Script-Control: X-error-vms-text="HT_ROOT:[SRC]EXAMPLE.TXT"<LF>
  |Script-Control: X-error-module=EXAMPLE<LF>
  |Script-Control: X-error-line=123<LF>
  |<LF>


CGI VARIABLES
-------------
Most of these CGI variable names are those supported by the INTERNET-DRAFT
authored by D.Robinson (drtr@ast.cam.ac.uk), 8 January 1996, plus some
"convenience" variables, breaking the query string into its components (KEY_,
FORM_, etc.) The CGI symbols (CERN/VMS-HTTPd-like DCL symbols instead of Unix
environment variables) are created by the SYS$COMMAND stream of each
script process before the script DCL procedure is invoked. By default each
variable name is prefixed by "WWW_" (similar to CERN HTTPd), although this can
be modified at the command line when starting the server.

     VARIABLE NAME            DESCRIPTION                            ORIGIN
     -------------            -----------                            ------

  (if the request is not authorized only AUTH_TYPE will exist)
  o  AUTH_ACCESS ............ "READ" or "READ+WRITE"                 WASD
  o  AUTH_AGENT ............. indicates authentication agent         WASD
  o  AUTH_GROUP ............. path authorization group               WASD
  o  AUTH_PASSWORD .......... only if "EXTERNAL" realm               WASD
  o  AUTH_REALM ............. authentication realm                   WASD
  o  AUTH_REALM_DESCRIPTION . realm description                      WASD
  o  AUTH_REMOTE_USER ....... original, now proxied username         WASD
  o  AUTH_TYPE .............. "BASIC" or "DIGEST" (or empty)         CGI
  o  AUTH_USER .............. details of user                        WASD

  o  CONTENT_LENGTH ......... "Content-Length:" from header          CGI
  o  CONTENT_TYPE ........... "Content-Type:" from header            CGI

  o  DOCUMENT_ROOT .......... base directory for serving files       Apache

  o  FORM_field ............. query "&" separated form elements      WASD
  o  FORM__integer .......... relaxed empty form field names         WASD

  o  GATEWAY_BG ............. socket BG device name                  WASD
  o  GATEWAY_EOF ............ CGIPLUSEOF sentinal (if CGIplus)       WASD
  o  GATEWAY_EOT ............ CGIPLUSEOT sentinal (if not DECnet)    WASD
  o  GATEWAY_ESC ............ CGIPLUSESC sentinal (if not DECnet)    WASD
  o  GATEWAY_INTERFACE ...... "CGI/1.1"                              CGI
  o  GATEWAY_MRS ............ maximum record size of mailbox         WASD

  o  HTML_BODYTAG ........... report <BODY..> tag (e.g. "BGCOLOR=")  WASD
  o  HTML_FOOTER ............ report footer text                     WASD
  o  HTML_FOOTERTAG ......... report footer <TD..> tag               WASD
  o  HTML_HEADER ............ report header text                     WASD
  o  HTML_HEADERTAG ......... report header <TD..> tag               WASD

  o  HTTP_ACCEPT ............ list of browser-accepted content types CGI
  O  HTTP_ACCEPT_ENCODING ... list of browser-accepted encodings     CGI
  o  HTTP_ACCEPT_CHARSET .... list of browser-accepted character set CGI
  o  HTTP_ACCEPT_LANGUAGE ... list of browser-accepted languages     CGI
  o  HTTP_AUTHORIZATION ..... only if "EXTERNAL" realm               CGI
  o  HTTP_CACHE_CONTROL ..... HTTP/1.1 cache control field           CGI
  o  HTTP_COOKIE ............ any cookie sent by the client          CGI
  o  HTTP_FORWARDED ......... list of proxy/gateway hosts            CGI
  o  HTTP_HOST .............. destination host name/port             CGI
  o  HTTP_IF_NOT_MODIFIED ... GMT time string                        CGI
  o  HTTP_PRAGMA ............ any pragma directive of header         CGI
  o  HTTP_RANGE ............. requested body byte range              CGI
  o  HTTP_REFERER ........... source document URL for this request   CGI
  o  HTTP_USER_AGENT ........ client/browser identification string   CGI
  o  HTTP_X_FORWARDED_FOR ... proxy forwarded for this client        SQUID

  o  KEY_integer ............ query string "+" separated elements    WASD
  o  KEY_COUNT .............. number of "+" separated elements       WASD

  o  PATH_INFO .............. virtual path of data requested in URL  CGI
  o  PATH_ODS ............... on-disk structure of path (0, 2 or 5)  WASD
  o  PATH_TRANSLATED ........ VMS file path of data requested in URL CGI

  o  QUERY_STRING ........... string following "?" in URL            CGI

  o  REMOTE_ADDR ............ IP host address of HTTP client         CGI
  o  REMOTE_HOST ............ IP host name of HTTP client            CGI
  o  REMOTE_PORT ............ IP port of HTTP client                 WASD
  o  REMOTE_USER ............ authenticated username (or empty)      CGI
  o  REQUEST_CHARSET ........ charset SET by mapping rule            WASD
  o  REQUEST_CONTENT_TYPE ... content-type SET by mapping rule       WASD
  o  REQUEST_METHOD ......... "GET", "PUT", etc.                     CGI
  o  REQUEST_SCHEME ......... "http:" or "https:"                    WASD
  o  REQUEST_TIME_GMT ....... request GMT time                       WASD
  o  REQUEST_TIME_LOCAL ..... request local time                     WASD
  o  REQUEST_URI ............ un-encoded path[?query-string]         Apache

  o  SCRIPT_DEFAULT ......... script process default directory       WASD
  o  SCRIPT_FILENAME ........ (e.g. "CGI-BIN:[000000]TEST.COM")      Apache
  o  SCRIPT_NAME ............ name of script (e.g. "/query")         CGI
  o  SCRIPT_RTE ............. script run-time environment            WASD
  o  SERVER_ADDR ............ IP host address of server system       WASD
  o  SERVER_ADMIN ........... "webmaster@site.domain"                Apache
  o  SERVER_CHARSET ......... default charset (e.g. "ISO-8895-1")    WASD
  o  SERVER_GMT ............. offset from GMT time (e.g. "+09:30)    WASD
  o  SERVER_NAME ............ IP host name of server system          CGI
  o  SERVER_PROTOCOL ........ HTTP protocol version ("HTTP/1.0")     CGI
  o  SERVER_PORT ............ IP port request was received on        CGI
  o  SERVER_SOFTWARE ........ software ID of the HTTPD daemon        CGI
  o  SERVER_SIGNATURE ....... "server host port"                     Apache

  o  UNIQUE_ID .............. a "unique" request ID                  Apache

  o  some SSL related CGI variable can be generated when SSL is available
     (see Sesola.C module)

NOTE: If script portability is a concern then confine use to the "standard" CGI
variables.  For WASD-specific scripts the extension variables may be used.  Not
all variables will be present for all requests.


CGI VARIABLES USING DCL SYMBOLS
-------------------------------
Due to the mechanism used to create DCL symbols for the CGI variables (for
standard CGI) the variable value is limited to the symbol size number of
characters minus the length of the DCL symbol name assignment (e.g.
"WWW_QUERY_STRING=A+B+C+D").  DCL symbol creation at the command line is
limited by the CLI command line length (255 characters for pre 7.3-2, 4095 for
post 7.3-1).  Symbol values however can be up to 1024 (or pragmatically about
1000 characters) for pre 7.3-2 and 8192 (or thereabouts) for post 7.3-1,
probably enough for any CGI variable value.  If a CGI value is too large for
for a single command-line assignment then build it up using multiple
assignments, a symbol assignment kludge!  All this is of course ultimately
limited by the size of the DCL command mailbox.


CGI VARIABLES USING CGIPLUS STREAM
----------------------------------
The maximum 'name=value' length for a CGI variable conveyed using the CGIPLUSIN
stream is limited by the size of of the 'DclCgiPlusInSize' global storage value
which is in turn set by the [BufferSizeDclCgiPlusIn] configuration directive. 
Hence if this is set to 3072 bytes (the current default) then the maximum
'name=value' length is the that value minus 3 bytes.  This value is also the
total mailbox buffer space allocated for script process IPC and so is also the
limit on all variables!  So, in the circumstance where the largest variable
needs to be 4000 bytes in length, and the rest total up to another 3000 bytes,
the [BufferSizeDclCgiPlusIn] is suggested to be 8192 bytes.  When this value is
adjusted it is recommended to review the HTTP server account's BYTLM quota.


CGIPLUS VARIABLE STRUCTURE
--------------------------
CGIplus (and standard CGI) variables are originally created as a single,
structured block of data.  The internal structure is quite simple.  Each
null-terminated "name=value" pair is preceded by an unsigned short (16 bit)
length field.  This value includes all characters in the "name=value" pair,
plus the terminating null character.  Hence the maximum length of one of these
pairs is 65535 minus 1.  The end of the "name=value" pairs is indicated by a
zero-value (and hence "name=value" length) short.

  |length|name=value\0|length|name=value\0|length|name=value\0|zero|

This data structure can easily be walked.  Passing these CGI variables to the
scripting process can be done in two ways, parsing each CGI variable and
providing it individually as a "record", and by providing the entire data
structure intact as a "struct".  Of course with the latter it remains for the
script itself to parse the structure to obtain the CGI variable values.

Transfering the CGIplus variables as a single structure (rather than as
per-variable records) can double the throughput!!  Demonstrated using the
[SRC.CGIPLUS]CGIPLUSTEST.C program.  This indicates that there is much less
than half the overhead for performing this using the 'struct' method!


VERSION HISTORY
---------------
07-APR-2004  MGD  bugfix; agent script should have non-strict-CGI ignored
                  (stupid problem introduced with script output caching)
26-FEB-2004  MGD  add SCRIPT_DEFAULT from SET 'script=default=<string>'
10-JAN-2004  MGD  absorb CGI/NPH header,
                  Script-Control: X-content-handler=SSI field
23-DEC-2003  MGD  for VMS 7.3-2 and later take advantage of the larger 
                  EDCL CLI line (255->4095) and symbol (1024->8192) sizes
28-SEP-2003  MGD  add HTTP_KEEP_ALIVE and HTTP_CONNECTION
21-AUG-2003  MGD  HTTP_RANGE field CGI variable
09-JUL-2003  MGD  add new dynamic caching capability
21-JUN-2003  MGD  bugfix; CgiOutput() (jpp@esme.fr)
11-MAY-2003  MGD  unrecognised request fields as "HTTP_.." variables
02-APR-2003  MGD  add HTTP_X_FORWARDED_FOR CGI variable
01-MAR-2003  MGD  path setting HTML body/header/footer
24-JAN-2003  MGD  relax initial CGI response line checking
17-JAN-2003  MGD  path setting 'script=query=none' suppresses parsing of
                  query string into form or key elements
                  added GATEWAY_EOF/EOT/ESC variables,
                  sentinals changed to have only RMS-compliant characters,
                  path set carriage-control on CGIPLUSIN stream,
                  supply more detail from "%DCL-E-OPENIN, blah" responses
10-JAN-2003  MGD  path setting 'script=query=relaxed' allows form-url-encoded
                  query strings to be non-conformant without error reporting
05-DEC-2002  MGD  in non-strict CGI mode report anything like
                  "%DCL-E-OPENIN, blah" as a failed script activation
08-OCT-2002  MGD  Script-Control: X-error-... fields
17-AUG-2002  MGD  modify PATH_ODS variable to allow for PWK and SRI
01-AUG-2002  MGD  bugfix; do not count callout records for CGI header purposes
16-JUL-2002  MGD  last-minute relaxation of stricter CGI header processing
                  allowing a trailing CR to terminate a single header record
30-APR-2002  MGD  "Cache-Control:" field for Mozilla compatibility
30-MAR-2002  MGD  stream-mode with zero output as suppressed carriage-control
24-NOV-2001  MGD  significant rework CgiOutput() CGI header processing
14-OCT-2001  MGD  support script=params=(name=value) SET rule
15-SEP-2001  MGD  modify CgiOutput() processing,
                  X-filter-stream removes non-printable characters
04-AUG-2001  MGD  support module WATCHing
27-FEB-2001  MGD  suppress "x-internal/" CONTENT_TYPE
20-FEB-2001  MGD  AUTH_REMOTE_USER for proxied authorization,
                  refine detection of authorization agent reponse errors
01-FEB-2001  MGD  bugfix; CGI strict output suppression for auth agent
18-JAN-2001  MGD  bugfix; REMOTE_PORT ntohs()
19-DEC-2000  MGD  refine [CgiStrictOutput] to detect an incomplete header
05-SEP-2000  MGD  additional "Script-Control:" directives
08-AUG-2000  MGD  if response "Content-Encoding:" force stream mode,
                  add GATEWAY_BG and GATEWAY_MRS variables
01-JUL-2000  MGD  modify CgiOutput() to better handle CGI responses,
                  add "Script-Control:" (CGI/1.2)
24-JUN-2000  MGD  SCRIPT_RTE for persistant run-time environments
10-MAY-2000  MGD  refined CgiOutput() and CgiHeader()
08-APR-2000  MGD  CGI binary header response can now be processed,
                  (some) VMS Apache compatibility
31-DEC-1999  MGD  PATH_ODS variable (to indicate on-disk file system)
17-DEC-1999  MGD  bugfix; quote double-up in CgiVariable()
27-NOV-1999  MGD  rework DCL variable stream,
                  remove sys$fao() from stream CGI variable creation,
                  avoid "''" substitution creating DCL symbol CGI variables
20-NOV-1999  MGD  end-of-header blank line may now be an empty record,
                  or a record containing a single '\n' or '\r\n' sequence
06-NOV-1999  MGD  allow slightly more latitude with form fields, no "=value"
                  (i.e. name terminated by '&') and back-to-back '&'
14-SEP-1999  MGD  modify CgiOutput() for delayed CGI "Content-Type:"
28-AUG-1999  MGD  support CGIplus "agents", added AUTH_USER
30-MAY-1999  MGD  add SSL variables (via call to SesolaCgiGenerateVariables())
21-FEB-1999  MGD  change authorization CGI variables
28-DEC-1998  MGD  added HTTP_ACCEPT_ENCODING
07-NOV-1998  MGD  WATCH facility
17-OCT-1998  MGD  CgiUrlDecodeString(),
                  SERVER_CHARSET, REQUEST_CHARSET, REQUEST_CONTENT_TYPE
28-APR-1998  MGD  CGI variable memory allocation changed in support of SSI
03-APR-1998  MGD  bugfix; extra quotes generated in form field value
19-MAR-1998  MGD  suppress 'Authorization:' unless "external" authorization
06-DEC-1997  MGD  functionality unbundled from DCL.c, generalized for version 5
*/
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

/* standard C header files */
#include <ctype.h>
#include <stdio.h>

/* VMS related header files */
#include <descrip.h>
#include <dvidef.h>
#include <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "CGI"

/**********/
/* macros */
/**********/

#define CLI$_BUFOVF 229400

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

char  ErrorCgiCli [] = "CLI reported \"!AZ\"",
      ErrorCgiNotStrict [] = "NOT a strict CGI response!!";

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

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

extern BOOL  OdsExtended;

extern int  DclCgiHeaderSize,
            DclCgiPlusInSize,
            DclSysCommandSize,
            DclSysOutputSize,
            EfnWait,
            ServerPort,
            SsiSizeMax;

extern char  DclCgiVariablePrefix[],
             HttpProtocol[],
             SoftwareID[],
             TimeGmtString[];

extern CONFIG_STRUCT Config;
extern HTTPD_PROCESS  HttpdProcess;
extern MSG_STRUCT Msgs;
extern SYS_INFO  SysInfo;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Creates a buffer containing a series of null-terminated strings, terminated by
a null-string (two successive null characters).  Each of the strings contains
either DCL command(s) to create a DCL symbol (CGI variable) using the CLI, or
an equate separated 'name=value' pair used for CGIplus variable streams, etc.
*/ 

CgiGenerateVariables
(
REQUEST_STRUCT *rqptr,
int VarType
)
{
   static $DESCRIPTOR (NumberFaoDsc, "!UL\0");
   static $DESCRIPTOR (StatusValueFaoDsc, "%X!8UL\0");
   static char  BgDevNam [65];
   static unsigned short  BgDevNamLength;
   static struct {
      short  buf_len;
      short  item;
      char   *buf_addr;
      short  *ret_len;
   }
   BgDevNamItemList [] = 
   {
      { sizeof(BgDevNam), DVI$_DEVNAM, &BgDevNam, &BgDevNamLength },
      { 0, 0, 0, 0 }
   };


   int  idx, status,
        Count,
        EmptyFieldNameCount,
        KeyCount;
   char  *cptr, *sptr, *zptr,
         *NameValuePtr;
   char  FieldName [256],
         LocalDateTime [48];

#ifdef __VAX
   char  String [1024];
#else
   /* allow for EDCL available with VMS 7.3-2 and later */
   char  String [8192];
#endif /* __VAX */

   $DESCRIPTOR (StringDsc, String);
   struct {
      unsigned short  Status;
      unsigned short  Count;
      unsigned long  Unused;
   } IOsb;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_CGI))
      WatchThis (rqptr, FI_LI, WATCH_MOD_CGI, "CgiGenerateVariables()");

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
      WatchThis (rqptr, FI_LI, WATCH_CGI, "VARIABLES !&?stream\rdcl\r",
                 VarType == CGI_VARIABLE_STREAM);

   if (rqptr->rqCgi.BufferLength)
   {
      /* free previous variables and storage */
      if (rqptr->rqCgi.BufferPtr)
         VmFreeFromHeap (rqptr, rqptr->rqCgi.BufferPtr, FI_LI); 
      rqptr->rqCgi.BufferLength = 0;
      rqptr->rqCgi.BufferPtr = rqptr->rqCgi.BufferCurrentPtr = NULL;
   }

   if (VarType == CGI_VARIABLE_STREAM)
      CgiVariableBufferMemory (rqptr, DclCgiPlusInSize*2);
   else
      CgiVariableBufferMemory (rqptr, DclSysCommandSize);

   HttpLocalTimeString (LocalDateTime, &rqptr->rqTime.Vms64bit);

   if (VMSnok (status =
       CgiVariable (rqptr, "AUTH_TYPE", rqptr->rqAuth.Type, VarType)))
      return (status);

   if (rqptr->rqAuth.Type[0])
   {
      if (rqptr->rqAuth.SourceRealm)
      { 
         if (VMSnok (status =
             CgiVariable (rqptr, "AUTH_ACCESS", 
                          AuthCanString (rqptr->rqAuth.RequestCan,
                                         AUTH_CAN_FORMAT_LONG),
                          VarType)))
            return (status);
      }

      if (rqptr->rqAuth.PathParameterPtr &&
          rqptr->rqAuth.PathParameterPtr[0])
      { 
         if (VMSnok (status =
             CgiVariable (rqptr, "AUTH_AGENT",
                          rqptr->rqAuth.PathParameterPtr, VarType)))
            return (status);
      }

      if (rqptr->rqAuth.GroupWritePtr[0])
      {
         if (VMSnok (status =
             CgiVariable (rqptr, "AUTH_GROUP",
                          rqptr->rqAuth.GroupWritePtr, VarType)))
            return (status);
      }
      else
      {
         if (VMSnok (status =
             CgiVariable (rqptr, "AUTH_GROUP",
                          rqptr->rqAuth.GroupReadPtr, VarType)))
            return (status);
      }

      if ((rqptr->rqAuth.PathParameterPtr &&
           rqptr->rqAuth.PathParameterPtr[0]) ||
          rqptr->rqAuth.SourceRealm == AUTH_SOURCE_EXTERNAL)
      {
         if (VMSnok (status =
             CgiVariable (rqptr, "AUTH_PASSWORD",
                          rqptr->RemoteUserPassword, VarType)))
            return (status);
      }

      if (VMSnok (status =
          CgiVariable (rqptr, "AUTH_REALM", rqptr->rqAuth.RealmPtr, VarType)))
         return (status);

      if (VMSnok (status =
          CgiVariable (rqptr, "AUTH_REALM_DESCRIPTION",
                       rqptr->rqAuth.RealmDescrPtr, VarType)))
         return (status);

      if (rqptr->rqAuth.RemoteUser[0])
         if (VMSnok (status =
             CgiVariable (rqptr, "AUTH_REMOTE_USER",
                          rqptr->rqAuth.RemoteUser, VarType)))
            return (status);

      if (VMSnok (status =
          CgiVariable (rqptr, "AUTH_USER", rqptr->rqAuth.UserDetailsPtr, VarType)))
         return (status);
   }

   sys$fao (&NumberFaoDsc, 0, &StringDsc, rqptr->rqHeader.ContentLength);
   if (VMSnok (status =
       CgiVariable (rqptr, "CONTENT_LENGTH", String, VarType)))
      return (status);

   if (rqptr->rqHeader.ContentTypePtr)
   {
      if (VMSnok (status =
          CgiVariable (rqptr, "CONTENT_TYPE",
                       rqptr->rqHeader.ContentTypePtr, VarType)))
         return (status);
   }
   else
   if (rqptr->rqContentInfo.ContentTypePtr &&
       !strsame (rqptr->rqContentInfo.ContentTypePtr, "x-internal/", 11))
   {
      if (VMSnok (status =
          CgiVariable (rqptr, "CONTENT_TYPE",
                       rqptr->rqContentInfo.ContentTypePtr, VarType)))
         return (status);
   }
   else
   {
      if (VMSnok (status =
          CgiVariable (rqptr, "CONTENT_TYPE", "", VarType)))
         return (status);
   }

   /* Apache-like - just empty for WASD */
   if (!(cptr = rqptr->rqPathSet.MapRootPtr)) cptr = "";
   if (VMSnok (status =
       CgiVariable (rqptr, "DOCUMENT_ROOT", cptr, VarType)))
      return (status);

   if (Config.cfScript.GatewayBg &&
       rqptr->ServicePtr->RequestScheme != SCHEME_HTTPS)
   {
      status = sys$getdviw (EfnWait, rqptr->rqClient.Channel, 0,
                            &BgDevNamItemList, &IOsb, 0, 0, 0);
      if (VMSok (status)) status = IOsb.Status;
      if (VMSok(status))
      {
         BgDevNam[BgDevNamLength] = '\0';
         if (VMSnok (status =
             CgiVariable (rqptr, "GATEWAY_BG",
                          BgDevNam[0] == '_' ? BgDevNam+1 : BgDevNam,
                          VarType)))
            return (status);
      }
      else
         return (status);
   }

   if (VMSnok (status =
       CgiVariable (rqptr, "GATEWAY_INTERFACE", "CGI/1.1", VarType)))
      return (status);

   if (VarType == CGI_VARIABLE_STREAM)
   {
      /* if 'stream' then must be CGIplus */
      if (rqptr->rqCgi.EofLength)
         if (VMSnok (status =
             CgiVariable (rqptr, "GATEWAY_EOF", rqptr->rqCgi.EofPtr, VarType)))
            return (status);
   }

   if (rqptr->rqCgi.EotLength)
      if (VMSnok (status =
          CgiVariable (rqptr, "GATEWAY_EOT", rqptr->rqCgi.EotPtr, VarType)))
         return (status);

   if (rqptr->rqCgi.EscLength)
      if (VMSnok (status =
          CgiVariable (rqptr, "GATEWAY_ESC", rqptr->rqCgi.EscPtr, VarType)))
         return (status);

   if (rqptr->DclTaskPtr)
   {
      /* size of mailbox */
      sys$fao (&NumberFaoDsc, 0, &StringDsc, DclSysOutputSize);
      if (VMSnok (status =
           CgiVariable (rqptr, "GATEWAY_MRS", String, VarType)))
          return (status);
   }

   if (rqptr->rqHeader.AcceptPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_ACCEPT",
                       rqptr->rqHeader.AcceptPtr, VarType)))
      return (status);

   if (rqptr->rqHeader.AcceptCharsetPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_ACCEPT_CHARSET",
                       rqptr->rqHeader.AcceptCharsetPtr, VarType)))
      return (status);

   if (rqptr->rqHeader.KeepAlivePtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_CONNECTION",
                       rqptr->rqHeader.ConnectionPtr, VarType)))
      return (status);

   if (rqptr->rqHeader.AcceptEncodingPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_ACCEPT_ENCODING",
                       rqptr->rqHeader.AcceptEncodingPtr, VarType)))
      return (status);

   if (rqptr->rqHeader.KeepAlivePtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_KEEP_ALIVE",
                       rqptr->rqHeader.KeepAlivePtr, VarType)))
      return (status);

   if (rqptr->rqHeader.AcceptLangPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_ACCEPT_LANGUAGE",
                       rqptr->rqHeader.AcceptLangPtr, VarType)))
         return (status);

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_EXTERNAL &&
       rqptr->rqHeader.AuthorizationPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_AUTHORIZATION",
                       rqptr->rqHeader.AuthorizationPtr, VarType)))
         return (status);

   if (rqptr->rqHeader.CacheControlPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_CACHE_CONTROL", rqptr->rqHeader.CacheControlPtr, VarType)))
         return (status);

   if (rqptr->rqHeader.CookiePtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_COOKIE", rqptr->rqHeader.CookiePtr, VarType)))
         return (status);

   if (rqptr->rqHeader.ForwardedPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_FORWARDED",
                       rqptr->rqHeader.ForwardedPtr, VarType)))
         return (status);

   if (rqptr->rqHeader.HostPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_HOST", rqptr->rqHeader.HostPtr, VarType)))
         return (status);

   if (rqptr->rqHeader.IfModifiedSincePtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_IF_MODIFIED_SINCE",
                       rqptr->rqHeader.IfModifiedSincePtr, VarType)))
         return (status);

   if (rqptr->rqHeader.PragmaPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_PRAGMA", rqptr->rqHeader.PragmaPtr, VarType)))
         return (status);

   if (rqptr->rqHeader.RangePtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_RANGE", rqptr->rqHeader.RangePtr, VarType)))
         return (status);

   if (rqptr->rqHeader.RefererPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_REFERER", rqptr->rqHeader.RefererPtr, VarType)))
         return (status);

   if (rqptr->rqHeader.UserAgentPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_USER_AGENT",
                       rqptr->rqHeader.UserAgentPtr, VarType)))
         return (status);

   if (rqptr->rqHeader.XForwardedForPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTTP_X_FORWARDED_FOR",
                       rqptr->rqHeader.XForwardedForPtr, VarType)))
         return (status);

   /* unrecognised request fields as "HTTP_.." CGI variables */
   if (rqptr->rqHeader.UnknownFieldsCount) memcpy (FieldName, "HTTP_", 5);
   for (idx = 0; idx < rqptr->rqHeader.UnknownFieldsCount; idx++)
   {
      zptr = (sptr = FieldName+5) + sizeof(FieldName)-6;
      cptr = rqptr->rqHeader.UnknownFieldsPtr[idx];
      while (*cptr && *cptr != ':' && sptr < zptr)
      {
         if (isalnum(*cptr))
            *sptr++ = toupper(*cptr);
         else
            *sptr++ = '_';
         cptr++;
      }
      *sptr = '\0';
      while (*cptr && *cptr != ':') cptr++;
      if (*cptr) cptr++;
      while (ISLWS(*cptr)) cptr++;
      if (VMSnok (status = CgiVariable (rqptr, FieldName, cptr, VarType)))
         return (status);
   }

   if (VMSnok (status =
       CgiVariable (rqptr, "PATH_INFO", rqptr->rqHeader.PathInfoPtr, VarType)))
      return (status);

   switch (rqptr->PathOds)
   {
      case 0 : cptr = NULL; break;
      case MAPURL_PATH_ODS_2 : cptr = "2"; break;
      case MAPURL_PATH_ODS_5 : if (OdsExtended) cptr = "5"; break;
      case MAPURL_PATH_ODS_ADS : cptr = "ADS"; break;
      case MAPURL_PATH_ODS_PWK : cptr = "PWK"; break;
      case MAPURL_PATH_ODS_SMB : cptr = "SMB"; break;
      case MAPURL_PATH_ODS_SRI : cptr = "SRI"; break;
      default : cptr = "?";
   }
   if (cptr)
      if (VMSnok (status =
          CgiVariable (rqptr, "PATH_ODS", cptr, VarType)))
         return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "PATH_TRANSLATED",
                    rqptr->ParseOds.ExpFileName, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "QUERY_STRING", rqptr->rqHeader.QueryStringPtr, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "REMOTE_ADDR",
                    rqptr->rqClient.IpAddressString, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "REMOTE_HOST", rqptr->rqClient.Lookup.HostName, VarType)))
      return (status);

   sys$fao (&NumberFaoDsc, 0, &StringDsc, rqptr->rqClient.IpPort);
   if (VMSnok (status =
       CgiVariable (rqptr, "REMOTE_PORT", String, VarType)))
      return (status);

   if (rqptr->rqAuth.CaseLess)
   {
      /* force remote user name to upper-case if a case-less authentication */
      zptr = (sptr = String) + sizeof(String)-1;
      for (cptr = rqptr->RemoteUser;
           *cptr && sptr < zptr;
           *sptr++ = toupper(*cptr++));
      *sptr = '\0';
      if (VMSnok (status =
          CgiVariable (rqptr, "REMOTE_USER", String, VarType)))
         return (status);
   }
   else
   if (VMSnok (status =
       CgiVariable (rqptr, "REMOTE_USER", rqptr->RemoteUser, VarType)))
      return (status);

   if (rqptr->rqPathSet.CharsetPtr &&
       rqptr->rqPathSet.CharsetPtr[0] &&
       rqptr->rqPathSet.CharsetPtr[0] != '(')
   {
      if (VMSnok (status =
          CgiVariable (rqptr, "REQUEST_CHARSET",
                       rqptr->rqPathSet.CharsetPtr, VarType)))
         return (status);
   }

   if (rqptr->rqPathSet.ContentTypePtr)
   {
      if (VMSnok (status =
          CgiVariable (rqptr, "REQUEST_CONTENT_TYPE",
                       rqptr->rqPathSet.ContentTypePtr, VarType)))
         return (status);
   }

   if (VMSnok (status =
       CgiVariable (rqptr, "REQUEST_METHOD", rqptr->rqHeader.MethodName, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "REQUEST_SCHEME",
                    rqptr->ServicePtr->RequestSchemeNamePtr, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "REQUEST_TIME_GMT", rqptr->rqTime.GmDateTime, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "REQUEST_TIME_LOCAL", LocalDateTime, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "REQUEST_URI", rqptr->rqHeader.RequestUriPtr, VarType)))
      return (status);

   /* Apache-like */
   if (rqptr->rqCgi.ScriptFileNamePtr &&
       rqptr->rqCgi.ScriptFileNamePtr[0])
   {
      /* VMS syntax, script complete file name */
      if (VMSnok (status =
           CgiVariable (rqptr, "SCRIPT_FILENAME",
                        rqptr->rqCgi.ScriptFileNamePtr, VarType)))
          return (status);
   }

   if (rqptr->rqPathSet.ScriptDefaultPtr &&
       rqptr->rqPathSet.ScriptDefaultPtr[0] != '#')
   {
      /* script default directory ('#' means backward-compatible) */
      if (VMSnok (status =
           CgiVariable (rqptr, "SCRIPT_DEFAULT",
                        rqptr->rqPathSet.ScriptDefaultPtr, VarType)))
          return (status);
   }

   if (VMSnok (status =
       CgiVariable (rqptr, "SCRIPT_NAME", rqptr->ScriptName, VarType)))
      return (status);

   if (rqptr->DclTaskPtr &&
       rqptr->DclTaskPtr->ScriptRunTime[0] &&
       rqptr->DclTaskPtr->ScriptRunTime[0] != '!')
   {
      /* Run-Time Environment (RTE engine file name) */
      if (VMSnok (status =
           CgiVariable (rqptr, "SCRIPT_RTE",
                        rqptr->DclTaskPtr->ScriptRunTime, VarType)))
          return (status);
   }

   /* Apache-like */
   if (Config.cfServer.AdminEmail[0])
      if (VMSnok (status =
          CgiVariable (rqptr, "SERVER_ADMIN",
                       Config.cfServer.AdminEmail, VarType)))
         return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "SERVER_CHARSET", Config.cfContent.CharsetDefault, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "SERVER_GMT", TimeGmtString, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "SERVER_NAME",
                    rqptr->ServicePtr->ServerHostName, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "SERVER_ADDR",
                    rqptr->ServicePtr->ServerIpAddressString, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "SERVER_PROTOCOL", HttpProtocol, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "SERVER_PORT",
                    rqptr->ServicePtr->ServerPortString, VarType)))
      return (status);

   /* Apache-like */
   ServerSignature (rqptr, String, sizeof(String));
   if (String[0])
      if (VMSnok (status =
          CgiVariable (rqptr, "SERVER_SIGNATURE", String, VarType)))
         return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "SERVER_SOFTWARE", SoftwareID, VarType)))
      return (status);

   /* Apache-like */
   if (VMSnok (status =
       CgiVariable (rqptr, "UNIQUE_ID", GenerateUniqueId(rqptr), VarType)))
      return (status);

   /*****************/
   /* SSL variables */
   /*****************/

   if (VMSnok (status = SesolaCgiGenerateVariables (rqptr, VarType)))
      return (status);

   /***************************/
   /* query string components */
   /***************************/

   if (rqptr->rqHeader.QueryStringPtr[0] &&
       !rqptr->rqPathSet.ScriptQueryNone)
   {
      EmptyFieldNameCount = KeyCount = 0;

      cptr = rqptr->rqHeader.QueryStringPtr;
      while (*cptr && *cptr != '=') cptr++;
      /* if an equal symbol was found then its a form not a keyword search */
      if (*cptr)
      {
         /***************/
         /* form fields */
         /***************/

         memcpy (FieldName, "FORM_", 5);
         cptr = rqptr->rqHeader.QueryStringPtr;
         while (*cptr)
         {
            sptr = FieldName + 5;
            zptr = FieldName + sizeof(FieldName);
            while (*cptr && *cptr != '=' && *cptr != '&' && sptr < zptr)
            {
               if (isalnum(*cptr))
                  *sptr++ = toupper(*cptr++);
               else
               {
                  *sptr++ = '_';
                  cptr++;
               }
            }
            if (sptr >= zptr)
            {
               ErrorVmsStatus (rqptr, SS$_RESULTOVF, FI_LI);
               return (STS$K_ERROR);
            }
            *sptr = '\0';

            if (!FieldName[5] || (*cptr && *cptr != '=' && *cptr != '&'))
            {
               if (*cptr == '&')
               {
                  /* allow back-to-back '&' (effectively null field name) */
                  cptr++;
                  continue;
               }
               /* out-of-place '=' (no field name) */
               if (rqptr->rqPathSet.ScriptQueryRelaxed)
               {
                  /* let's be relaxed about this, script will handle it */
                  sys$fao (&NumberFaoDsc, 0, &StringDsc, ++EmptyFieldNameCount);
                  FieldName[5] = '_';
                  strcpy (FieldName+6, String);
               }
               else
               {
                  rqptr->rqResponse.HttpStatus = 400;
                  ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_FORM),
                                FI_LI);
                  return (STS$K_ERROR);
               }
            }

            /* if encountered an '=' */
            if (*cptr == '=') cptr++;
            Count = CgiUrlDecodeString (rqptr, cptr,
                                        String, sizeof(String), '&');
            if (Count < 0) return (STS$K_ERROR);
            cptr += Count;
            if (VMSnok (status =
                CgiVariable (rqptr, FieldName, String, VarType)))
               return (status);
         }

         if (VMSnok (status =
             CgiVariable (rqptr, "KEY_COUNT", "0", VarType)))
            return (status);
      }
      else
      {
         /******************/
         /* query keywords */
         /******************/

         cptr = rqptr->rqHeader.QueryStringPtr;
         while (*cptr)
         {
            sys$fao (&NumberFaoDsc, 0, &StringDsc, ++KeyCount);
            memcpy (FieldName, "KEY_", 4);
            strcpy (FieldName+4, String);

            Count = CgiUrlDecodeString (rqptr, cptr,
                                        String, sizeof(String), '+');
            if (Count < 0) return (STS$K_ERROR);
            cptr += Count;
            if (VMSnok (status =
                CgiVariable (rqptr, FieldName, String, VarType)))
               return (status);
         }

         sys$fao (&NumberFaoDsc, 0, &StringDsc, KeyCount);
         if (VMSnok (status =
             CgiVariable (rqptr, "KEY_COUNT", String, VarType)))
            return (status);
      }
   }
   else
   if (!rqptr->rqPathSet.ScriptQueryNone)
   {
      /* no keywords if no query string! */
      if (VMSnok (status =
          CgiVariable (rqptr, "KEY_COUNT", "0", VarType)))
         return (status);
   }

   /*********************/
   /* SET script=params */
   /*********************/

   if (rqptr->rqPathSet.ScriptParamsPtr)
   {
      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_CGI))
         WatchThis (rqptr, FI_LI, WATCH_MOD_CGI, "!&Z",
                    rqptr->rqPathSet.ScriptParamsPtr);

      NameValuePtr = rqptr->rqPathSet.ScriptParamsPtr;
      for (;;)
      {
         status = StringParseNameValue (&NameValuePtr, true,
                                        FieldName, sizeof(FieldName),
                                        String, sizeof(String));
         if (VMSnok (status)) break;
         status = CgiVariable (rqptr, FieldName, String, VarType);
         if (VMSnok (status)) break;
      }
      if (status != SS$_ENDOFFILE) ErrorVmsStatus (rqptr, status, FI_LI);
   }

   /*************/
   /* SET html= */
   /*************/

   if (rqptr->rqPathSet.HtmlBodyTagPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTML_BODYTAG",
                       rqptr->rqPathSet.HtmlBodyTagPtr, VarType)))
         return (status);

   if (rqptr->rqPathSet.HtmlFooterPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTML_FOOTER",
                       rqptr->rqPathSet.HtmlFooterPtr, VarType)))
         return (status);

   if (rqptr->rqPathSet.HtmlFooterTagPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTML_FOOTERTAG",
                       rqptr->rqPathSet.HtmlFooterTagPtr, VarType)))
         return (status);

   if (rqptr->rqPathSet.HtmlHeaderPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTML_HEADER",
                       rqptr->rqPathSet.HtmlHeaderPtr, VarType)))
         return (status);

   if (rqptr->rqPathSet.HtmlHeaderTagPtr)
      if (VMSnok (status =
          CgiVariable (rqptr, "HTML_HEADERTAG",
                       rqptr->rqPathSet.HtmlHeaderTagPtr, VarType)))
         return (status);

   return (CgiVariable (rqptr, NULL, NULL, VarType));
}

/*****************************************************************************/
/*
URL-decode a URL-encoded string!  Return -1 if an error occurs, otherwise
return the number of characters read from the original, encoded string.
*/ 

int CgiUrlDecodeString
(
REQUEST_STRUCT *rqptr,
char *EncodedString,
char *DecodedString,
int SizeofDecodedString,
char Separator
)
{
   char  ch;
   char  *cptr, *sptr, *zptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_CGI))
      WatchThis (rqptr, FI_LI, WATCH_MOD_CGI,
                 "CgiUrlDecodeString() !&Z", EncodedString);

   cptr = EncodedString;
   zptr = (sptr = DecodedString) + SizeofDecodedString;
   while (*cptr && *cptr != Separator && sptr < zptr)
   {
      if (*cptr == '+')
      {
         *sptr++ = ' ';
         cptr++;
      }
      else
      if (*cptr == '%')
      {
         /* an escaped character ("%xx" where xx is a hex number) */
         cptr++;
         ch = 0;
         if (*cptr >= '0' && *cptr <= '9')
            { ch = (*cptr - (int)'0') << 4; cptr++; }
         else
         if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
            { ch = (tolower(*cptr) - (int)'a' + 10) << 4; cptr++; }
         else
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI);
            return (-1);
         }
         if (*cptr >= '0' && *cptr <= '9')
            { ch += (*cptr - (int)'0'); cptr++; }
         else
         if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
            { ch += (tolower(*cptr) - (int)'a' + 10); cptr++; }
         else
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI);
            return (-1);
         }
         if (sptr < zptr) *sptr++ = ch;
      }
      else
         *sptr++ = *cptr++;
   }
   if (sptr >= zptr)
   {
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI);
      return (-1);
   }
   *sptr = '\0';
   if (*cptr) cptr++;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_CGI))
      WatchDataFormatted ("!&Z\n", DecodedString);

   return (cptr - EncodedString);
}

/*****************************************************************************/
/*
Depending on whether the CGI variable is for creation at the DCL CLI or as part
of a CGI variable stream (e.g. CGIplus) create a null-terminated string
containing either commands to create a DCL symbol, or a 'name=value' pair.

DCL symbol creation at the command line is limited by the CLI command line 
length (255 characters for pre 7.2-3, 4095 for post 7.2-1).  Symbol values
however can be up to 1024 (or pragmatically about 1000 characters) for pre
7.3-2 and 8192 (or thereabouts) for post 7.3-1, probably enough for any CGI
variable value.  If a CGI value is too large for for a single command-line
assignment then build it up using multiple assignments, a symbol assignment
kludge!
*/ 

int CgiVariable
(
REQUEST_STRUCT *rqptr,
char *SymbolName,
char *SymbolValue,
int VarType
)
{
   static char  DeleteAllLocalSymbol[] = "DELETE/SYMBOL/LOCAL/ALL";

   int  status,
        CliLineSize,
        CliSymbolSize,
        SymbolCount,
        SymbolValueLength;
   unsigned short  Length;
   char  *cptr, *sptr, *zptr,
         *CgiPlusInCCPtr,
         *CgiVariablePrefixPtr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_CGI))
      WatchThis (rqptr, FI_LI, WATCH_MOD_CGI,
                 "CgiVariable() !AZ !&Z", SymbolName, SymbolValue);

   if (!SymbolName)
   {
      /************************/
      /* end of CGI variables */
      /************************/

      if (rqptr->rqCgi.BufferRemaining < sizeof(short))
         CgiVariableBufferMemory (rqptr, sizeof(short));

      /* zero length record terminates generated CGI variables */
      *(short*)rqptr->rqCgi.BufferCurrentPtr = 0;
      rqptr->rqCgi.BufferRemaining -= sizeof(short);
      rqptr->rqCgi.BufferCurrentPtr += sizeof(short);

      return (SS$_NORMAL);
   }

   if (rqptr->rqPathSet.CgiPrefixPtr)
      CgiVariablePrefixPtr = rqptr->rqPathSet.CgiPrefixPtr;
   else
      CgiVariablePrefixPtr = DclCgiVariablePrefix;

   CgiPlusInCCPtr = rqptr->rqPathSet.CgiPlusInCC;

   if (!SymbolValue) SymbolValue = "";

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
      WatchDataFormatted ("!AZ=!AZ\n", SymbolName, SymbolValue);

   if (VarType == CGI_VARIABLE_STREAM)
   {
      /******************************/
      /* variables in record stream */
      /******************************/

      if (rqptr->rqCgi.BufferRemaining <= DclCgiPlusInSize + sizeof(short))
         CgiVariableBufferMemory (rqptr, DclCgiPlusInSize*2);

      zptr = (sptr = rqptr->rqCgi.BufferCurrentPtr + sizeof(short)) +
             (rqptr->rqCgi.BufferRemaining - sizeof(short));
      for (cptr = CgiVariablePrefixPtr; *cptr; *sptr++ = *cptr++);
      for (cptr = SymbolName; *cptr && sptr < zptr; *sptr++ = *cptr++);
      if (sptr < zptr) *sptr++ = '=';
      for (cptr = SymbolValue; *cptr && sptr < zptr; *sptr++ = *cptr++);
      if (sptr < zptr && CgiPlusInCCPtr[0]) *sptr++ = CgiPlusInCCPtr[0];
      if (sptr < zptr && CgiPlusInCCPtr[1]) *sptr++ = CgiPlusInCCPtr[1];
      if (sptr < zptr) *sptr++ = '\0';
      if (sptr < zptr)
      {
         Length = sptr - (char*)rqptr->rqCgi.BufferCurrentPtr - sizeof(short);
         *(short*)rqptr->rqCgi.BufferCurrentPtr = Length;
         rqptr->rqCgi.BufferRemaining -= Length + sizeof(short);
         rqptr->rqCgi.BufferCurrentPtr += Length + sizeof(short);
         return (SS$_NORMAL);
      }
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI);
      return (STS$K_ERROR);
   }

   /********************************/
   /* variables created at DCL CLI */
   /********************************/

   /* the 10 or 18 is the overhead in any symbol assignment kludge needed */
   if (SysInfo.VersionInteger >= 732)
   {
      /* take advantage of EDCL - ultimately limited by 'DclSysCommandSize'! */
      CliLineSize = 4095;
      CliSymbolSize = 8192 - 14;
   }
   else
   {
      /* same old command-line length we've grown up knowing and hating */
      CliLineSize = 255;
      CliSymbolSize = 1024 - 18;
   }

   if (rqptr->rqCgi.BufferRemaining <= CliLineSize + sizeof(short))
      CgiVariableBufferMemory (rqptr, DclSysCommandSize);

   zptr = (sptr = rqptr->rqCgi.BufferCurrentPtr + sizeof(short)) + CliLineSize;
   for (cptr = CgiVariablePrefixPtr; *cptr; *sptr++ = *cptr++);
   for (cptr = SymbolName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = '=';
   if (sptr < zptr) *sptr++ = '=';
   if (sptr < zptr) *sptr++ = '\"';
   cptr = SymbolValue;
   while (*cptr && sptr < zptr)
   {
      if (*cptr == '\"')
      {
         /* escape double quotes, by doubling up */
         if (sptr+2 < zptr)
         {
            cptr++;
            *sptr++ = '\"';
            *sptr++ = '\"';
         }
         else
            zptr = sptr;
         continue;
      }

      if (*cptr == '\'')
      {
         /* escape single quotes by concatenation */
         if (sptr+7 < zptr)
         {
            cptr++;
            memcpy (sptr, "\"+\"\'\"+\"", 7);
            sptr += 7;
         }
         else
            sptr = zptr;
         continue;
      }

      if (sptr < zptr) *sptr++ = *cptr++;
   }
   if (sptr < zptr) *sptr++ = '\"';
   if (sptr < zptr)
   {
      *sptr++ = '\0';
      Length = sptr - (char*)rqptr->rqCgi.BufferCurrentPtr - sizeof(short);
      *(short*)rqptr->rqCgi.BufferCurrentPtr = Length;
      rqptr->rqCgi.BufferRemaining -= Length + sizeof(short);
      rqptr->rqCgi.BufferCurrentPtr += Length + sizeof(short);
      return (SS$_NORMAL);
   }

   /* loop assigning maximum amount allowed by DCL until all assigned */
   SymbolCount = 0;
   cptr = SymbolValue;
   while (*cptr)
   {
      if (rqptr->rqCgi.BufferRemaining <= CliLineSize + sizeof(short))
         CgiVariableBufferMemory (rqptr, DclSysCommandSize);

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

      /* set this to DCL line length minus one for the terminating quote */
      zptr = (sptr = rqptr->rqCgi.BufferCurrentPtr + sizeof(short)) +
             CliLineSize - 1;
      switch (++SymbolCount)
      {
         case 1 : memcpy (sptr, "A=\"", 3); break;
         case 2 : memcpy (sptr, "B=\"", 3); break;
         case 3 : memcpy (sptr, "C=\"", 3); break;
         case 4 : memcpy (sptr, "D=\"", 3); break;
         case 5 : memcpy (sptr, "E=\"", 3); break;
         case 6 : memcpy (sptr, "F=\"", 3); break;
         default : memcpy (sptr, "X=\"", 3); break;
      }
      sptr += 3;
      while (*cptr && sptr < zptr)
      {
         if (*cptr == '\"')
         {
            /* escape double quotes, by doubling up */
            if (sptr+2 < zptr)
            {
               cptr++;
               *sptr++ = '\"';
               *sptr++ = '\"';
            }
            else
               zptr = sptr;
            continue;
         }

         if (*cptr == '\'')
         {
            /* escape single quotes by concatenation */
            if (sptr+7 < zptr)
            {
               cptr++;
               memcpy (sptr, "\"+\"\'\"+\"", 7);
               sptr += 7;
            }
            else
               sptr = zptr;
            continue;
         }

         if (sptr < zptr) *sptr++ = *cptr++;
      }

      *sptr++ = '\"';
      *sptr++ = '\0';
      Length = sptr - (char*)rqptr->rqCgi.BufferCurrentPtr - sizeof(short);
      *(short*)rqptr->rqCgi.BufferCurrentPtr = Length;
      rqptr->rqCgi.BufferRemaining -= Length + sizeof(short);
      rqptr->rqCgi.BufferCurrentPtr += Length + sizeof(short);
   }
   SymbolValueLength = cptr - SymbolValue;

   /* assign the temporary symbol value(s) to the CGI symbol */
   if (rqptr->rqCgi.BufferRemaining <= CliLineSize + sizeof(short))
      CgiVariableBufferMemory (rqptr, DclSysCommandSize);
   sptr = rqptr->rqCgi.BufferCurrentPtr + sizeof(short);
   for (cptr = CgiVariablePrefixPtr; *cptr; *sptr++ = *cptr++);
   for (cptr = SymbolName; *cptr; *sptr++ = *cptr++);
   switch (SymbolCount)
   {
      case 1 : memcpy (sptr, "==A", 4); sptr += 4; break;
      case 2 : memcpy (sptr, "==A+B", 6); sptr += 6; break;
      case 3 : memcpy (sptr, "==A+B+C", 8); sptr += 8; break;
      case 4 : memcpy (sptr, "==A+B+C+D", 10); sptr += 10; break;
      case 5 : memcpy (sptr, "==A+B+C+D+E", 12); sptr += 12; break;
      case 6 : memcpy (sptr, "==A+B+C+D+E+F", 14); sptr += 14; break;
      default : break;
   }
   Length = sptr - (char*)rqptr->rqCgi.BufferCurrentPtr - sizeof(short);

   if (SymbolCount > 6 ||
       Length + SymbolValueLength > CliSymbolSize)
   {
      /* cannot create a symbol this size */
      rqptr->rqResponse.ErrorTextPtr = SymbolName;
      rqptr->rqResponse.ErrorOtherTextPtr = "DCL symbol assignment too large!";
      ErrorVmsStatus (rqptr, CLI$_BUFOVF, FI_LI);
      return (STS$K_ERROR);
   }

   *(short*)rqptr->rqCgi.BufferCurrentPtr = Length;
   rqptr->rqCgi.BufferRemaining -= Length + sizeof(short);
   rqptr->rqCgi.BufferCurrentPtr += Length + sizeof(short);

   /* not really necessary, but let's be tidy */
   if (rqptr->rqCgi.BufferRemaining <=
       sizeof(DeleteAllLocalSymbol) + sizeof(short))
      CgiVariableBufferMemory (rqptr, DclSysCommandSize);
   memcpy (rqptr->rqCgi.BufferCurrentPtr + sizeof(short),
           DeleteAllLocalSymbol, sizeof(DeleteAllLocalSymbol));
   *(short*)rqptr->rqCgi.BufferCurrentPtr = sizeof(DeleteAllLocalSymbol);
   rqptr->rqCgi.BufferRemaining -= sizeof(DeleteAllLocalSymbol) + sizeof(short);
   rqptr->rqCgi.BufferCurrentPtr += sizeof(DeleteAllLocalSymbol) + sizeof(short);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Allocate, or reallocate on subsequent calls, memory for storing the CGI
variables in.  Adjusts the CGI variable buffer pointers, lengths, etc,
appropriately.
*/ 

CgiVariableBufferMemory
(
REQUEST_STRUCT *rqptr,
int BufferChunk
)
{
   int  CurrentLength,
        CurrentOffset;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_CGI))
      WatchThis (rqptr, FI_LI, WATCH_MOD_CGI,
                 "CgiVariableBufferMemory() !UL !UL !UL",
                 BufferChunk, rqptr->rqCgi.BufferLength,
                 rqptr->rqCgi.BufferCurrentPtr - rqptr->rqCgi.BufferPtr);

   if (!rqptr->rqCgi.BufferLength)
   {
      /* initialize CGI variable buffer */
      rqptr->rqCgi.BufferLength = rqptr->rqCgi.BufferRemaining = BufferChunk;
      rqptr->rqCgi.BufferPtr = rqptr->rqCgi.BufferCurrentPtr =
         VmGetHeap (rqptr, BufferChunk);
      return;
   }

   CurrentLength = rqptr->rqCgi.BufferLength;
   CurrentOffset = rqptr->rqCgi.BufferCurrentPtr - rqptr->rqCgi.BufferPtr;

   rqptr->rqCgi.BufferPtr =
      VmReallocHeap (rqptr, rqptr->rqCgi.BufferPtr,
                     CurrentLength + BufferChunk, FI_LI);

   rqptr->rqCgi.BufferLength = CurrentLength + BufferChunk;
   rqptr->rqCgi.BufferRemaining += BufferChunk;
   rqptr->rqCgi.BufferCurrentPtr = rqptr->rqCgi.BufferPtr + CurrentOffset;
}

/*****************************************************************************/
/*
Process output from a CGI script (or even plain DCL processing).  For CGI
scripts check the first output from each for CGI-relevant HTTP header lines. 
For all output check that carriage-control is as required.  Returns one of two
things.   Either a constant value ("DCL_OUTPUT_...") that indicates some event
in output processing, or a value indicating quantity of output still available
in the stream.
*/ 

int CgiOutput
(
REQUEST_STRUCT *rqptr,
char *OutputPtr,
int OutputCount
)
{
   BOOL  EndOfHeader,
         CgiCompliant;
   int  cnt, status,
        AbsorbCount,
        HeaderLength,
        HeaderRemaining,
        LineCount;
   char  *cptr, *lptr, *sptr, *zptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_CGI))
   {
      WatchThis (rqptr, FI_LI, WATCH_MOD_CGI,
                 "CgiOutput() !UL !&X", OutputCount, OutputPtr);
      WatchDataDump (OutputPtr, OutputCount);
   }

   cptr = OutputPtr;
   cnt = OutputCount;

   if (!cptr)
   {
      /********************/
      /* reset CGI output */
      /********************/

      rqptr->rqCgi.CalloutInProgress =
         rqptr->rqCgi.ContentEncoding =
         rqptr->rqCgi.ContentTypeText =
         rqptr->rqCgi.ProcessingBody = false;

      rqptr->rqCgi.CalloutOutputCount =
         rqptr->rqCgi.OutputMode =
         rqptr->rqCgi.RecordCount =
         rqptr->rqCgi.XVMSRecordMode = 0;

      if (rqptr->rqCgi.HeaderPtr)
         VmFreeFromHeap (rqptr, rqptr->rqCgi.HeaderPtr, FI_LI);

      rqptr->rqCgi.HeaderLength = rqptr->rqCgi.HeaderLineCount = 0;
      rqptr->rqCgi.HeaderPtr = VmGetHeap (rqptr, DclCgiHeaderSize);

      return (0);
   }

   if (rqptr->rqCgi.EofLength)
   {
      if (cnt >= rqptr->rqCgi.EofLength &&
          cnt <= rqptr->rqCgi.EofLength+2 &&
          !memcmp (cptr, rqptr->rqCgi.EofPtr, rqptr->rqCgi.EofLength))
      {
         /******************************/
         /* end of output from script! */
         /******************************/

         rqptr->rqCgi.AbsorbHeader =
            rqptr->rqCgi.BufferRecords = false;

         if (!rqptr->rqCgi.IsCliDcl &&
             !rqptr->rqCgi.AgentScript &&
             !rqptr->rqCgi.ProcessingBody &&
             Config.cfScript.CgiStrictOutput)
         {
            /* hmmmm, didn't receive any output */
            if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
               WatchThis (rqptr, FI_LI, WATCH_CGI, ErrorCgiNotStrict);
            return (CGI_OUTPUT_NOT_STRICT);
         }

         rqptr->rqCgi.AgentScript = false;

         if (rqptr->rqCache.LoadFromCgi)
         {
            rqptr->rqCache.LoadStatus = SS$_ENDOFFILE;
            CacheLoadEnd (rqptr);
         }

         return (CGI_OUTPUT_END);
      }
   }

   if (rqptr->rqCgi.CalloutInProgress)
   {
      /********************************/
      /* output escape is in progress */
      /********************************/

      if (cnt >= rqptr->rqCgi.EotLength &&
          cnt <= rqptr->rqCgi.EotLength+2 &&
          !memcmp (cptr, rqptr->rqCgi.EotPtr, rqptr->rqCgi.EotLength))
      {
         /******************************/
         /* end of escape from script! */
         /******************************/

         return (CGI_OUTPUT_ESCAPE_END);
      }

      return (CGI_OUTPUT_ESCAPE);
   }

   if (rqptr->rqCgi.EscLength)
   {
      if (cnt >= rqptr->rqCgi.EscLength &&
          cnt <= rqptr->rqCgi.EscLength+2 &&
          !memcmp (cptr, rqptr->rqCgi.EscPtr, rqptr->rqCgi.EscLength))
      {
         /******************************/
         /* escape from script output! */
         /******************************/

         return (CGI_OUTPUT_ESCAPE_BEGIN);
      }
   }

   rqptr->rqCgi.RecordCount++;

   if (!rqptr->rqCgi.ProcessingBody && !rqptr->rqCgi.IsCliDcl)
   {
      /******************************/
      /* processing response header */
      /******************************/

      /* buffer part of, one, or multiple lines of script (header) output */
      LineCount = 0;
      EndOfHeader = false;
      sptr = rqptr->rqCgi.HeaderPtr + rqptr->rqCgi.HeaderLength;
      zptr = sptr + DclCgiHeaderSize;
      while (*cptr && sptr < zptr)
      {
         if (*cptr != '\n' &&
             *(unsigned short*)cptr != '\r\n' &&
             *(unsigned short*)cptr != '\r\0')
         {
            *sptr++ = *cptr++;
            continue;
         }
         if (*cptr == '\n')
         {
            /* record terminated by a trailing newline */
            if (sptr > rqptr->rqCgi.HeaderPtr && *(sptr-1) == '\n')
            {
               cptr++;
               EndOfHeader = true;
               break;
            }
            *sptr++ = '\r';
            if (sptr < zptr) *sptr++ = *cptr++;
            LineCount++;
            rqptr->rqCgi.HeaderCarRet = false;
            continue;
         }
         else
         if (*(unsigned short*)cptr == '\r\n')
         {
            /* record 'correctly' terminated */
            if (sptr > rqptr->rqCgi.HeaderPtr && *(sptr-1) == '\n')
            {
               cptr++;
               cptr++;
               EndOfHeader = true;
               break;
            }
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = *cptr++;
            LineCount++;
            rqptr->rqCgi.HeaderCarRet = false;
            continue;
         }
         else
         {
            /* single record terminated by a trailing carriage-return */
            if (sptr > rqptr->rqCgi.HeaderPtr && *(sptr-1) == '\n')
            {
               cptr++;
               EndOfHeader = true;
               break;
            }
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '\n';
            LineCount++;
            rqptr->rqCgi.HeaderCarRet = false;
            continue;
         }
      }

      /* an empty record can only mean one thing, a '\n' in record-mode! */
      if (!OutputCount || rqptr->rqCgi.HeaderCarRet)
      {
         /* header output contains no explicit carriage-control */
         LineCount++;
         if (sptr > rqptr->rqCgi.HeaderPtr && *(sptr-1) == '\n')
            EndOfHeader = true;
         else
         {
            if (sptr < zptr) *sptr++ = '\r';
            if (sptr < zptr) *sptr++ = '\n';
         }
      }

      if (sptr >= zptr)
      {
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
            WatchThis (rqptr, FI_LI, WATCH_CGI, ErrorCgiNotStrict);
         return (CGI_OUTPUT_NOT_STRICT);
      }
      *sptr = '\0';
      HeaderLength = sptr - rqptr->rqCgi.HeaderPtr;

      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_CGI))
         WatchDataDump(rqptr->rqCgi.HeaderPtr, HeaderLength);
   }

   if (!rqptr->rqCgi.ProcessingBody && !rqptr->rqCgi.IsCliDcl)
   {
      if (!rqptr->rqCgi.HeaderLineCount)
      {
         /* this is/might be the first line, try to establish what it is */
         lptr = rqptr->rqCgi.HeaderPtr;
         if (*lptr == 'H' && !memcmp (lptr, "HTTP/1.", 7))
         {
            /**************************/
            /* full HTTP stream (NPH) */
            /**************************/

            if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
               WatchThis (rqptr, FI_LI, WATCH_CGI, "RESPONSE NPH mode");

            if (!rqptr->rqCgi.AbsorbHeader)
            {
               /* get the response status code for logging purposes */
               lptr += 7;
               while (*lptr && !ISLWS(*lptr)) lptr++;
               while (*lptr && !isdigit(*lptr)) lptr++;
               if (isdigit(*lptr)) rqptr->rqResponse.HttpStatus = atoi(lptr);

               if (rqptr->rqPathSet.CacheNPH &&
                   !rqptr->rqCache.LoadCheck)
               {
                  /* script output to be cached */
                  rqptr->rqCache.LoadFromCgi = CacheLoadBegin (rqptr, 0, NULL);
                  if (rqptr->rqCache.LoadFromCgi)
                     CacheLoadData (rqptr, OutputPtr, OutputCount);
               } 

               rqptr->rqCgi.ProcessingBody = true;
               return (OutputCount);
            }
         }
         else
         {
            /************************************/
            /* check for CGI-like response line */
            /************************************/

            /* ensure it looks something like "blah-blah:" */
            while (*lptr && *lptr != ':' && !ISLWS(*lptr)) lptr++;
            if (*lptr != ':')
            {
               /***********************/
               /* non-CGI/NPH output? */
               /***********************/

               if (rqptr->rqCgi.AgentScript)
               {
                  /* not allowed for an authorization agent */
                  AuthAgentCalloutResponseError (rqptr);
                  return (CGI_OUTPUT_NOT_STRICT);
               }

               if (Config.cfScript.CgiStrictOutput)
               {
                  if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
                     WatchThis (rqptr, FI_LI, WATCH_CGI, ErrorCgiNotStrict);
                  return (CGI_OUTPUT_NOT_STRICT);
               }

               lptr = rqptr->rqCgi.HeaderPtr;
               if (*lptr == '%')
               {
                  /* check for something like "%DCL-E-OPENIN, blah" */
                  for (cptr = lptr+1; *cptr && isalpha(*cptr); cptr++);
                  if (cptr[0] == '-' &&
                      (cptr[1] == 'F' || cptr[1] == 'E' || cptr[1] == 'W') &&
                      cptr[2] == '-')
                  {
                     cptr += 3;
                     while (*cptr && isalpha(*cptr)) cptr++;
                     if (*cptr == ',')
                     {
                        /* yep, looks like it */
                        if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
                           WatchThis (rqptr, FI_LI, WATCH_CGI,
                                      ErrorCgiCli, lptr);
                        rqptr->rqResponse.HttpStatus = 502;
                        ErrorGeneral (rqptr, lptr, FI_LI);
                        return (CGI_OUTPUT_NOT_STRICT);
                     }
                  }
               }

               ResponseHeader (rqptr, 200, "text/plain", -1, NULL, NULL);
               rqptr->rqCgi.ContentTypeText =
                  rqptr->rqCgi.ProcessingBody = true;
               rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD;
            }
         }

         if (!LineCount && rqptr->rqCgi.RecordCount == 1)
         {
            /* no carriage-control has been detected in first record */
            rqptr->rqCgi.HeaderCarRet = true;
            if (sptr < zptr) *sptr++ = '\r';
            if (sptr < zptr) *sptr++ = '\n';
            LineCount++;
         }
      }
   }

   if (!rqptr->rqCgi.ProcessingBody && !rqptr->rqCgi.IsCliDcl)
   {
      /***************************/
      /* (still) response header */
      /***************************/

      rqptr->rqCgi.HeaderLineCount += LineCount;
      rqptr->rqCgi.HeaderLength = HeaderLength = sptr - rqptr->rqCgi.HeaderPtr;

      /* eliminate header line(s) */
      memmove (OutputPtr, cptr, OutputCount - (cptr - OutputPtr));

      /* adjust for what we have buffered/eliminated of the header */
      OutputCount -= cptr - OutputPtr;

      if (!(rqptr->rqCgi.ProcessingBody = EndOfHeader)) return (OutputCount);

      /***************************/
      /* end of response header! */
      /***************************/

      /* look at each line in the script-generated CGI response header */
      CgiCompliant = false;
      LineCount = 0;
      HeaderRemaining = rqptr->rqCgi.HeaderLength;
      cptr = rqptr->rqCgi.HeaderPtr;
      while (*cptr)
      { 
         /**************************/
         /* process header line(s) */
         /**************************/

         for (zptr = lptr = cptr;
              *zptr && *zptr != '\r' && *(unsigned short*)zptr != '\r\n';
              zptr++);
         LineCount++;

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
            WatchThis (rqptr, FI_LI, WATCH_CGI,
                       "RESPONSE header line !UL \'!#AZ\' !UL bytes",
                       LineCount, zptr-lptr, lptr, zptr-lptr);

         /* continue on to the beginning of (any) next line */
         if (*zptr == '\r') zptr++;
         if (*zptr == '\n') zptr++;
         HeaderRemaining -= zptr - lptr;

         if (rqptr->rqCgi.AbsorbHeader &&
             *cptr == 'H' && !memcmp (cptr, "HTTP/1.", 7))
         {
            /*********************/
            /* absorb NPH header */
            /*********************/

            /* well, sort of! */
            CgiCompliant = true;
         }
         else
         if (toupper(*cptr) == 'C' && strsame (cptr, "Content-Type:", 13))
         {
            /****************/
            /* content-type */
            /****************/

            CgiCompliant = true;
            cptr += 13;
            while (ISLWS(*cptr) && NOTEOL(*cptr)) cptr++;
            sptr = cptr;
            while (NOTEOL(*cptr)) cptr++;
            if (!rqptr->rqCgi.AbsorbHeader)
            {
               rqptr->rqResponse.ContentTypePtr =
                  VmGetHeap (rqptr, cptr-sptr+1);
               memcpy (rqptr->rqResponse.ContentTypePtr, sptr, cptr-sptr);
               rqptr->rqResponse.ContentTypePtr[cptr-sptr] = '\0';
               rqptr->rqHeader.ContentTypePtr =
                  rqptr->rqResponse.ContentTypePtr;
            }

            if (strsame (sptr, "text/", 5))
               rqptr->rqCgi.ContentTypeText = true;
            else
               rqptr->rqCgi.ContentTypeText = false;

            /* eliminate this header line */
            rqptr->rqCgi.HeaderLength -= zptr - lptr;
            memmove (lptr, zptr, HeaderRemaining+1);
            zptr -= zptr - lptr;
         }
         else
         if (toupper(*cptr) == 'C' && strsame (cptr, "Content-Length:", 15))
         {
            /******************/
            /* content-length */
            /******************/

            cptr += 15;
            while (ISLWS(*cptr)) cptr++;
            if (!rqptr->rqCgi.AbsorbHeader)
               rqptr->rqResponse.ContentLength = atoi(cptr);
            while (NOTEOL(*cptr)) cptr++;
         }
         else
         if (toupper(*cptr) == 'C' && strsame (cptr, "Content-Encoding:", 17))
         {
            /********************/
            /* content-encoding */
            /********************/

            /* take note the response body is encoded in some way */
            rqptr->rqCgi.ContentEncoding = true;
         }
         else
         if (toupper(*cptr) == 'S' && strsame (cptr, "Status:", 7))
         {
            /**********/
            /* status */
            /**********/

            CgiCompliant = true;
            cptr += 7;
            /* create HTTP header status line using supplied status */
            while (*cptr++ && !isdigit(*cptr) && *cptr != '\n') cptr++;
            if (!rqptr->rqCgi.AbsorbHeader)
            {
               /* get response status code for header and logging purposes */
               if (isdigit(*cptr)) rqptr->rqResponse.HttpStatus = atoi(cptr);
            }

            /* eliminate this header line */
            rqptr->rqCgi.HeaderLength -= zptr - lptr;
            memmove (lptr, zptr, HeaderRemaining+1);
            zptr -= zptr - lptr;
         }
         else
         if (toupper(*cptr) == 'S' && strsame (cptr, "Script-Control:", 15))
         {
            /*****************************************/
            /* script control (Apache Group CGI/1.2) */
            /*****************************************/

            cptr += 15;
            while (*cptr && NOTEOL(*cptr))
            {
               while (ISLWS(*cptr) || *cptr == ',' || *cptr == ';') cptr++;
               if (EOL(*cptr)) break;
               cptr += CgiScriptControlField (rqptr, cptr);
            }

            /* eliminate this header line */
            rqptr->rqCgi.HeaderLength -= zptr - lptr;
            memmove (lptr, zptr, HeaderRemaining+1);
            zptr -= zptr - lptr;
         }
         else
         if (toupper(*cptr) == 'X' && strsame (cptr, "X-vms-record-mode:", 18))
         {
            /*****************************/
            /* VMS Apache record control */
            /*****************************/

            /*
               VMS Apache (in pre-CSWS days) uses an eXtension header field
               so that the script can convey to the server whether the output
               should be processed as a binary stream or as records.
               (Keep this in post-CSWS days for backward compatibility.)
            */

            cptr += 18;
            while (ISLWS(*cptr) && NOTEOL(*cptr)) cptr++;
            /* this value should either 0 or 1 ("no" or "yes") plus 1 */
            rqptr->rqCgi.XVMSRecordMode = atoi(cptr)+1;

            /* eliminate this header line */
            rqptr->rqCgi.HeaderLength -= zptr - lptr;
            memmove (lptr, zptr, HeaderRemaining+1);
            zptr -= zptr - lptr;
         }
         else
         if (toupper(*cptr) == 'L' && strsame (cptr, "Location:", 9))
         {
            /************/
            /* location */
            /************/

            CgiCompliant = true;
            cptr += 9;
            while (ISLWS(*cptr) && NOTEOL(*cptr)) cptr++;
            sptr = cptr;
            while (NOTEOL(*cptr)) cptr++;
            if (!rqptr->rqCgi.AbsorbHeader)
            {
               rqptr->rqResponse.LocationPtr = VmGetHeap (rqptr, cptr-sptr+1);
               memcpy (rqptr->rqResponse.LocationPtr, sptr, cptr-sptr);
               rqptr->rqResponse.LocationPtr[cptr-sptr] = '\0';
            }

            /* eliminate this header line */
            rqptr->rqCgi.HeaderLength -= zptr - lptr;
            memmove (lptr, zptr, HeaderRemaining+1);
            zptr -= zptr - lptr;
         }
         else
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
            WatchThis (rqptr, FI_LI, WATCH_CGI,
                       "UNRECOGNISED response field");
         
         /* bump to the start of the next (if any) line */
         if (!*(cptr = zptr)) break;
      }

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
         if (rqptr->rqCgi.AbsorbHeader)
            WatchThis (rqptr, FI_LI, WATCH_CGI, "RESPONSE header absorbed");

      /****************************/
      /* generate response header */
      /****************************/

      if (!CgiCompliant) return (CGI_OUTPUT_NOT_STRICT);

      if (!rqptr->rqCgi.OutputMode)
      {
         if (rqptr->rqCgi.XVMSRecordMode)
         {
            if (rqptr->rqCgi.XVMSRecordMode-1)
               rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD;
            else
               rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_STREAM;
         }
         else
         /* always enforce stream mode if it's encoded in some way! */
         if (rqptr->rqCgi.ContentEncoding)
            rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_STREAM;
         else
         if (rqptr->rqCgi.ContentTypeText)
            rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD;
         else
            rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_STREAM;
      }

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
      {
         switch (rqptr->rqCgi.OutputMode)
         {
            case CGI_OUTPUT_MODE_STREAM : cptr = "stream"; break;
            case CGI_OUTPUT_MODE_RECORD : cptr = "record"; break;
            case CGI_OUTPUT_MODE_CRLF : cptr = "crlf"; break;
            default : cptr = "?";
         }
         WatchThis (rqptr, FI_LI, WATCH_CGI, "RESPONSE !AZ mode", cptr);
      }

      if (rqptr->rqCgi.ScriptControlErrorTextPtr)
      {
         /* script is requesting the server generate an error message */
         sptr = rqptr->rqCgi.ScriptControlErrorModulePtr;
         cnt = rqptr->rqCgi.ScriptControlErrorLine;
         if (status = rqptr->rqCgi.ScriptControlErrorVmsStatus)
         {
            rqptr->rqResponse.ErrorTextPtr =
               rqptr->rqCgi.ScriptControlErrorTextPtr;
            rqptr->rqResponse.ErrorOtherTextPtr =
               rqptr->rqCgi.ScriptControlErrorVmsTextPtr;
            if (sptr)
               ErrorVmsStatus (rqptr, status, sptr, cnt);
            else
               ErrorVmsStatus (rqptr, status, FI_LI);
         }   
         else
         {
            cptr = rqptr->rqCgi.ScriptControlErrorTextPtr; 
            if (sptr)
               ErrorGeneral (rqptr, cptr, sptr, cnt);
            else
               ErrorGeneral (rqptr, cptr, FI_LI);
         }
      }
      else
      if (!rqptr->rqCgi.AbsorbHeader)
      {
         if (rqptr->rqPathSet.CacheCGI &&
             !rqptr->rqCache.LoadCheck)
         {
            /* script output to be cached */
            rqptr->rqCache.LoadFromCgi =
               CacheLoadBegin (rqptr, rqptr->rqResponse.ContentLength,
                                      rqptr->rqResponse.ContentTypePtr);
            if (rqptr->rqCache.LoadFromCgi &&
                rqptr->rqCgi.HeaderLength)
            {
               /* plus one to include the terminating null */
               CacheLoadData (rqptr, rqptr->rqCgi.HeaderPtr,
                                     rqptr->rqCgi.HeaderLength+1);
            }
         } 

         ResponseHeader (rqptr, rqptr->rqResponse.HttpStatus,
                         rqptr->rqHeader.ContentTypePtr, -1, NULL,
                         rqptr->rqCgi.HeaderPtr);
      }

      if (!OutputCount) return (OutputCount);
   }

   /*************************/
   /* HTTP carriage-control */
   /*************************/

   if (rqptr->rqCgi.FilterStream)
   {
      /* remove all non-printable characters */
      for (zptr=(cptr=OutputPtr)+OutputCount;
           (isprint(*cptr) || isspace(*cptr)) && cptr < zptr;
           cptr++);
      if (cptr < zptr)
      {
         /* a non-printable was detected, now eliminate it and any following */
         sptr = cptr;
         while (cptr < zptr)
         {
            while (!(isprint(*cptr) || isspace(*cptr)) && cptr < zptr) cptr++;
            while ((isprint(*cptr) || isspace(*cptr)) && cptr < zptr)
               *sptr++ = *cptr++;
         }
         *sptr = '\0';
         OutputCount = sptr - OutputPtr;
      }
   }

   if (rqptr->rqCgi.OutputMode == CGI_OUTPUT_MODE_RECORD ||
       rqptr->rqCgi.IsCliDcl)
   {
      if (!OutputCount || OutputPtr[OutputCount-1] != '\n' )
      {
         OutputPtr[OutputCount++] = '\n';
         OutputPtr[OutputCount] = '\0';
         if (rqptr->FileContentPtr)
         {
            CgiOutputFile (rqptr, OutputPtr, OutputCount);
            return (0);
         }
         if (rqptr->rqCache.LoadFromCgi)
            CacheLoadData (rqptr, OutputPtr, OutputCount);
         return (OutputCount);
      }
   }
   else
   if (rqptr->rqCgi.OutputMode == CGI_OUTPUT_MODE_CRLF)
   {
      if (!OutputCount ||
          OutputPtr[OutputCount-1] != '\n' ||
          (OutputCount >= 2 && OutputPtr[OutputCount-2] != '\r'))
      {
         /* empty record, or no trailing <LF>, or no trailing <CR><LF> */
         if (OutputPtr[OutputCount-1] == '\n') OutputCount--;
         OutputPtr[OutputCount++] = '\r';
         OutputPtr[OutputCount++] = '\n';
         OutputPtr[OutputCount] = '\0';
         if (rqptr->FileContentPtr)
         {
            CgiOutputFile (rqptr, OutputPtr, OutputCount);
            return (0);
         }
         if (rqptr->rqCache.LoadFromCgi)
            CacheLoadData (rqptr, OutputPtr, OutputCount);
         return (OutputCount);
      }
   }
   else
   if (!OutputCount)
   {
      /* stream-mode with zero bytes must mean suppressed carriage-control */
      OutputPtr[OutputCount++] = '\n';
      OutputPtr[OutputCount] = '\0';
      if (rqptr->FileContentPtr)
      {
         CgiOutputFile (rqptr, OutputPtr, OutputCount);
         return (0);
      }
      if (rqptr->rqCache.LoadFromCgi)
         CacheLoadData (rqptr, OutputPtr, OutputCount);
      return (OutputCount);
   }

   if (rqptr->FileContentPtr)
   {
      CgiOutputFile (rqptr, OutputPtr, OutputCount);
      return (0);
   }
   if (rqptr->rqCache.LoadFromCgi)
      CacheLoadData (rqptr, OutputPtr, OutputCount);
   return (OutputCount);
}

/*****************************************************************************/
/*
Process a CGI/1.2 "Script-Control:" response header field.  Most of these are
WASD extensions.  Returns the number of characters parsed from the line.
*/ 

int CgiScriptControlField
(
REQUEST_STRUCT *rqptr,
char *ControlString
)
{                                           
   int  Number;
   char  *cptr, *sptr, *zptr;
   char  Scratch [1024];

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

   if (WATCH_MODULE(WATCH_MOD_CGI))
   {
      for (cptr = ControlString; *cptr && NOTEOL(*cptr); cptr++);
      if (EOL(*cptr)) cptr--;
      WatchThis (rqptr, FI_LI, WATCH_MOD_CGI,
                 "CgiScriptControlField() !#AZ",
                 cptr-ControlString+1, ControlString);
   }

   cptr = ControlString;

   /* the one and only "official" field of CGI/1.2 :^) */
   if (strsame (cptr, "no-abort", 8))
   {
      HttpdTimerSet (rqptr, TIMER_OUTPUT, -1);
      HttpdTimerSet (rqptr, TIMER_NOPROGRESS, -1);
      cptr += 8;
      while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
      return (cptr - ControlString);
   }

   /**************************/
   /* carriage-control modes */
   /**************************/

   if (strsame (cptr, "X-crlf-mode", 11))
   {
      rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_CRLF;
      cptr += 11;
      while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
      return (cptr - ControlString);
   }
   if (strsame (cptr, "X-record-mode", 13))
   {
      rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD;
      cptr += 13;
      while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
      return (cptr - ControlString);
   }
   if (strsame (cptr, "X-stream-mode", 13))
   {
      rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_STREAM;
      cptr += 13;
      while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
      return (cptr - ControlString);
   }

   /*******************/
   /* stream handling */
   /*******************/

   if (strsame (cptr, "X-buffer-records", 16))
   {
      rqptr->rqCgi.BufferRecords = true;
      cptr += 16;
      while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
      return (cptr - ControlString);
   }
   if (strsame (cptr, "X-filter-stream", 15))
   {
      rqptr->rqCgi.FilterStream = true;
      cptr += 15;
      while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
      return (cptr - ControlString);
   }

   /************************/
   /* server cache control */
   /************************/

   if (strsame (cptr, "X-cache-", 8))
   {
      if (strsame (cptr+8, "max=", 4))
      {
         cptr += 12;
         if (strsame (cptr, "none", 4))
            Number = 0;
         else
            Number = atoi(cptr);
         rqptr->rqCgi.ScriptControlCacheMaxKBytes = Number;
         while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
         return (cptr - ControlString);
      }
      if (strsame (cptr+8, "expires=", 8))
      {
         cptr += 16;
         if (strsame (cptr, "day", 3))
            Number = CACHE_EXPIRES_DAY;
         else
         if (strsame (cptr, "hour", 4))
            Number = CACHE_EXPIRES_HOUR;
         else
         if (strsame (cptr, "minute", 6))
            Number = CACHE_EXPIRES_MINUTE;
         else
         if (*cptr == '0')
            Number = CACHE_EXPIRES_NONE;
         else
         {
            /* if it's not a 'hh:mm:ss' format then it's in minutes */
            Number = MetaConSetSeconds (NULL, cptr, 60);
            if (Number < 0) Number = 0;
         }
         rqptr->rqCgi.ScriptControlCacheExpiresAfter = Number;
         while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
         return (cptr - ControlString);
      }
   }

   /**********/
   /* timers */
   /**********/

   if (strsame (cptr, "X-timeout-", 10))
   {
      if (strsame (cptr+10, "output=", 7))
      {
         cptr += 17;
         if (strsame (cptr, "none", 4))
            Number = -1;
         else
            Number = atoi(cptr);
         HttpdTimerSet (rqptr, TIMER_OUTPUT, Number);
         while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
         return (cptr - ControlString);
      }
      if (strsame (cptr+10, "noprogress=", 11))
      {
         cptr += 21;
         if (strsame (cptr, "none", 4))
            Number = -1;
         else
            Number = atoi(cptr);
         HttpdTimerSet (rqptr, TIMER_NOPROGRESS, Number);
         while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
         return (cptr - ControlString);
      }
   }
   if (strsame (cptr, "X-lifetime=", 11))
   {
      cptr += 11;
      if (strsame (cptr, "none", 4))
         Number = -1;
      else
      {
         if (!(Number = atoi(cptr) * 60))
         {
            if (rqptr->DclTaskPtr->TaskType == DCL_TASK_TYPE_CGI_SCRIPT)
               Number = Config.cfScript.ZombieLifeTime+1;
            else
               Number = Config.cfScript.CgiPlusLifeTime+1;
         }
      }
      rqptr->DclTaskPtr->LifeTimeSecond = Number;
      while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
      return (cptr - ControlString);
   }

   /********************************/
   /* error message control fields */
   /********************************/

   if (strsame (cptr, "X-error-", 8))
   {
      if (strsame (cptr+8, "line=", 5))
      {
         cptr += 13;
         rqptr->rqCgi.ScriptControlErrorLine = atoi(cptr);
         while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
         return (cptr - ControlString);
      }
      if (strsame (cptr+8, "module=", 7))
      {
         cptr += 15;
         zptr = cptr;
         StringParseValue (&cptr, Scratch, sizeof(Scratch));
         sptr = VmGetHeap (rqptr, cptr-zptr+1);
         strcpy (sptr, Scratch);
         rqptr->rqCgi.ScriptControlErrorModulePtr = sptr;
         return (cptr - ControlString);
      }
      if (strsame (cptr+8, "text=", 5))
      {
         cptr += 13;
         zptr = cptr;
         StringParseValue (&cptr, Scratch, sizeof(Scratch));
         sptr = VmGetHeap (rqptr, cptr-zptr+1);
         strcpy (sptr, Scratch);
         rqptr->rqCgi.ScriptControlErrorTextPtr = sptr;
         return (cptr - ControlString);
      }
      if (strsame (cptr+8, "vms-status=", 11))
      {
         cptr += 19;
         if (*(unsigned short*)cptr == '%x' || *(unsigned short*)cptr == '%X')
            rqptr->rqCgi.ScriptControlErrorVmsStatus = strtol(cptr+2, NULL, 16);
         else
            rqptr->rqCgi.ScriptControlErrorVmsStatus = atoi(cptr);
         while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++;
         return (cptr - ControlString);
      }
      if (strsame (cptr+8, "vms-text=", 9))
      {
         cptr += 17;
         zptr = cptr;
         StringParseValue (&cptr, Scratch, sizeof(Scratch));
         sptr = VmGetHeap (rqptr, cptr-zptr+1);
         strcpy (sptr, Scratch);
         rqptr->rqCgi.ScriptControlErrorVmsTextPtr = sptr;
         return (cptr - ControlString);
      }
   }

   /*******************/
   /* content handler */
   /*******************/

   if (strsame (cptr, "X-content-handler=", 18))
   {
      cptr += 18;
      StringParseValue (&cptr, Scratch, sizeof(Scratch));
      if (strsame (Scratch, "SSI", -1))
      {
         if (!rqptr->SsiTaskPtr &&
             !rqptr->FileContentPtr)
         {
            /* initialize the file contents structure */
            CgiOutputFile (rqptr, NULL, SsiSizeMax);
            /* set the content structure handler so SSI engine gets control */
            rqptr->FileContentPtr->ContentHandlerFunction = &SsiBegin;
            /* don't want the CGI header in the contents */
            rqptr->rqCgi.AbsorbHeader = true;
         }
      }
      return (cptr - ControlString);
   }

   /************************/
   /* ignore anything else */
   /************************/

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
   {
      WatchThis (rqptr, FI_LI, WATCH_CGI,
                 "UNRECOGNISED script control field");
      WatchDataFormatted ("!&AZ", cptr);
   }
   /* discard */
   StringParseValue (&cptr, Scratch, sizeof(Scratch));
   return (cptr - ControlString);
}

/*****************************************************************************/
/*
Capturing script output to a 'file contents' structure.  This will be detected
and 'handled' by RequestEnd().
*/ 

CgiOutputFile
(
REQUEST_STRUCT *rqptr,
char *OutputPtr,
int OutputCount
)
{                                           
#define CONTENT_SIZE_INITIAL 8192

   int  ContentSize;
   char  *cptr, *sptr, *zptr;
   FILE_CONTENT  *fcptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_CGI))
   {
      if (fcptr = rqptr->FileContentPtr)
         WatchThis (rqptr, FI_LI, WATCH_MOD_CGI,
                    "CgiOutputFile() !UL !UL !UL !UL !&X",
                    fcptr->ContentSizeMax, fcptr->ContentSize, 
                    fcptr->ContentLength, OutputCount, OutputPtr);
      else
         WatchThis (rqptr, FI_LI, WATCH_MOD_CGI,
                    "CgiOutputFile() !UL !&X", OutputCount, OutputPtr);
      if (OutputPtr) WatchDataDump (OutputPtr, OutputCount);
   } 

   if (!OutputPtr)
   {
      /**************/
      /* initialize */
      /**************/

      ContentSize = CONTENT_SIZE_INITIAL;
      if (ContentSize > OutputCount) ContentSize = OutputCount;

      /* allow one extra character for a terminating null */
      rqptr->FileContentPtr = fcptr = (FILE_CONTENT*)
         VmGetHeap (rqptr, sizeof(FILE_CONTENT) + ContentSize+1);

      fcptr->ContentSizeMax = OutputCount;
      fcptr->ContentSize = ContentSize;

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CGI))
         WatchThis (rqptr, FI_LI, WATCH_CGI,
                    "CONTENT !UL bytes max", fcptr->ContentSizeMax);

      /* buffer space immediately follows the structured storage */
      fcptr->ContentPtr = (char*)fcptr + sizeof(FILE_CONTENT);

      /* populate the file contents structure with some file data */
      zptr = (sptr = fcptr->FileName) + sizeof(fcptr->FileName);
      for (cptr = rqptr->rqCgi.ScriptFileNamePtr;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
      if (sptr >= zptr)
      {
         ErrorGeneralOverflow (rqptr, FI_LI);
         return;
      }
      *sptr = '\0';
      fcptr->FileNameLength = sptr - fcptr->FileName;

      memcpy (&fcptr->CdtBinTime, &rqptr->rqTime.Vms64bit, 8);
      memcpy (&fcptr->RdtBinTime, &rqptr->rqTime.Vms64bit, 8);

      fcptr->UicGroup = 0;
      fcptr->UicMember = 0;
      fcptr->Protection = 0;

      return;
   }

   /**********/
   /* buffer */
   /**********/

   fcptr = rqptr->FileContentPtr;

   if (fcptr->ContentLength + OutputCount > fcptr->ContentSize)
   {
      if (fcptr->ContentSize >= fcptr->ContentSizeMax)
      {
         /* keep track of how large it would have grown */
         fcptr->ContentLength += OutputCount;
         return;
      }

      /* double it's size each time, up to the size limit */
      fcptr->ContentSize += fcptr->ContentSize;
      if (fcptr->ContentSize > fcptr->ContentSizeMax)
         fcptr->ContentSize = fcptr->ContentSizeMax;

      rqptr->FileContentPtr = fcptr = (FILE_CONTENT*)
         VmReallocHeap (rqptr, rqptr->FileContentPtr,
                        fcptr->ContentSize+1, FI_LI);
      fcptr->ContentPtr = (char*)fcptr + sizeof(FILE_CONTENT);
   }

   memcpy (fcptr->ContentPtr + fcptr->ContentLength, OutputPtr, OutputCount); 
   fcptr->ContentLength += OutputCount;
   fcptr->ContentPtr[fcptr->ContentLength] = '\0';

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_CGI))
      WatchThis (rqptr, FI_LI, WATCH_MOD_CGI, "!UL !UL !UL",
                 fcptr->ContentSizeMax, fcptr->ContentSize, 
                 fcptr->ContentLength);
}

/*****************************************************************************/
/*
Generate a (hopefully) unique sequence of 224 bits (28 bytes, which is a fair
bit :^) The string is generated using the a count sequence and quadword binary
times each time it is called.  This provides a continuous, non-repeating series
of unlikely bit combinations with a one in 2^224 chance (I think!) of presence
in an I/O stream.
*/ 

CgiSequence
(
char OneChar,
char *CgiSequencePtr,
int *CgiSequenceLengthPtr
)
{                                           
   static $DESCRIPTOR (SequenceFaoDsc, "$!AZ-!8XL!8XL!8XL-\0");
   static $DESCRIPTOR (SequenceDsc, "");
   static char  OneCharString [] = " ";
   static int  SequenceCount = 0;

   unsigned short  Length;
   unsigned long  BinTime[2];

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

   if (WATCH_MODULE(WATCH_MOD_CGI))
      WatchThis (NULL, FI_LI, WATCH_MOD_CGI, "CgiSequence()");

   SequenceCount++;
   sys$gettim(&BinTime);
   SequenceDsc.dsc$w_length = 29;
   SequenceDsc.dsc$a_pointer = CgiSequencePtr;
   OneCharString[0] = OneChar;
   sys$fao (&SequenceFaoDsc, &Length, &SequenceDsc,
            OneCharString, SequenceCount, BinTime[1], BinTime[0]);
   *CgiSequenceLengthPtr = Length-1;

   if (WATCH_MODULE(WATCH_MOD_CGI))
      WatchThis (NULL, FI_LI, WATCH_MOD_CGI, "!&Z", CgiSequencePtr);
}

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

