/*****************************************************************************/
/*
                                CgiLib.c

For C Language scripts these functions provide a number of common CGI
activities as a simple object code module.  One objective is the reasonably
transparent, core support for WASD CGI and CGIplus, VMS Apache (CSWS),
Purveyor, "vanilla" CGI (e.g. Netscape FastTrack), and OSU DECnet-based
scripting.  Of course all this is basically from the perspective of the WASD
environment.

THESE ROUTINES ARE NOT DESIGNED TO BE THREAD-SAFE!

*** NOTE ***
------------
As of version 1.5 CGILIB was reworked to allow compilation into an object
module by defining the macro CGILIB_OBJECT_MODULE for inclusion at link-time
rather than at compilation time.  This is obviously a more elegant way to
build code and the author can only plead being brain-dead during the period it
exploded from a handful of original functions just separated from application
code for convenience to the far more extensive collection currently present. 
It *should* also allow for the pre-1.5 behaviour of source-code-inclusion for
backward compatibility - but no absolute guarantees!  A number of additional
functions have been provided to allow 'Set'ting of run-time characteristics
that previously could be pre-configured at compilation using macros.  See a
number of WASD scripts for hints of how to use as an object module.


INCLUDING IN YOUR CODE
----------------------
Build as an object module, It may then be linked against the rest of the
application.  Compile directly by defining CGILIB_OBJECT_MODULE as in the
following example.

  $ INCLUDES = "/INCLUDE=[]"
  $ DEFINES = "/DEFINE=(CGILIB_OBJECT_MODULE)"
  $ CC CGILIB /DECC /STANDARD=RELAXED_ANSI 'INCLUDES' 'DEFINES'

Once compiled it may linked as an object module directly

  $ LINK APPLICATION,[.OBJ_AXP]CGILIB.OBJ /EXEC=HT_EXE:APPLICATION.EXE

or if it has been placed into an object library

  $ LINK APPLICATION,[.OBJ_AXP]CGILIB.OLB/LIBRARY /EXEC=HT_EXE:APPLICATION.EXE

To change the default size of the CGIplus variable stream record buffer add an
appropriate definition before #including the CGILIB header file, as shown in
the following example:

  #define CGILIB_CGIPLUS_RECORD_SIZE 4096


FUNCTIONALITY 
-------------
The following is a list of available functions (see descriptions below for
further information).  Functions and global storage beginning 'CgiLib__' are
for internal CGILIB use only, and should not directly be called by script code.

  void CgiLibCgiPlusEOF ();
  void CgiLibCgiPlusEOT ();
  void CgiLibCgiPlusESC ();
  void CgiLibCgiPlusInGets (char *String, int SizeOfString);
  void CgiLibCgiPlusSetVarRecord ();
  void CgiLibCgiPlusSetVarStruct ();

  void CgiLibEnvironmentBinaryOut ();
  void CgiLibEnvironmentInit (int argc, char *argv[], int DoResponseCgi);
  int CgiLibEnvironmentIsApache ();
  int CgiLibEnvironmentIsCgi ();
  int CgiLibEnvironmentIsCgiPlus ();
  int CgiLibEnvironmentIsOsu ();
  int CgiLibEnvironmentIsPurveyor ();
  int CgiLibEnvironmentIsWasd ();
  char* CgiLibEnvironmentName ();
  void CgiLibEnvironmentRecordOut ();
  int CgiLibEnvironmentSetDebug (int);
  char* CgiLibEnvironmentSetVarNone (char*);
  char* CgiLibEnvironmentVersion ();

  int CgiLibResponseHeader (int StatusCode,
                            char *ContentType,
                            ...);

  int CgiLibResponseRedirect (char *FormatString, ...);

  int CgiLibResponseError (char *SrcFileName,
                           int SrcLineNumber,
                           int StatusValue,
                           char *FormatString,
                           ...);

  int CgiLibResponseSuccess (char *FormatString, ...);

  char* CgiLibResponseSetBody (char *string)
  char* CgiLibResponseSetCharset (char *string)
  char* CgiLibResponseSetDefaultCharset (char *string)
  char* CgiLibResponseSetErrorMessage (char *string)
  int CgiLibResponseSetErrorStatus (int status)
  char* CgiLibResponseSetErrorTitle (char *string)
  char* CgiLibResponseSetErrorInfo (char *string)
  char* CgiLibResponseSetSoftwareID (char *string)
  char* CgiLibResponseSetSuccessMessage (char *string)
  int CgiLibResponseSetSuccessStatus (int status)
  char* CgiLibResponseSetSuccessTitle (char *string)

  char* CgiVar (char *VarName);
  char* CgiLibVar (char *VarName);
  char* CgiLibVarNull (char *VarName);
  int CgiLibVarByDesc (struct dsc$descriptor* NameDsc,
                       struct dsc$descriptor* ValueDsc,
                       short int *Length);

  void CgiLibInit (int argc, char* argv[], void ProcessRequest(void));

  CgiLibOsuInit (int argc, char *argv[]);

  CgiLibOsuStdoutCgi ();
  CgiLibOsuStdoutRaw ();

  int CgiLibUrlDecode (char *String);

  int CgiLibUrlEncode (char *PlainString,
                      int NumberOfChars,
                      char *EncodedString,
                      int SizeOfEncodedString);

  int CgiLibUrlEncodeFileName (char *FileName,
                               char *EncodedString,
                               int SizeOfEncodedString,
                               int RelaxedEncode,
                               int ForceLower);

  char* CgiLibFormEncodedParse (char *String, int *Context);

  int CgiLibHtmlDeEntify (char *String);

  int CgiLibHtmlEscape (char *PlainString,
                       int NumberOfChars,
                       char *EscapedString,
                       int SizeOfEscapedString);

  int CgiLibAnchorHtmlEscape (char *PlainString,
                              int NumberOfChars,
                              char *EscapedString,
                              int SizeOfEscapedString,
                              int AnchorUrls);

  char* CgiLibHttpStatusCodeText (int StatusCode);

  char* CgiLibReadRequestBody (char **BodyPtrPtr, int *BodyLength);
  int CgiLibFormRequestBody (char *BodyPtr, int BodyLength);


SCRIPTING ENVIRONMENT
---------------------
A number of functions are used to initialize and support behaviour in the
various scripting environments.  It should always be called before the use of
any other functions.

The following function initializes the scripting environment detected according
to various environment variables characteristic to each.  The 'argc' and 'argv'
standard C command-line parameters are only needed if the script is to support
the OSU environment (zero and NULL may be substituted otherwise).  Parameter
'DoResponseCgi' "forces" the functions to return a CGI response regardless of
which environment it is executing in.

  void CgiLibEnvironmentInit (int argc,
                              char *argv[],
                              int DoResponseCgi);

Each of the first five functions returns true or false depending on which
environment the script is executing in.  The sixth returns a pointer to a
string indicating which environment it is.

  int CgiLibEnvironmentIsApache ();
  int CgiLibEnvironmentIsCgi ();
  int CgiLibEnvironmentIsCgiPlus ();
  int CgiLibEnvironmentIsWasd ();
  int CgiLibEnvironmentIsOsu ();
  int CgiLibEnvironmentIsPurveyor ();

The following two functions ensure the <stdout> stream is appropriately set
for either binary (non-record, no translation of newline characters, etc.) or
standard output.  Generally WASD and OSU always function in binary mode while
standard CGI environments normally accept record-oriented output.  Binary mode
should always be ensured for non-textual output.

  CgiLibEnvironmentCgiBinaryOut ();
  CgiLibEnvironmentCgiRecordOut ();

The following four set the run-time characteristsic of the CGILIB environment. 
The first turns CGILIB debug on or off.  The second return a string identifying
the server environment.  The third accepts a pointer to either NULL or a string
(most usually "").  The fourth returns a string identifying the CGILIB
version.

  char* CgiLibEnvironmentName ();
  int CgiLibEnvironmentSetDebug (int);
  char* CgiLibEnvironmentSetVarNone (char*);
  char* CgiLibEnvironmentVersion ();

The following function can be used to execute a script function without the
script needing to concern itself with whether it's been called in a standard
CGI or CGIplus environment.

  void CgiLibInit (int argc, char* argv[], void ProcessRequest(void));


REQUEST RESPONSES ... CGILIB_RESPONSE_SUPPORT macro
-----------------
These functions allow HTTP response headers and defacto WASD error and
"success" report pages to be generated.  The CGILIB_RESPONSE_... macros (see
CGILIB.H) allow various per-script customisations to be performed.

The following function generates an HTTP response header.  For WASD and OSU
environments it will generate a full HTTP response, while for vanilla CGI
environments it uses a CGI response.  302 (redirection, see also
'CgiLibResponseRedirect()') and 401 (authentication) responses are handled. 
Supply NULL for 'ContentType' parameter and the location URL or authentication
header line as parameter three.  The third can be a 'printf()' format string
with associated parameter supplied following.  This can also be used to supply
additional newline terminated header lines into the response.  If a specific
character set is required, begin the format string with "; charset=" and then
the charset string, followed by a newline character.

  int CgiLibResponseHeader (int StatusCode,
                            char *ContentType,
                            ...);

The next generates an HTTP 302 redirect response.  If the format string begins
"%!" then the redirect is made absolute with the appropriate local server
host name and if necessary port prefixing whatever are supplied as parameters. 
If the "%!" does not precede the format string then it can either be a full
absolute or local redirection URL, it's just appended to the "Location: " field
name.  The format string can be for a 'printf()' with associated parameters
supplied.  Strings fed to this function must be URL-encoded if necessary.

  int CgiLibResponseRedirect (char *FormatString, ...);

Generate a WASD format error report.  If 'StatusValue' is non-zero then it is
assumed to be a VMS status code and a message is generated from it.  If zero
then only the format string is used.  The format string may be a plain-text
message or contain 'printf()' formatting characters with any associated
parameters. 

  int CgiLibResponseError (char *SrcFileName,
                           int SrcLineNumber,
                           int StatusValue,
                           char *FormatString,
                           ...);

Generate a "success" report in the usual WASD format.  The format string may be
a plain-text message or contain 'printf()' formatting characters with any
associated parameters. 

  int CgiLibResponseSuccess (char *FormatString, ...);

The following functions allow various response strings and status codes to be
set at run-time.  Note that these function do not aloocate storage, whatever
they are called with must point to something permananent.  They are also
customizable as macros in CGILIB.H (deprecated functionality).

  char* CgiLibResponseSetBody (char *string)
  char* CgiLibResponseSetCharset (char *string)
  char* CgiLibResponseSetDefaultCharset (char *string)
  char* CgiLibResponseSetErrorMessage (char *string)
  int CgiLibResponseSetErrorStatus (int status)
  char* CgiLibResponseSetErrorTitle (char *string)
  char* CgiLibResponseSetErrorInfo (char *string)
  char* CgiLibResponseSetSoftwareID (char *string)
  char* CgiLibResponseSetSuccessMessage (char *string)
  int CgiLibResponseSetSuccessStatus (int status)
  char* CgiLibResponseSetSuccessTitle (char *string)


CGI VARIABLES ... CGILIB_CGIVAR_SUPPORT macro
-------------
This library provides a simple, uniform mechanism for access to CGI variable
values transparently in the WASD CGI and CGIplus environments, as well as OSU
(via the request dialog phase) and "vanilla" CGI environments (e.g. Netscape
FastTrack).  CGI variables names are always assumed to be in upper-case and to
be preceded by "WWW_".  Will automatically detect and adjust behaviour
according to whether the CGI variables are actually prefixed with "WWW_" (as
with WASD, OSU and Purveyor) or not (as with Netscape FastTrack).

Simply call the CgiLibVar() function (or CgiVar() for backward-compatibility)
with the CGI variable name to be accessed and if that CGI variable exists a
pointer to the value will be returned.  Non-existant variables return
CGILIB_NONE (see below), which is by default an empty string ("").  The values
pointed to should NOT be modified in any way (copy the value if this is
required).

  char* CgiVar (char *VarName);
  char* CgiLibVar (char *VarName);

The following macro can be used to determine whether an empty string is
returned for a variables that does not exists (the default) or a NULL pointer.

  #define CGILIB_NONE NULL

Function CgiLibVarNull() will always return NULL if the CGI variable does not
exist, or a pointer to the variable string value.  This may be used to
differentiate between a CGI variable containing an empty value and not actually
existing at all.

  char* CgiLibVarNull (char *VarName);

The following function may be used to obtain CGI variables using VMS
descriptors.  Returns zero and a space-filled value descriptor if the variable
does not exist, one and a space padded value if it does.  The 'Length' address
parameter is optional and if supplied the actual length of the variable value
string (excluding any padding spaces) is set.

  int CgiLibVarByDesc
  (
  struct dsc$descriptor *NameDsc,
  struct dsc$descriptor *ValueDsc,
  short int *Length
  );


CGIPLUS ... CGILIB_CGIPLUS_SUPPORT macro
-------
In the CGIplus environment supplying an empty variable name, CgiLibVar(""),
will cause the function to block until the arrival of a request, upon which it
just returns a CGILIB_NONE pointer, allowing start-of-request synchronization. 
This also resets the variables in preparation for reading the CGI data stream
of the next request, an alternative to calling with a NULL parameter at the end
of request processing.

Supplying a wildcard variable name, CgiLibVar ("*"), returns each CGIplus
variable 'name=value' string in turn, with end-of-variables being indicated by
a NULL pointer.

The following function will output a suitably flushed (to make it a single
record) CGIplus end-of-file indicator telling the server the script is finished
servicing the response.

  CgiLibCgiPlusEOF ();

Again, the following two functions output records to delimit the beginning and
end (respectively) of a CGIplus callout.

  CgiLibCgiPlusESC ();
  CgiLibCgiPlusEOT ();

This CGIplus function allows a single string to be read from the CGIPLUSIN
stream.  This is intended for a script in callout mode to read a single record
response from the server.  If the record read overflows the string buffer
provided it is truncated and null-terminated.

  void CgiLibCgiPlusInGets (char *String, int SizeOfString);

The following functions switch the CGI variable stream between 'record' mode
(the default, where each CGI variable occupies an individual I/O on CGIPLUSIN
stream) and 'struct' mode (the CGI variables are I/Oed as a single, binary
structure).  'Struct' mode is SIGNIFICANTLY more efficient and has lower
latency.  The WASD HTTPd version is detected and for 7.2 and following the
stream is automatically switched to 'struct' mode if neither of the functions
have been used to explicitly set the mode.  CGILIB may also be forced to retain
'record' mode by creating the environment variable CGILIB_CGIPLUS_VAR_RECORD
(e.g. define it as a logical, assign symbol).  This facility may be useful for
performance comparisons, etc.  See [SRC.HTTPD]CGI.C for information on the
internal structure of the 'struct' stream.

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!

  void CgiLibCgiPlusSetVarRecord ();
  void CgiLibCgiPlusSetVarStruct ();


OSU ENVIRONMENT ... CGILIB_OSU_SUPPORT macro
---------------
The OSU (DECthreads) HTTP server DECnet-based scripting environment is
supported semi-transparently.  When OSU is detected the CgiLibOsuInit()
initializes the OSU request information (by engaging in the dialog phase)
storing CGI-like variables for later access by CgiLibVar().  It must be
supplied with command line arguments.

  CgiLibOsuInit (int argc, char *argv[]);

Supplying a wildcard variable name (i.e. "*") returns each OSU variable
'name=value' string in turn, with no more being indicated by a NULL pointer.

The function CgiLibStdoutRaw() reopen()s <stdout> to NET_LINK.  An explicit
used of this function is not required if CgiLibEnvironmentInit() has been
called.                                                       

  CgiLibOsuStdoutRaw ();

Script responses are designed to be returned in OSU "raw" mode; the script
taking care of the full response header and correctly carriage-controlled data
stream, text or binary!!  The script standard output stream is reopened in
binary mode (no translation of '\n') with a maximum record size set to 4096
bytes (failure to set this results in the OSU server reporting
%SYSTEM-F-DATAOVERUN errors).  The binary output is enclosed by suitably
fflushed() "<DNETRAW>" and "</DNETRAW>" control tags (the flush make them
individual records as required by OSU).  If in debug the script output is
placed into "text" mode.

The function CgiLibStdoutCgi() reopen()s <stdout> to NET_LINK.  An explicit
used of this function is not required if CgiLibEnvironmentInit() has been
called.

  CgiLibOsuStdoutCgi ();

Script responses are designed to be returned in OSU "CGI" mode;  very similar
to other implementations where the output begins with a CGI-compliant header.
The script standard output stream is reopened in binary mode (no translation of
'\n') with a maximum record size set to 4096. The script output is enclosed by
suitably fflushed() "<DNETCGI>" and "</DNETCGI>" control tags (the flush make
them individual records as required by OSU).  If in debug the script output is
placed into "text" mode.


FORM-ENCODED PARSE ... CGILIB_GENERAL_SUPPORT macro
------------------

  char CgiLibFormEncodedParse (char *String, int *Context);

This function parses a "application/x-www-form-urlencoded" string into it's
decoded, constituent name and value pairs.  It can be passed *any* urlencoded
string including QUERY_STRING CGI variables and POSTed request bodies.  It
returns a dynamically allocated string comprising a "name=value" pair, in much
the same format as a CgiLibVar("*") call.  When the form fields are exhausted
it returns a pointer to NULL.  The form field name is not prefixed by
"WWW_FORM", or anything else.  This buffer is reused with each call and so
should not be modified, any contents should be copied as with CgiLibVar()
constraints.  The 'Context' parameter should be initialized to zero before the
initial call.  At the end of a parse it is reinitialized to zero.


URL-DECODE ... CGILIB_GENERAL_SUPPORT macro
----------
Decode a URL-encoded string (i.e. string containing "+" substituted for spaces
and other forbidden characters encoded using the "%xx" hexadecimal scheme). 
Resultant string is always the same size or smaller so it can be done in-situ! 
Returns the size of the resultant string.

  int CgiLibUrlDecode (char *String);


URL-ENCODE ... CGILIB_GENERAL_SUPPORT macro
----------
URL-encode all non-alpha-numeric characters. If 'EncodedString' is NULL
sufficient memory will be dynamically allocated to hold the encoded string. 
For fixed size 'EncodedString' it will not overflow the supplied string but
does not return any indication it reached the limit.  Returns the length of the
encoded string or a pointer to the encoded string (which should be cast to
(char*) by the calling routine).
 
  int CgiLibUrlEncode (char *PlainString,
                       int NumberOfChars,
                       char *EncodedString,
                       int SizeOfEncodedString);

If 'NumberOfChars' is -1 all characters up to the null are copied.  If
'SizeOfEncodedString' is -1 the target storage if considered "big enough".


URL-ENCODE FILE NAME ... CGILIB_GENERAL_SUPPORT macro
--------------------
URL-encode (%nn) a possibly extended specification file name.  This can be done
in two ways.  Leave the VMS extended file specification escape sequences in
place (e.g. "^_") but encode absolutely forbidden characters (.e.g ' ', '?',
"%", etc.), called 'RelaxedEncode' here.  Or a strict encode, where the
extended escaped sequences are first un-escaped into characters, then those
charaters URL-encoded as necessary.  If 'ForceLower' true, and no extended file
specification characters were found (e.g. lower-case, escaped characters) then
replace all upper-case alphabetics with lower-case.

  int CgiLibUrlEncodeFileName (char *FileName,
                               char *EncodedString,
                               int SizeOfEncodedString,
                               int RelaxedEncode,
                               int ForceLower);

If 'SizeOfEncodedString' is -1 the target storage if considered "big enough".
If 'EncodedString' is NULL sufficient memory will be dynamically allocated to
hold the encoded string.


HTML-ESCAPE ... CGILIB_GENERAL_SUPPORT macro
-----------
Escape plain-text characters forbidden to occur in HTML documents (i.e. '<',
'>' and '&').  If 'EscapedString' is NULL sufficient memory will be dynamically
allocated to hold the encoded string.  For fixed size 'EscapedString' it will
not overflow the supplied string but does not return any indication it reached
the limit.  Returns the length of the escaped string or a pointer to the
encoded string (which should be cast to (char*) by the calling routine).

  int CgiLibHtmlEscape (char *PlainString,
                        int NumberOfChars,
                        char *EscapedString,
                        int SizeOfEscapedString);

'AnchorUrls' may be used to turn HTML URLs into HTML anchors in the text by
calling with CGILIB_ANCHOR_WEB.  It will also attempt to detect mail addresses
(anything like 'this@wherever.host.name') and create "mailto:" links out of
them, call with CGILIB_ANCHOR_MAIL.  To get both use a bit-wise OR.

  int CgiLibAnchorHtmlEscape (char *PlainString,
                              int NumberOfChars,
                              char *EscapedString,
                              int SizeOfEscapedString,
                              int AnchorUrls);

If 'NumberOfChars' is -1 all characters up to the null are copied.  If
'SizeOfEscapedString' is -1 the target storage if considered "big enough".

  int CghiLibHtmlDeEntify (char *String)

Convert numeric HTML entities in a string into their character equivalents
(e.g. "&#38" to '&', "&#00; to 0x00, etc.)  Also converts common alphabetic
entities (e.g. "&amp;", &nbsp;", &lt;", etc.) but not all (any that are not
recognised are left untouched).  Does not URL-decode!   Resultant string is
always the same size or smaller so it can be done in-situ!  Returns the size of
the resultant string.


STATUS MEANING ... CGILIB_GENERAL_SUPPORT macro
--------------
Returns a string to a "standard" status code meaning, e.g. 200 "Success".

   char* CgiLibHttpStatusCodeText (int StatusCode);


PROCESSING POSTED REQUESTS ... CGILIB_POST_SUPPORT macro
--------------------------
The following function will read a POSTed request body into a single array of
char (doesn't matter whether it's text or binary).  Returns NULL if it's not a
POST, a pointer if it is.  The caller must free() this memory when finished
with it.

  char* CgiLibReadRequestBody (char **BodyPtrPtr, int *BodyLength);

This works with WASD, "vanilla" CGI and OSU environments.  With OSU it _must_
be called before initializing output (with CgiLibOsuStdoutCgi/Raw()) which
takes the script out of dialog and into output phase.

The following function takes a "application/x-www-form-urlencoded" or
"multipart/form-data" body (which could have been read with the above function)
and decodes the form fields inserting them into the list of CGI variables as if
they were "WWW_FORM_" values processed from the query string.  The values may
then be accessed as if standard CGI variables using CgiLibVar("WWW_FORM_..."). 
The ability to access the contents of such POSTed requests in this fashion is a
*VERY USEFUL CAPABILITY*.

  int CgiLibFormRequestBody (char *BodyPtr, int BodyLength);

NOTE ON MULTIPART/FORM-DATA:  As it is possible to POST un-encoded characters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~  with this method (all characters from 0x00 to
0xff are transmitted un-encoded) non-printable characters (0x00-0x1f,
0x7f-0x9f, 0xff) are HTML-entified (i.e. 0x00 becomes "&#0;", 0x01 "&#1;",
etc.) before storage as variables.  These would need to be de-entified before
use (with CgiLibHtmlDeEntify()).  "multipart/form-data" is generated using the
"<INPUT TYPE=file>" HTML form tag. If the field is an upload ("type=file") and
there is an associated content-type and file name (as there should be) these
are placed in variables with the same name as the field with
"_MIME_CONTENT_TYPE" and "_MIME_FILENAME" added.


NOTE ON CSWS V1.0-1 CGI RESPONSE
--------------------------------
12-JAN-2001 There seems to be a bug in the Apache 1.3.12 CGI engine, confirmed
by Compaq VMS Engineering with the ASF.  If the request has a "Pragma:
no-cache" field (i.e. [RELOAD] button) and the script returns a "Status: nnn"
response status line, with a trailing "Last-Modified: date" field, the CSWS
server returns a header but no body.  Although I haven't tried all the
in-and-outs of possible field combinations this one's sure a winner.

Hence this library introduces a fudge, not returning the "Status: nnn" field
for responses where it is not absolutely required, when Apache is detected. 
This reults in a "Content-Type: xxxxx", "Location: URL", etc., being returned
as the first line and seems to work well enough and without the original
problem.


COPYRIGHT
---------
Copyright (C) 1999-2004 Mark G.Daniel
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under the conditions of the GNU GENERAL PUBLIC LICENSE, version 2.


VERSION HISTORY (update SoftwareID in header file as well!)
---------------
20-MAY-2004  MGD  v1.6.13, bugfix; (introduced with 1.6.12)
                           with CGIplus ensure body is read for each request
26-JAN-2004  MGD  v1.6.12, bugfix; CgiLibReadRequestBody() if the body has
                           already been read then just return the ptr (CSWS)
23-DEC-2003  MGD  v1.6.11, minor conditional mods to support IA64
14-JUN-2003  MGD  v1.6.10, bugfix; CgiLibVarByDesc() (jpp@esme.fr)
                           bugfix; CgiLib_PrepareRequest() (jpp@esme.fr)
24-MAR-2003  MGD  v1.6.9, (courtesy Jean-Pierre Petit, jpp@esme.fr) 
                          CgiLibFormRequestBody() use strstr() on
                          "..form-urlencoded" to accomodate some browsers,
                          CgiLibInit(), CgiLib__PrepareRequest() and
                          CgiLibVarByDesc()
03-MAR-2003  MGD  v1.6.8, CgiLib__BodyMultipartFormData() now entifies only
                          'control' characters (backward compatibility via
                          environment variable CGILIB_MULTIPART_ISPRINT
30-DEC-2002  MGD  v1.6.7, bugfix; freopen() in CgiLibEnvironmentInit() 
11-OCT-2002  MGD  v1.6.6, refine use of APACHE$FIXBG bit toggling (also
                          ensuring SYS$OUTPUT returns to record mode on exit)
11-SEP-2002  MGD  v1.6.5, bugfix; CgiLibResponseHeader() 'charset='
                          supplied is parameter format string
15-AUG-2002  MGD  v1.6.4, add debug content-type to CGIplus CgiLib__GetVar()
                          (somewhat unsuccessfully, see note),
                          bugfix; query string key values for non-WWW prefix
22-APR-2002  MGD  v1.6.3, CSWS APACHE_SHARED_SOCKET to APACHE$SHARED_SOCKET,
                          bugfix; more aspects of "WWW_"-less CGI variables 
27-JUN-2001  MGD  v1.6.2, bugfix; adjust CgiLib__SetEnvironment()
                          GATEWAY_INTERFACE detection (for OSU)
03-MAY-2001  MGD  v1.6.1, bugfix; some aspects of "WWW_"-less CGI variables
07-APR-2001  MGD  v1.6.0, provide for streamed CGIplus variables,
                          bugfix; refine changes from 1.5.3
22-MAR-2001  MGD  v1.5.3, refine CgiLib__GetVar() to allow environments
                          without "WWW_" CGI variable prefix to be supported
08-JAN-2001  MGD  v1.5.2, CSWS V1.0-1 (VMS Apache) "fixbg" support (see note
                          above), APACHE_INPUT becomes APACHE$INPUT,
                          bugfix; encode/escape terminate empty result string,
                          bugfix; CgiLib__GetKey()
09-DEC-2000  MGD  v1.5.1, bugfix; CgiLibResponseHeader() charset
28-OCT-2000  MGD  v1.5.0, make CGILIB available as an object module,
                          add 'Set' functions to parallel previous macros,
                          make adjustments for RELAXED_ANSI compilation
24-JUN-2000  MGD  v1.4.2, make CgiLibVar() real function (not #define),
                          bugfix; CgiLibResponseError(),
                          bugfix; form fields in non-CGIplus environments
                                  (Imre Fazekas, fazekas@datex-sw.hu)
30-MAY-2000  MGD  v1.4.1, explicitly exit SS$_INSFMEM if allocation fails
09-APR-2000  MGD  v1.4.0, VMS Apache environment (1.3.9 BETA),
                          CgiLibEnvironmentName() returns WWW_SERVER_SOFTWARE,
                          modify report format inline with WASD changes
19-FEB-2000  MGD  v1.3.0, CgiLibUrlEncodeFileName(),
                          CgiLibHtmlDeEntify(),
                          CgiLibFormRequestBody() will now also
                          process "multipart/form-data" bodies,
                          change CgiLib__VarList() data structuire to use
                          full integers instead of short integers for lengths,
                          bugfix; worst-case %nn in URL-encodes
24-SEP-1999  MGD  v1.2.0, changes to support Purveyor,
                          CgiLibCgiPlus...(), CgiLibEnvironment...(),
                          CgiLibResponse...(), CgiLibAnchorHtmlEscape()
26-JUN-1999  MGD  v1.1.0, support POSTed body forms under standard CGI
24-APR-1999  MGD  v1.0.0, initial '#include'able code module
*/
/*****************************************************************************/

/* standard C headers */
#include <ctype.h>
#include <errno.h>
#include <file.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/* VMS headers */
#include <descrip.h>
#include <libdef.h>
#include <lib$routines.h>
#include <stsdef.h>
#include <ssdef.h>
#include <starlet.h>

/* module header */
#ifdef CGILIB_OBJECT_MODULE
#  include <cgilib.h>
#else
   /* backward compibility */
#  ifndef CGILIB_H_LOADED
#     ifdef CGILIB_INCLUDE_H
#        include CGILIB_INCLUDE_H
#     else
#        include "/ht_root/src/misc/cgilib.h"
#     endif
#  endif
#endif

/* just modifies the handling of a couple of things */
#define CGILIB_DEVELOPMENT 1
#define CGILIB_VARLIST_DEBUG 0

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

/* ohhh, the contortions we go through!! */

#ifdef CGILIB_OBJECT_MODULE

#define  CGILIB__DEBUG                      CgiLib__Debug
#define  CGILIB__RESPONSE_BODY              CgiLib__ResponseBodyPtr
#define  CGILIB__RESPONSE_CHARSET           CgiLib__ResponseCharsetPtr
#define  CGILIB__RESPONSE_DEFAULT_CHARSET   CgiLib__ResponseDefCharsetPtr
#define  CGILIB__RESPONSE_ERROR_INFO        CgiLib__ResponseErrorInfoPtr
#define  CGILIB__RESPONSE_ERROR_MESSAGE     CgiLib__ResponseErrorMessagePtr
#define  CGILIB__RESPONSE_ERROR_STATUS      CgiLib__ResponseErrorStatus
#define  CGILIB__RESPONSE_ERROR_TITLE       CgiLib__ResponseErrorTitlePtr
#define  CGILIB__RESPONSE_SOFTWAREID        CgiLib__ResponseSoftwareIdPtr
#define  CGILIB__RESPONSE_SUCCESS_MESSAGE   CgiLib__ResponseSuccessMsgPtr
#define  CGILIB__RESPONSE_SUCCESS_STATUS    CgiLib__ResponseSuccessStatus
#define  CGILIB__RESPONSE_SUCCESS_TITLE     CgiLib__ResponseSuccessTitlePtr

#else

#define  CGILIB__DEBUG                      CGILIB_DEBUG
#define  CGILIB__RESPONSE_DEFAULT_CHARSET   CGILIB_RESPONSE_DEFAULT_CHARSET
#define  CGILIB__RESPONSE_BODY              CGILIB_RESPONSE_BODY
#define  CGILIB__RESPONSE_CHARSET           CGILIB_RESPONSE_CHARSET
#define  CGILIB__RESPONSE_ERROR_INFO        CGILIB_RESPONSE_ERROR_INFO
#define  CGILIB__RESPONSE_ERROR_MESSAGE     CGILIB_RESPONSE_ERROR_MESSAGE
#define  CGILIB__RESPONSE_ERROR_STATUS      CGILIB_RESPONSE_ERROR_STATUS
#define  CGILIB__RESPONSE_ERROR_TITLE       CGILIB_RESPONSE_ERROR_TITLE
#define  CGILIB__RESPONSE_SOFTWAREID        CGILIB_RESPONSE_SOFTWAREID
#define  CGILIB__RESPONSE_SUCCESS_MESSAGE   CGILIB_RESPONSE_SUCCESS_MESSAGE
#define  CGILIB__RESPONSE_SUCCESS_STATUS    CGILIB_RESPONSE_SUCCESS_STATUS
#define  CGILIB__RESPONSE_SUCCESS_TITLE     CGILIB_RESPONSE_SUCCESS_TITLE

#endif

int  CgiLib__Debug,
     CgiLib__CgiPrefixWWW,
     CgiLib__CgiPlusLoadVariables = 1,
     CgiLib__CgiPlusUsageCount,
     CgiLib__CgiPlusVarRecord,
     CgiLib__CgiPlusVarStruct,
     CgiLib__Environment,
     CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_RECORD,
     CgiLib__ResponseCount,
     CgiLib__ResponseCgi,
     CgiLib__ResponseErrorStatus,
     CgiLib__ResponseSuccessStatus;

char  *CgiLib__CgiPlusEofPtr,
      *CgiLib__CgiPlusEotPtr,
      *CgiLib__CgiPlusEscPtr,
      *CgiLib__VarNonePtr = CGILIB_NONE,
      *CgiLib__QueryStringPtr,
      *CgiLib__ResponseBodyPtr,
      *CgiLib__ResponseCharsetPtr,
      *CgiLib__ResponseDefCharsetPtr,
      *CgiLib__ResponseErrorInfoPtr,
      *CgiLib__ResponseErrorMessagePtr,
      *CgiLib__ResponseErrorTitlePtr,
      *CgiLib__ResponseSoftwareIdPtr,
      *CgiLib__ResponseSuccessMsgPtr,
      *CgiLib__ResponseSuccessTitlePtr;

char  CgiLib__SoftwareID [] = CGILIB_SOFTWAREID,
      CgiLib__VarEmpty [] = "";

FILE  *CgiLib__CgiPlusInFile;

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

       /**************************/
#if     CGILIB_ENVIRONMENT_SUPPORT
       /**************************/

/*****************************************************************************/
/*
(jpp@esme.fr)  Wrapper function for tranparently handling a script without
it needing to concern itself with whether it's been called in a standard CGI or
CGIplus environment.
*/

void CgiLibInit
(
int argc,
char* argv[],
void ProcessRequest(void)
)
{
   int  Debug,
        IsCgiPlus;

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

   Debug = (getenv ("CGILIB_DEBUG") != NULL);
   CgiLibEnvironmentSetDebug (Debug);

   CgiLibEnvironmentInit (argc, argv, 0);

   IsCgiPlus = CgiLibEnvironmentIsCgiPlus ();

   if (IsCgiPlus)
   {
      for (;;)
      {
         /* block waiting for the next request */
         CgiLibVar ("");
         CgiLib__PrepareRequest (ProcessRequest);
         CgiLibCgiPlusEOF ();
      }
   }
   else
       CgiLib__PrepareRequest (ProcessRequest);
}

/*****************************************************************************/
/*
(jpp@esme.fr)  Support function for CgiLibInit().
*/

void CgiLib__PrepareRequest (void ProcessRequest(void))

{
   int  PostBufferCount;
   char  *PostBufferPtr,
         *CgiRequestMethodPtr,
         *CgiContentTypePtr;

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

   CgiRequestMethodPtr = CgiLibVar ("WWW_REQUEST_METHOD");
   if (!strcmp (CgiRequestMethodPtr, "POST"))
   {
      CgiContentTypePtr = CgiLibVar ("WWW_CONTENT_TYPE");
      if (strstr (CgiContentTypePtr, "application/x-www-form-urlencoded"))
      {
         /* is URL form encoded, turn that into pseudo CGI variables */
         CgiLibReadRequestBody (&PostBufferPtr, &PostBufferCount);
         CgiLibFormRequestBody (PostBufferPtr, PostBufferCount);
         if (PostBufferPtr == NULL || PostBufferCount == 0)
         {
            CgiLibResponseError (__FILE__, __LINE__, 0, "No body in the POST!");
            return;
         }
         free (PostBufferPtr);
      }
   }
   ProcessRequest ();
}

/*****************************************************************************/
/*
Initialize the scripting environment.  Parameter 'DoResponseCgi' if true sets
the script do return CGI compliant responses regardless of whether it can
support others or not.
*/

void CgiLibEnvironmentInit
(
int argc,
char *argv[],
int DoResponseCgi
)
{
   int  OsuBodyLength;
   char  *OsuBodyPtr,
         *OsuMethodPtr;

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

#ifdef CGILIB_OBJECT_MODULE

   CgiLib__ResponseBodyPtr = CGILIB_RESPONSE_BODY;
   CgiLib__ResponseCharsetPtr = CGILIB_RESPONSE_CHARSET;
   CgiLib__ResponseDefCharsetPtr = CGILIB_RESPONSE_DEFAULT_CHARSET;
   CgiLib__ResponseErrorInfoPtr = CGILIB_RESPONSE_ERROR_INFO;
   CgiLib__ResponseErrorMessagePtr = CGILIB_RESPONSE_ERROR_MESSAGE;
   CgiLib__ResponseErrorStatus = CGILIB_RESPONSE_ERROR_STATUS;
   CgiLib__ResponseErrorTitlePtr = CGILIB_RESPONSE_ERROR_TITLE;
   CgiLib__ResponseSoftwareIdPtr = CGILIB_RESPONSE_SOFTWAREID;
   CgiLib__ResponseSuccessMsgPtr = CGILIB_RESPONSE_SUCCESS_MESSAGE;
   CgiLib__ResponseSuccessStatus = CGILIB_RESPONSE_SUCCESS_STATUS;
   CgiLib__ResponseSuccessTitlePtr = CGILIB_RESPONSE_SUCCESS_TITLE;

#endif

   if (CgiLib__VarNonePtr != NULL && !CgiLib__VarNonePtr[0])
      CgiLib__VarNonePtr = CgiLib__VarEmpty;

   if (CGILIB__DEBUG)
   {
      /* force it to CGI compliant response */
      DoResponseCgi = 1;
      /* CGI interfaces object to this non-CGI output before the header */
      /** if (CGILIB__DEBUG) fprintf (stdout, "CgiLibEnvironmentInit()\n"); **/
   }

   CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_RECORD;

   CgiLib__CgiPlusEofPtr =
      CgiLib__CgiPlusEotPtr =
      CgiLib__CgiPlusEscPtr = NULL;

   CgiLib__ResponseCgi = DoResponseCgi;

   CgiLib__SetEnvironment ();

#if CGILIB_OSU_SUPPORT

   if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU)
   {
      /*******************/
      /* OSU environment */
      /*******************/

      /* OSU is always sent as a binary stream ... *always* */
      CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_BINARY;

      CgiLibOsuInit (argc, argv);

#if CGILIB_POST_SUPPORT

      OsuMethodPtr = CgiLib__GetVar ("WWW_REQUEST_METHOD", "");
      if (strcmp (OsuMethodPtr, "POST") ||
          strcmp (OsuMethodPtr, "PUT"))
      {
         /* any body MUST be read before terminating the dialog phase! */
         CgiLibReadRequestBody (&OsuBodyPtr, &OsuBodyLength);
      }

#endif /* CGILIB_POST_SUPPORT */

      if (CgiLib__ResponseCgi = DoResponseCgi)
         CgiLibOsuStdoutCgi ();
      else
         CgiLibOsuStdoutRaw ();
   }
   else

#endif /* CGILIB_OSU_SUPPORT */

#ifdef blah
   if (CGILIB__DEBUG)
   {
      /* debug output is plain-text */
      fputs ("Content-Type: text/plain\n\n", stdout);
      /* this works with WASD, Apache, doesn't with OSU, others? */
      if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE ||
          CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS ||
          CgiLib__Environment == CGILIB_ENVIRONMENT_WASD)
      {
         fputs ("$ SHOW PROCESS /ALL\n", stdout);
         system ("show process/all");
         fputs ("$ SHOW LOG /PROCESS *\n", stdout);
         system ("show log /process *");
         fputs ("$ SHOW SYMBOL *\n", stdout);
         system ("show symbol *");
      }
   }
   else
#endif
   if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE ||
       CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS ||
       CgiLib__Environment == CGILIB_ENVIRONMENT_WASD)
   {
      /*
         WASD CGI/CGIplus and Apache default is a binary stream.
         Apache needs "fix"ing to allow transfers >=32 kbytes.
         Binary streams are more efficient because the C-RTL buffers
         multiple '\n'-delimited records up to the size of the stdout device
         buffer capacity, then QIOs, rather that QIOing each '\n' record.
      */

      CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_BINARY;
      if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE)
      {
         CgiLib__ApacheFixBg (1);
         stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=bin", "mrs=16384");
      }
      else
         stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=bin");
      if (stdout == NULL) exit (vaxc$errno);
   }

#if CGILIB_RESPONSE_SUPPORT

   CgiLib__ResponseCount = 0;

#endif /* CGILIB_RESPONSE_SUPPORT */
}

/*****************************************************************************/
/*
Determine which environment we're executing within.
*/

void CgiLib__SetEnvironment ()

{
   char  *cptr;

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

   /* CGI interfaces object to this non-CGI output before the header */
   /** if (CGILIB__DEBUG) fprintf (stdout, "CgiLib__EnvironmentCheck()\n"); **/

#if CGILIB_CGIPLUS_SUPPORT

   /* look for WASD request body I/O stream logical */
   if (getenv ("HTTP$INPUT") != NULL)
   {
      /* look for CGIplus stream logical */
      if ((CgiLib__CgiPlusEofPtr = getenv("CGIPLUSEOF")) != NULL)
      {
         CgiLib__Environment = CGILIB_ENVIRONMENT_CGIPLUS;
         return;
      }
   }

#endif /* CGILIB_CGIPLUS_SUPPORT */

#if CGILIB_DEVELOPMENT

   if (getenv ("CGILIB__ENVIRONMENT") != NULL)
   {
      CgiLib__Environment = atoi (getenv ("CGILIB__ENVIRONMENT"));
      CgiLib__CgiPrefixWWW = 1;
      return;
   }

#endif

   CgiLib__Environment = 0;

#if CGILIB_OSU_SUPPORT

   if (getenv ("WWWEXEC_RUNDOWN_STRING") != NULL)
   {
      /* OSU environment */
      CgiLib__Environment = CGILIB_ENVIRONMENT_OSU;
      CgiLib__CgiPrefixWWW = 1;
      return;
   }

#endif /* CGILIB_OSU_SUPPORT */

   /* determine if CGI variables begin with or without "WWW_" */
   cptr = getenv ("WWW_GATEWAY_INTERFACE");
   if (cptr == NULL)
      CgiLib__CgiPrefixWWW = 0;
   else
      CgiLib__CgiPrefixWWW = 1;

   if (getenv ("HTTP$INPUT") != NULL)
   {
      /* WASD request body I/O stream logical detected */

#if CGILIB_CGIPLUS_SUPPORT

      /* look for CGIplus stream */
      if ((CgiLib__CgiPlusEofPtr = getenv("CGIPLUSEOF")) != NULL)
      {
         CgiLib__Environment = CGILIB_ENVIRONMENT_CGIPLUS;
         return;
      }

#endif /* CGILIB_CGIPLUS_SUPPORT */

      CgiLib__Environment = CGILIB_ENVIRONMENT_WASD;
      return;
   }

   if (getenv ("APACHE$SHARED_SOCKET") != NULL)
   {
      CgiLib__Environment = CGILIB_ENVIRONMENT_APACHE;
      return;
   }

   if ((cptr = getenv ("WWW_SERVER_SOFTWARE")) != NULL)
   {
      if (strstr (cptr, "Purveyor") != NULL)
      {
         CgiLib__Environment = CGILIB_ENVIRONMENT_PURVEYOR;
         return;
      }
   }

   /* assume vanilla CGI */
   CgiLib__Environment = CGILIB_ENVIRONMENT_CGI;
}

/*****************************************************************************/
/*
Set the <stdout> stream to binary mode (no translation of '\n', etc.)
*/

void CgiLibEnvironmentBinaryOut ()

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibEnvironmentBinaryOut()\n");

   CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_BINARY;

#if CGILIB_OSU_SUPPORT

   if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) return;

#endif /* CGILIB_OSU_SUPPORT */

   if (CGILIB__DEBUG) return;

   if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE)
      CgiLib__ApacheFixBg (1);

   if ((stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=bin")) == NULL)
      exit (vaxc$errno);
}

/*****************************************************************************/
/*
Set the <stdout> stream is back to record mode.
*/

/* for backward compatibility */
#pragma inline(CgiLibEnvironmentStandardOut)
void CgiLibEnvironmentStandardOut () { CgiLibEnvironmentRecordOut(); }

void CgiLibEnvironmentRecordOut ()

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibEnvironmentRecordOut()\n");

   CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_RECORD;

#if CGILIB_OSU_SUPPORT

   if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) return;

#endif /* CGILIB_OSU_SUPPORT */

   if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE)
      CgiLib__ApacheFixBg (0);

   if ((stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=rec")) == NULL)
      exit (vaxc$errno);
}

/*****************************************************************************/
/*
According to "Compaq Secure Web Server Version 1.0-1 for OpenVMS Alpha (based
on Apache 1.3.12) Version 1.0-1 Release Notes" this is required to support the
transfer of any binary content in excess of 32 kbytes.  This is based on a code
snippet in these release notes, but makes the sharable image activation and
symbol resolution dynamic to allow the object module to be linked on systems
without VMS Apache and/or the APACHE$FIXBG.EXE shareable image available.
'WantBin' if one sets to binary, if 0 resets to record mode.  This toggling has
it's limitations but that's the way APACHE$FIXBG.C works!  This function
assumes the environment starts out in record mode.
*/

void CgiLib__ApacheFixBg (int WantBin)

{
   static int  IsBin;
   static unsigned short  SysOutputChannel;
   static int (*ApacheFixBg)(unsigned short, int);

   static $DESCRIPTOR (StdOutputDsc, "SYS$OUTPUT");
   static $DESCRIPTOR (ApacheFixBgImageDsc, "APACHE$FIXBG");
   static $DESCRIPTOR (ApacheFixBgDsc, "APACHE$FIXBG");

   int  status;

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

   if (CGILIB__DEBUG)
      fprintf (stdout, "CgiLib__ApacheFixBg() %d %d\n", WantBin, IsBin);

   /* if want binary and already binary just return */
   if (WantBin && IsBin) return;
   /* if want record and already record just return */
   if (!WantBin && !IsBin) return;

   IsBin = !IsBin;

   if (SysOutputChannel)
      status = SS$_NORMAL;
   else
   {
      lib$establish (CgiLib__ApacheFixBgHandler);
      status = lib$find_image_symbol (&ApacheFixBgImageDsc, &ApacheFixBgDsc,
                                      &ApacheFixBg, 0);
      if (CGILIB__DEBUG)
         fprintf (stdout, "lib$find_image_symbol() %%X%08.08X\n", status);
      lib$revert ();
      if (!(status & 1)) exit (status);

      status = sys$assign (&StdOutputDsc, &SysOutputChannel, 0, 0);

      atexit (&CgiLib__ApacheExitRecordMode);
   }

   if (status & 1) status = ApacheFixBg (SysOutputChannel, 1);
   if (!(status & 1)) exit (status);
}

/*****************************************************************************/
/*
Just continue, to report an error if the image couldn't be activated or the
required symbol not found.
*/

int CgiLib__ApacheFixBgHandler ()

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLib__ApacheFixBgHandler()\n");

   return (SS$_CONTINUE);
}

/*****************************************************************************/
/*
Set the Apache <stdout> stream is back to record mode.
*/

void CgiLib__ApacheExitRecordMode ()

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLib__ApacheExitRecordMode()\n");

   CgiLib__ApacheFixBg (0);
}

/*****************************************************************************/
/*
Return true or false depending on whether the statement is true or false!
*/

int CgiLibEnvironmentIsApache ()

{
   return (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE);
}

int CgiLibEnvironmentIsCgi ()

{
   return (CgiLib__Environment == CGILIB_ENVIRONMENT_CGI);
}

int CgiLibEnvironmentIsCgiPlus ()

{
   return (CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS);
}

int CgiLibEnvironmentIsOsu ()

{
   return (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU);
}

int CgiLibEnvironmentIsPurveyor ()

{
   return (CgiLib__Environment == CGILIB_ENVIRONMENT_PURVEYOR);
}

int CgiLibEnvironmentIsWasd ()

{
   return (CgiLib__Environment == CGILIB_ENVIRONMENT_WASD);
}

/*****************************************************************************/
/*
Return a pointer to a string with the name of the scripting environment.
For the WASD server append whether it's CGI or CGIplus.
*/

char* CgiLibEnvironmentName ()

{
   static char  Environment [64];

   char  *cptr, *sptr, *zptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibEnvironmentName()\n");

   if (CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS &&
       CgiLib__CgiPlusLoadVariables)
   {
      /* with CGIplus we can't use a variable before the stream is read from */
      return ("WASD (CGIplus)");
   }

   cptr = CgiLib__GetVar ("WWW_SERVER_SOFTWARE", "");
   if (!*cptr) return ("(unknown)");
   if (strstr (cptr, "WASD") == NULL) return (cptr);

   zptr = (sptr = Environment) + sizeof(Environment)-1;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (CgiLib__Environment == CGILIB_ENVIRONMENT_WASD) cptr = " (CGI)";
   if (CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS) cptr = " (CGIplus)";
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   return (Environment);
}

/*****************************************************************************/
/*
Return a pointer to a string with the version of CGILIB.
*/

char* CgiLibEnvironmentVersion ()

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibEnvironmentVersion()\n");

   return (CgiLib__SoftwareID);
}

/*****************************************************************************/
/*
Set or reset the CGILIB debug.  Return previous value.
*/

int CgiLibEnvironmentSetDebug (int SetDebug)

{
   int  PrevDebug;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibEnvironmentSetDebug()\n");

   PrevDebug = CgiLib__Debug;
   CgiLib__Debug = SetDebug;
   return (PrevDebug);
}

/*****************************************************************************/
/*
Set the string value used when a CGI variable return NONE.  This is usually ""
(empty string) or NULL (zero). Return previous value.
*/

char* CgiLibEnvironmentSetVarNone (char* VarNonePtr)

{
   char  *PrevVarNonePtr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibEnvironmentSetVarNone()\n");

   PrevVarNonePtr = CgiLib__VarNonePtr;
   CgiLib__VarNonePtr = VarNonePtr;
   return (PrevVarNonePtr);
}

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

       /******************************/
#endif /* CGILIB_ENVIRONMENT_SUPPORT */
       /******************************/

       /***********************/
#if     CGILIB_RESPONSE_SUPPORT
       /***********************/

/****************************************************************************/
/*
Generate a full HTTP (WASD and OSU) or CGI response header appropriate to the
environment.  302 (redirection) and 401 (authentication) responses are handled. 
Supply NULL for content parameters and the location URL or authentication
header line as parameter three.  The third can be a 'printf()' format string
with associated parameter supplied following.  This can also be used to supply
additional newline terminated header lines into the response.  If a specific
character set is required begin this format string with "; charset=" and then
charset string, followed by a newline character.
*/ 

int CgiLibResponseHeader
(
int StatusCode,
char *ContentType,
...
)
{
   static FILE  *ResponseStdout = NULL;

   int  argcnt,
        charcnt,
        ContentTypeText;
   char  *CharsetPtr,
         *ContentTypeNewLine,
         *FormatStringPtr,
         *RequestTimeGmtPtr,
         *ServerSoftwarePtr;
   va_list  argptr;

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

   va_count (argcnt);

   if (CGILIB__DEBUG)
      fprintf (stdout, "CgiLibResponseHeader() %d %d |%s|\n",
               argcnt, StatusCode, ContentType);

   if (!CgiLib__Environment) CgiLib__SetEnvironment ();

   if (ResponseStdout == NULL)
   {
      if (CgiLib__Environment == CGILIB_ENVIRONMENT_CGI ||
          CgiLib__Environment == CGILIB_ENVIRONMENT_PURVEYOR)
      {
         /* open a permanent, record-oriented <stdout> stream */
         if ((ResponseStdout = fopen ("SYS$OUTPUT:", "w", "ctx=rec")) == NULL)
            exit (vaxc$errno);
      }
      else
         ResponseStdout = stdout;
   }

   if (argcnt > 2)
   {
      /* more response header line(s)/information has been included */
      va_start (argptr, ContentType);
      FormatStringPtr = (char*)va_arg (argptr, char*);
      if (CGILIB__DEBUG) fprintf (stdout, "|%s|\n", FormatStringPtr);
   }
   else
      FormatStringPtr = NULL;

   ContentTypeNewLine = "\n";
   if (ContentType == NULL) ContentType = "";
   if (!ContentType[0]) ContentType = "text/plain";
   if (ContentTypeText = !strncmp (ContentType, "text/", 5))
   {
      if (FormatStringPtr != NULL &&
          (!strncmp (FormatStringPtr, ";charset=", 9) ||
           !strncmp (FormatStringPtr, "; charset=", 10)))
      {
         /* caller has specifically included a character set specification */
         CharsetPtr = ContentTypeNewLine = "";
      }
      else
      {
         /* check script, request and server, before using default */
         if ((CharsetPtr = CGILIB__RESPONSE_CHARSET) == NULL)
         {
            /* there is no script-enforced character set */
            CharsetPtr = CgiLib__GetVar ("WWW_REQUEST_CHARSET", "");
            if (!CharsetPtr[0])
               CharsetPtr = CgiLib__GetVar ("WWW_SERVER_CHARSET", "");
            if (!CharsetPtr[0])
               CharsetPtr = CGILIB__RESPONSE_DEFAULT_CHARSET;
         }
      }
   }
   else
      CharsetPtr = "";

   if (((CgiLib__Environment == CGILIB_ENVIRONMENT_WASD &&
         CgiLib__EnvironmentStdoutType != CGILIB_ENVIRONMENT_STDOUT_RECORD) ||
        CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS ||
        CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) &&
       !CgiLib__ResponseCgi)
   {
      /* full response header */
      ServerSoftwarePtr = CgiLib__GetVar ("WWW_SERVER_SOFTWARE", "");
      if (!ServerSoftwarePtr[0])
         ServerSoftwarePtr = CGILIB__RESPONSE_SOFTWAREID;
      RequestTimeGmtPtr = CgiLibVarNull ("REQUEST_TIME_GMT");

      if (StatusCode == 302)
      {
         charcnt = fprintf (ResponseStdout,
"HTTP/1.0 302 %s\n\
Server: %s\n\
%s%s%s\
Location: ",
            CgiLibHttpStatusCodeText(StatusCode),
            ServerSoftwarePtr,
            RequestTimeGmtPtr == NULL ? "" : "Date: ",
            RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr,
            RequestTimeGmtPtr == NULL ? "" : "\n");
      }
      else
      if (StatusCode == 401)
      {
         charcnt = fprintf (ResponseStdout,
"HTTP/1.0 401 %s\n\
Server: %s\n\
%s%s%s\
WWW-Authenticate: ",
            CgiLibHttpStatusCodeText(StatusCode),
            ServerSoftwarePtr,
            RequestTimeGmtPtr == NULL ? "" : "Date: ",
            RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr,
            RequestTimeGmtPtr == NULL ? "" : "\n");
      }
      else
      if (ContentTypeText)
      {
         charcnt = fprintf (ResponseStdout,
"HTTP/1.0 %d %s\n\
Server: %s\n\
%s%s%s\
Content-Type: %s%s%s%s",
            StatusCode, CgiLibHttpStatusCodeText(StatusCode),
            ServerSoftwarePtr,
            RequestTimeGmtPtr == NULL ? "" : "Date: ",
            RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr,
            RequestTimeGmtPtr == NULL ? "" : "\n",
            ContentType, CharsetPtr[0] ? "; charset=" : "", CharsetPtr,
            ContentTypeNewLine);
      }
      else
      {
         charcnt = fprintf (ResponseStdout,
"HTTP/1.0 %d %s\n\
Server: %s\n\
%s%s%s\
Content-Type: %s\n",
            StatusCode, CgiLibHttpStatusCodeText(StatusCode),
            ServerSoftwarePtr,
            RequestTimeGmtPtr == NULL ? "" : "Date: ",
            RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr,
            RequestTimeGmtPtr == NULL ? "" : "\n",
            ContentType);
      }
   }
   else
   {
      /* vanilla CGI environment/response */
      if (StatusCode == 302)
      {
         if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE)
            charcnt = fprintf (ResponseStdout, "Location: ");
         else
            charcnt = fprintf (ResponseStdout,
"Status: 302 %s\n\
Location: ",
               CgiLibHttpStatusCodeText(StatusCode));
      }
      else
      if (StatusCode == 401)
      {
         charcnt = fprintf (ResponseStdout,
"Status: 401 %s\n\
WWW-Authenticate: ",
            CgiLibHttpStatusCodeText(StatusCode));
      }
      else
      if (ContentTypeText)
      {
         ServerSoftwarePtr = CgiLib__GetVar ("WWW_SERVER_SOFTWARE", "");
         if (strstr (ServerSoftwarePtr, "WASD/5.") != NULL ||
             strstr (ServerSoftwarePtr, "WASD/6.0") != NULL)
         {
            /* bit of backward compatibility for WASD 5.n and 6.0 */
            charcnt = fprintf (ResponseStdout, "Content-Type: %s%s%s%s",
               ContentType, CharsetPtr[0] ? "; charset=" : "", CharsetPtr,
               ContentTypeNewLine);
         }
         else
         {
            /* all others, including WASD 6.1ff */
            if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE &&
                StatusCode / 100 == 2)
               charcnt = fprintf (ResponseStdout, "Content-Type: %s%s%s%s",
                                  ContentType, CharsetPtr[0] ?
                                  "; charset=" : "", CharsetPtr,
                                  ContentTypeNewLine);
            else
               charcnt = fprintf (ResponseStdout,
"Status: %d %s\n\
Content-Type: %s%s%s%s",
                  StatusCode, CgiLibHttpStatusCodeText(StatusCode),
                  ContentType, CharsetPtr[0] ?
                  "; charset=" : "", CharsetPtr,
                  ContentTypeNewLine);
         }
      }
      else
      {
         if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE &&
             StatusCode / 100 == 2)
            charcnt = fprintf (ResponseStdout, "Content-Type: %s\n",
                               ContentType);
         else
            charcnt = fprintf (ResponseStdout,
"Status: %d %s\n\
Content-Type: %s\n",
               StatusCode, CgiLibHttpStatusCodeText(StatusCode), ContentType);
      }
   }

   if (argcnt > 2)
   {
      /* more response header line(s)/information has been included */
      charcnt += vfprintf (ResponseStdout, FormatStringPtr, argptr);
      va_end (argptr);
   }

   if (StatusCode == 302 ||
       StatusCode == 401)
   {
      fputs ("\n", ResponseStdout);
      charcnt++;
   }

   fputs ("\n", ResponseStdout);
   charcnt++;

   fflush (ResponseStdout);

   CgiLib__ResponseCount += charcnt;

   return (charcnt);
}

/****************************************************************************/
/*
Generate an HTTP 302 redirect response.  If the format string begins "%!" then
the redirect is made absolute with the appropriate local server host name and
if necessary port prefixing whatever are supplied as parameters.  If the "%!"
does not precede the format string then it can either be a full absolute or
local redirection URL, it's just appended to the "Location: " field name.  The
format string can be for a 'printf()' with associated parameters supplied.
Strings fed to this function must be appropriately URL-encoded if necessary.
*/

int CgiLibResponseRedirect
(
char *FormatString,
...
)
{
   int  argcnt,
        charcnt;
   char  *AbsolutePtr,
         *FormatStringPtr,
         *HttpHostPtr,
         *HostPortPtr,
         *RequestTimeGmtPtr,
         *ServerNamePtr,
         *ServerPortPtr,
         *ServerSoftwarePtr;
   char  HostPort [256];
   va_list  argptr;

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

   va_count (argcnt);

   if (CGILIB__DEBUG)
      fprintf (stdout, "CgiLibResponseRedirect() %d |%s|\n",
               argcnt, FormatString);

   FormatStringPtr = FormatString;

   if (*(unsigned short*)FormatStringPtr == '%!')
   {
      FormatStringPtr += 2;
      AbsolutePtr = "//";

      HttpHostPtr = CgiLib__GetVar ("WWW_HTTP_HOST", "");
      if (HttpHostPtr[0])
      {
         /* use the request supplied host[:port] */
         HostPortPtr = HttpHostPtr;
      }
      else
      {
         /* build it up from the server name and port */
         ServerNamePtr = CgiLib__GetVar ("WWW_SERVER_NAME", "");
         ServerPortPtr = CgiLib__GetVar ("WWW_SERVER_PORT", "");
         if (!strcmp (ServerPortPtr, "80") ||
             !strcmp (ServerPortPtr, "443")) 
            HostPortPtr = ServerNamePtr;
         else
            sprintf (HostPortPtr = HostPort, "%s:%s",
                     ServerNamePtr, ServerPortPtr);
      }
   }
   else
      HostPortPtr = AbsolutePtr = "";

   if (((CgiLib__Environment == CGILIB_ENVIRONMENT_WASD &&
         CgiLib__EnvironmentStdoutType != CGILIB_ENVIRONMENT_STDOUT_RECORD) ||
        CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS ||
        CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) &&
       !CgiLib__ResponseCgi)
   {
      /* full response header */
      ServerSoftwarePtr = CgiLib__GetVar ("WWW_SERVER_SOFTWARE", "");
      if (!ServerSoftwarePtr[0])
         ServerSoftwarePtr = CGILIB__RESPONSE_SOFTWAREID;
      RequestTimeGmtPtr = CgiLibVarNull ("REQUEST_TIME_GMT");

      charcnt = fprintf (stdout,
"HTTP/1.0 302 %s\n\
Server: %s\n\
%s%s%s\
Location: %s%s",
         CgiLibHttpStatusCodeText(302), ServerSoftwarePtr,
         RequestTimeGmtPtr == NULL ? "" : "Date: ",
         RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr,
         RequestTimeGmtPtr == NULL ? "" : "\n",
         AbsolutePtr, HostPortPtr);
   }
   else
   {
      /* vanilla CGI environment/response */
      charcnt = fprintf (stdout,
"Status: 302 %s\n\
Location: %s%s",
         CgiLibHttpStatusCodeText(302),
         AbsolutePtr, HostPortPtr);
   }

   va_start (argptr, FormatString);
   vfprintf (stdout, FormatStringPtr, argptr);
   va_end (argptr);

   fputs ("\n\n", stdout);
   charcnt += 2;

   return (charcnt);
}

/*****************************************************************************/
/*
Generate an error report in the quasi-standard WASD format.  If 'StatusValue'
is non-zero then it is assumed to be a VMS status code and a message generated
from it.  If zero then only the format string is used.  The format string may
be a plain-text message or contain 'printf()' formatting characters with any
associated parameters. 
*/

int CgiLibResponseError
(
char *SrcFileName,
int SrcLineNumber,
int StatusValue,
char *FormatString,
...
)
{
   int  argcnt,
        charcnt,
        status,
        FullResponse;
   short int  Length;
   char  *cptr, *sptr, *zptr;
   char  FileName [128];
   va_list  argptr;

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

   va_count (argcnt);

   if (CGILIB__DEBUG)
      fprintf (stdout, "CgiLibResponseError() %d %d\n", argcnt, StatusValue);

   FullResponse = !CgiLib__ResponseCount;

   charcnt = 0;

   /* 
      The source file format provided by the "__FILE__" macro will be
      "device:[directory]name.type;ver".  Reduce that to "name.type".
   */
   zptr = (sptr = FileName) + sizeof(FileName)-1;
   for (cptr = SrcFileName; *cptr; cptr++);
   while (cptr > SrcFileName && *cptr != ']') cptr--;
   if (*cptr == ']')
   {
      cptr++;
      while (*cptr && *cptr != ';' && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
   }
   else
   {
      /* assume it's just a module name */
      strncpy (FileName, cptr, sizeof(FileName));
      FileName[sizeof(FileName)-1] = '\0';
   }

   if (FullResponse)
   {
      CgiLibResponseHeader (CGILIB__RESPONSE_ERROR_STATUS, "text/html");
      charcnt += fprintf (stdout,
"<HTML>\n\
<HEAD>\n\
<META NAME=\"generator\" CONTENT=\"%s\">\n\
<META NAME=\"module\" CONTENT=\"%s\">\n\
<META NAME=\"line\" CONTENT=\"%d\">\n\
<META NAME=\"environment\" CONTENT=\"%s\">\n\
<TITLE>%s %d %s</TITLE>\n\
</HEAD>\n\
<BODY%s%s>\n",
         CGILIB__RESPONSE_SOFTWAREID, FileName, SrcLineNumber,
         CgiLibEnvironmentName(),
         CGILIB__RESPONSE_ERROR_TITLE, CGILIB__RESPONSE_ERROR_STATUS,
         CgiLibHttpStatusCodeText(CGILIB__RESPONSE_ERROR_STATUS),
         CGILIB__RESPONSE_BODY ? " " : "", CGILIB__RESPONSE_BODY);
   }
   else
   {
      charcnt += fprintf (stdout,
"\n<P><HR SIZE=3 WIDTH=100%% NOSHADE>\n\
<!-- SoftwareID: %s Module: %s Line: %d -->\n",
          CGILIB__RESPONSE_SOFTWAREID, FileName, SrcLineNumber);
   }

   charcnt += fprintf (stdout,
"<FONT SIZE=+1><B>%s %d</B> &nbsp;-&nbsp; %s</FONT>\n",
      CGILIB__RESPONSE_ERROR_TITLE,
      CGILIB__RESPONSE_ERROR_STATUS,
      CGILIB__RESPONSE_ERROR_MESSAGE);

   if (StatusValue)
   {
#     pragma message __save
#     pragma message disable (ADDRCONSTEXT)
      char  StatusMsg [256];
      $DESCRIPTOR (StatusMsgDsc, StatusMsg);

      /* cater for VAX environment as best we can */
#     pragma message disable (IMPLICITFUNC)
#     ifdef CGILIB_OBJECT_MODULE
#        ifdef __VAX
#           define SYS$GETMSG sys$getmsg
#        endif
#     endif
      status = sys$getmsg (StatusValue, &Length, &StatusMsgDsc, 1, 0);
#     pragma message __restore

      if (status & 1)
      {
         StatusMsg[Length] = '\0';
         StatusMsg[0] = toupper(StatusMsg[0]);
         charcnt += fprintf (stdout, "<P><!-- %%X%08.08X -->%s ... ",
                             StatusValue, StatusMsg);
      }
      else
         exit (status);
   }
   else
   {
      fputs ("<P>", stdout);
      charcnt += 4;
   }

   va_start (argptr, FormatString);
   charcnt += vfprintf (stdout, FormatString, argptr);
   va_end (argptr);

   fputs ("\n", stdout);
   charcnt++;

   if (CGILIB__RESPONSE_ERROR_INFO != NULL &&
       CGILIB__RESPONSE_ERROR_INFO[0])
      charcnt += fprintf (stdout, "<P>%s", CGILIB__RESPONSE_ERROR_INFO);

   cptr = CgiLib__GetVar ("WWW_SERVER_SIGNATURE", "");
   if (cptr[0])
      charcnt += fprintf (stdout,
                    "<P><HR WIDTH=85%% ALIGN=left SIZE=2 NOSHADE>\n%s", cptr);

   if (FullResponse)
   {
      fputs (
"</BODY>\n\
</HTML>\n",
         stdout);
      charcnt += 16;
   }

   CgiLib__ResponseCount += charcnt;

   return (charcnt);
}

/*****************************************************************************/
/*
Generate a "success" report in the defacto standard WASD format.  The format
string may be a plain-text message or contain 'printf()' formatting characters
with any associated parameters. 
*/

int CgiLibResponseSuccess
(
char *FormatString,
...
)
{
   int  argcnt,
        charcnt;
   short int  Length;
   char  *cptr;
   va_list  argptr;

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

   va_count (argcnt);

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibResponseSuccess() %d\n", argcnt);

   charcnt = 0;

   CgiLibResponseHeader (CGILIB__RESPONSE_SUCCESS_STATUS, "text/html");
   charcnt += fprintf (stdout,
"<HTML>\n\
<HEAD>\n\
<META NAME=\"generator\" CONTENT=\"%s\">\n\
<META NAME=\"environment\" CONTENT=\"%s\">\n\
<TITLE>%s %d %s</TITLE>\n\
</HEAD>\n\
<BODY%s%s>\n\
<FONT SIZE=+1><B>%s %d</B> &nbsp;-&nbsp; %s</FONT>\n\
<P>",
      CGILIB__RESPONSE_SOFTWAREID,
      CgiLibEnvironmentName(),
      CGILIB__RESPONSE_SUCCESS_TITLE, CGILIB__RESPONSE_SUCCESS_STATUS,
      CgiLibHttpStatusCodeText(CGILIB__RESPONSE_SUCCESS_STATUS),
      CGILIB__RESPONSE_BODY ? " " : "", CGILIB__RESPONSE_BODY,
      CGILIB__RESPONSE_SUCCESS_TITLE,
      CGILIB__RESPONSE_SUCCESS_STATUS,
      CGILIB__RESPONSE_SUCCESS_MESSAGE);

   va_start (argptr, FormatString);
   charcnt += vfprintf (stdout, FormatString, argptr);
   va_end (argptr);

   cptr = CgiLib__GetVar ("WWW_SERVER_SIGNATURE", "");
   if (cptr[0])
      charcnt += fprintf (stdout,
                    "<P><HR WIDTH=85%% ALIGN=left SIZE=2 NOSHADE>\n%s", cptr);

   fputs (
"</BODY>\n\
</HTML>\n",
      stdout);
   charcnt += 17;

   CgiLib__ResponseCount += charcnt;

   return (charcnt);
}

/*****************************************************************************/
/*
Set the <BODY> tag parameters.  Returns pointer to any previous.
*/

char* CgiLibResponseSetBody (char *cptr)

{
   char  *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibResponseSetBody()\n");

   sptr = CgiLib__ResponseBodyPtr;
   CgiLib__ResponseBodyPtr = cptr;
   return (sptr);
}

/*****************************************************************************/
/*
Set the response character set.  Returns pointer to any previous.
*/

char* CgiLibResponseSetCharset (char *cptr)

{
   char  *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibResponseSetCharset()\n");

   sptr = CgiLib__ResponseCharsetPtr;
   CgiLib__ResponseCharsetPtr = cptr;
   return (sptr);
}

/*****************************************************************************/
/*
Set the default character set.  Returns pointer to any previous.
*/

char* CgiLibResponseSetDefaultCharset (char *cptr)

{
   char  *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibResponseSetDefaultCharset()\n");

   sptr = CgiLib__ResponseDefCharsetPtr;
   CgiLib__ResponseDefCharsetPtr = cptr;
   return (sptr);
}

/*****************************************************************************/
/*
Set the error message.  Returns pointer to any previous.
*/

char* CgiLibResponseSetErrorMessage (char *cptr)

{
   char  *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibResponseSetErrorMessage()\n");

   sptr = CgiLib__ResponseErrorMessagePtr;
   CgiLib__ResponseErrorMessagePtr = cptr;
   return (sptr);
}

/*****************************************************************************/
/*
Set the error HTTP status code.  Returns pointer to any previous.
*/

int CgiLibResponseSetErrorStatus (int status)

{
   int  pstatus;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibResponseSetErrorStatus()\n");

   pstatus = CgiLib__ResponseErrorStatus;
   CgiLib__ResponseErrorStatus = status;
   return (pstatus);
}

/*****************************************************************************/
/*
Set the error <TITLE> string.  Returns pointer to any previous.
*/

char* CgiLibResponseSetErrorTitle (char *cptr)

{
   char  *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibResponseSetErrorTitle()\n");

   sptr = CgiLib__ResponseErrorTitlePtr;
   CgiLib__ResponseErrorTitlePtr = cptr;
   return (sptr);
}

/*****************************************************************************/
/*
Set the error additional information string.  Returns pointer to any previous.
*/

char* CgiLibResponseSetErrorInfo (char *cptr)

{
   char  *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibResponseSetErrorInfo()\n");

   sptr = CgiLib__ResponseErrorInfoPtr;
   CgiLib__ResponseErrorInfoPtr = cptr;
   return (sptr);
}

/*****************************************************************************/
/*
Set the success message string.  Returns pointer to any previous.
*/

char* CgiLibResponseSetSuccessMessage (char *cptr)

{
   char  *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibResponseSetSuccessMessage()\n");

   sptr = CgiLib__ResponseSuccessMsgPtr;
   CgiLib__ResponseSuccessMsgPtr = cptr;
   return (sptr);
}

/*****************************************************************************/
/*
Set the success HTTP status code.  Returns pointer to any previous.
*/

int CgiLibResponseSetSuccessStatus (int status)

{
   int  pstatus;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibResponseSetSuccessStatus()\n");

   pstatus = CgiLib__ResponseSuccessStatus;
   CgiLib__ResponseSuccessStatus = status;
   return (pstatus);
}

/*****************************************************************************/
/*
Set the success <TITLE> string.  Returns pointer to any previous.
*/

char* CgiLibResponseSetSuccessTitle (char *cptr)

{
   char  *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibResponseSetSuccessTitle()\n");

   sptr = CgiLib__ResponseSuccessTitlePtr;
   CgiLib__ResponseSuccessTitlePtr = cptr;
   return (sptr);
}

/*****************************************************************************/
/*
Set the software ID string.  Returns pointer to any previous.
*/

char* CgiLibResponseSetSoftwareID (char *cptr)

{
   char  *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibResponseSetSoftwareID()\n");

   sptr = CgiLib__ResponseSoftwareIdPtr;
   CgiLib__ResponseSoftwareIdPtr = cptr;
   if (CGILIB__DEBUG) fprintf (stdout, "|%s|%s|\n", cptr, sptr);
   return (sptr);
}

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

       /***************************/
#endif /* CGILIB_RESPONSE_SUPPORT */
       /***************************/

#if CGILIB_GENERAL_SUPPORT
#   define CGILIB_URL_DECODE 1
#endif
#if CGILIB_CGIVAR_NON_WASD
#   define CGILIB_URL_DECODE 1
#endif
#if CGILIB_POST_SUPPORT
#   define CGILIB_URL_DECODE 1
#endif
#ifndef CGILIB_URL_DECODE
#   define CGILIB_URL_DECODE 0
#endif

       /*****************/
#if     CGILIB_URL_DECODE
       /*****************/

/*****************************************************************************/
/*
Decode URL-encoded string.  Resultant string is always the same size or smaller
so it can be done in-situ!  Returns the size of the resultant string.
*/

int CgiLibUrlDecode (char *String)

{
   char  ch;
   char  *cptr, *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibUrlDecode() |%s|\n", String);

   cptr = sptr = String;
   while (*cptr)
   {
      switch (*cptr)
      {
         case '+' :

            *sptr++ = ' ';
            cptr++;

            continue;

         case '%' :

            cptr++;
            while (*cptr == '\r' || *cptr == '\n') cptr++;
            ch = 0;
            if (*cptr >= '0' && *cptr <= '9')
               ch = (*cptr - (int)'0') << 4;
            else
            if (toupper(*cptr) >= 'A' && toupper(*cptr) <= 'F')
               ch = (toupper(*cptr) - (int)'A' + 10) << 4;
            else
            {
               *String = '\0';
               return (-1);
            }
            if (*cptr) cptr++;
            while (*cptr == '\r' || *cptr == '\n') cptr++;
            if (*cptr >= '0' && *cptr <= '9')
               ch += (*cptr - (int)'0');
            else
            if (toupper(*cptr) >= 'A' && toupper(*cptr) <= 'F')
               ch += (toupper(*cptr) - (int)'A' + 10);
            else
            {
               *String = '\0';
               return (-1);
            }
            if (*cptr) cptr++;
            *sptr++ = ch;

            continue;

         case '\r' :
         case '\n' :

            cptr++;
            continue;

         default :
            
            *sptr++ = *cptr++;

      }
   }
   *sptr = '\0';

   if (CGILIB__DEBUG) fprintf (stdout, "|%s|\n", String);
   return (sptr-String);
}

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

       /*********************/
#endif /* CGILIB_URL_DECODE */
       /*********************/

       /**********************/
#if     CGILIB_GENERAL_SUPPORT
       /**********************/

/****************************************************************************/
/*
URL-encode (nearly) all non-alpha-numeric characters. If 'EncodedString' is
NULL sufficient memory will be dynamically allocated to hold the encoded
string.  For fixed size 'EncodedString' it will not overflow the supplied
string but does not return any indication it reached the limit.  Returns the
length of the encoded string or a pointer to the encoded string (which should
be cast to (char*) by the calling routine). 
*/ 
 
int CgiLibUrlEncode
(
char *PlainString,
int NumberOfChars,
char *EncodedString,
int SizeOfEncodedString
)
{
   static char  EmptyString [] = "",
                HexDigits [] = "0123456789abcdef";

   int  ccnt,
        CurrentLength,
        EncodedSize;
   char  *cptr, *sptr, *zptr,
         *EncodedPtr;

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

   if (CGILIB__DEBUG)
      fprintf (stdout, "CgiLibUrlEncode() |%s| %d\n",
               PlainString, NumberOfChars);

   if (!*(cptr = PlainString))
   {
      if (EncodedString == NULL)
         return ((int)EmptyString);
      else
      {
         EncodedString[0] = '\0';
         return (0);
      }
   }

   if (EncodedString == NULL)
   {
      sptr = zptr = EncodedPtr = NULL;
      EncodedSize = 0;
   }
   else
   {
      sptr = EncodedPtr = EncodedString;
      if (SizeOfEncodedString == -1)
         zptr = (char*)INT_MAX;
      else
         zptr = sptr + SizeOfEncodedString - 1;
   }

   if (NumberOfChars == -1)
      ccnt = INT_MAX;
   else
      ccnt = NumberOfChars;

   while (*cptr && ccnt)
   {
      if (EncodedString == NULL && sptr >= zptr)
      {
         /* out of storage (or first use) get more (or some) memory */
         CurrentLength = sptr - EncodedPtr;
         if ((EncodedPtr =
              realloc (EncodedPtr, EncodedSize+CGILIB_ENCODED_CHUNK)) == NULL)
            exit (SS$_INSFMEM);
         EncodedSize += CGILIB_ENCODED_CHUNK;
         /* recalculate pointers */
         sptr = EncodedPtr + CurrentLength;
         /* three allows a worst-case %nn encoding without failing */
         zptr = EncodedPtr + EncodedSize - 3;
      }
      else
      if (sptr >= zptr)
         break;

      if (isalnum(*cptr) ||
          *cptr == '/' ||
          *cptr == '-' ||
          *cptr == '_' ||
          *cptr == '.' ||
          /* strictly, the following characters should be encoded */
          *cptr == '$' ||
          *cptr == '*' ||
          *cptr == ';' ||
          *cptr == '~' ||
          *cptr == '^')
      {
         *sptr++ = *cptr++;
         ccnt--;
         continue;
      }

      if (sptr < zptr) *sptr++ = '%';
      if (sptr < zptr) *sptr++ = HexDigits[(*cptr & 0xf0) >> 4];
      if (sptr < zptr) *sptr++ = HexDigits[*cptr & 0x0f];
      cptr++;
      ccnt--;
   }
   *sptr = '\0';
   if (CGILIB__DEBUG)
      fprintf (stdout, "%d |%s|\n", sptr-EncodedPtr, EncodedPtr);

   if (EncodedString == NULL)
      return ((int)EncodedPtr);
   else
      return (sptr - EncodedPtr);
}
 
/*****************************************************************************/
/*
URL-encode (%nn) a possibly extended specification file name.  This can be done
in two ways.  Leave the VMS extended file specification escape sequences in
place (e.g. "^_") but encode absolutely forbidden characters (.e.g ' ', '?',
"%", etc.), called 'RelaxedEncode' here.  Or a strict encode, where the
extended escaped sequences are first un-escaped into characters, then those
charaters URL-encoded as necessary. If 'ForceLower' true, and no extended file
specification characters were found (e.g. lower-case, escaped characters) then
replace all upper-case alphabetics with lower-case.

If 'EncodedString' is NULL sufficient memory will be dynamically allocated to
hold the encoded string.  For fixed size 'EncodedString' it will not overflow
the supplied string but does not return any indication it reached the limit. 
Returns the length of the encoded string or a pointer to the encoded string
(which should be cast to (char*) by the calling routine). 
*/ 

int CgiLibUrlEncodeFileName
(
char *FileName,
char *EncodedString,
int SizeOfEncodedString,
int RelaxedEncode,
int ForceLower
)
{
   static char  EmptyString [] = "",
                HexDigits [] = "0123456789abcdef";

   int  CurrentLength,
        EncodedSize,
        HitExtended;
   char  ch;
   char  *cptr, *sptr, *zptr,
         *EncodedPtr;

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

   if (CGILIB__DEBUG)
      fprintf (stdout, "CgiLibUrlEncodeFileName() |%s| %d %d\n",
               FileName, RelaxedEncode, ForceLower);

   if (!*(cptr = FileName))
   {
      if (EncodedString == NULL)
         return ((int)EmptyString);
      else
      {
         EncodedString[0] = '\0';
         return (0);
      }
   }

   if (EncodedString == NULL)
   {
      sptr = zptr = EncodedPtr = NULL;
      EncodedSize = 0;
   }
   else
   {
      sptr = EncodedPtr = EncodedString;
      if (SizeOfEncodedString == -1)
         zptr = (char*)INT_MAX;
      else
         zptr = sptr + SizeOfEncodedString - 1;
   }

   HitExtended = 0;
   cptr = FileName;
   while (ch = *cptr)
   {
      if (EncodedString == NULL && sptr >= zptr)
      {
         /* out of storage (or first use) get more (or some) memory */
         CurrentLength = sptr - EncodedPtr;
         if ((EncodedPtr =
              realloc (EncodedPtr, EncodedSize+CGILIB_ENCODED_CHUNK)) == NULL)
            exit (SS$_INSFMEM);
         EncodedSize += CGILIB_ENCODED_CHUNK;
         /* recalculate pointers */
         sptr = EncodedPtr + CurrentLength;
         /* three allows a worst-case %nn encoding without failing */
         zptr = EncodedPtr + EncodedSize - 3;
      }
      else
      if (sptr >= zptr)
         break;

      if (islower(ch)) HitExtended = 1;

      if (!RelaxedEncode && ch == '^')
      {
         /* extended file specification escape character */
         HitExtended = 1;
         ch = *++cptr;
         switch (ch)
         {
            case '!' :
            case '#' :
            case '&' :
            case '\'' :
            case '`' :
            case '(' :
            case ')' :
            case '+' :
            case '@' :
            case '{' :
            case '}' :
            case '.' :
            case ',' :
            case ';' :
            case '[' :
            case ']' :
            case '%' :
            case '^' :
            case '=' :
            case ' ' :
               break;
            case '_' :
               ch = ' ';
               break;
            default :
               if (isxdigit (ch))
               {
                  ch = 0;
                  if (*cptr >= '0' && *cptr <= '9')
                     ch = (*cptr - (int)'0') << 4;
                  else
                  if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
                     ch = (tolower(*cptr) - (int)'a' + 10) << 4;
                  cptr++;   
                  if (*cptr >= '0' && *cptr <= '9')
                     ch += (*cptr - (int)'0');
                  else
                  if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
                     ch += (tolower(*cptr) - (int)'a' + 10);
               }
               else
                  ch = *cptr;
         }
      }

      /* URL-encode if necessary */
      if (isalnum(ch) ||
          ch == '/' ||
          ch == '-' ||
          ch == '_' ||
          ch == '.' ||
          ch == '$' ||
          ch == '*' ||
          ch == ':' ||
          ch == ';' ||
          (RelaxedEncode &&
           (ch == ',' ||
            ch == '\'' ||
            ch == '=' ||
            ch == '~' ||
            ch == '^' ||
            ch == '[' ||
            ch == ']' ||
            ch == '(' ||
            ch == ')' ||
            ch == '{' ||
            ch == '}' )))
         *sptr++ = ch;
      else
      {
         if (sptr < zptr) *sptr++ = '%';
         if (sptr < zptr) *sptr++ = HexDigits[(ch & 0xf0) >> 4];
         if (sptr < zptr) *sptr++ = HexDigits[ch & 0x0f];
      }
      if (*cptr) cptr++;
   }

   *sptr = '\0';

   /* if it's not an extended specification then force to lower-case */
   if (ForceLower && !HitExtended)
      for (sptr = EncodedPtr; *sptr; sptr++)
         if (isupper(*sptr)) *sptr = tolower(*sptr);

   if (CGILIB__DEBUG)
      fprintf (stdout, "%d |%s|\n", sptr-EncodedPtr, EncodedPtr);

   if (EncodedString == NULL)
      return ((int)EncodedPtr);
   else
      return (sptr - EncodedPtr);
}

/****************************************************************************/
/*
Escape plain-text characters forbidden to occur in HTML documents (i.e. '<',
'>' and '&').  If 'EscapedString' is NULL sufficient memory will be dynamically
allocated to hold the encoded string.  For fixed size 'EscapedString' it will
not overflow the supplied string but does not return any indication it reached
the limit.  Returns the length of the escaped string or a pointer to the
encoded string (which should be cast to (char*) by the calling routine).
'AnchorUrls' may be used to turn HTML URLs into HTML anchors in the text by
calling with CGILIB_ANCHOR_WEB.  It will also attempt to detect mail addresses
(anything like 'this@wherever.host.name') and create "mailto:" links out of
them, call with CGILIB_ANCHOR_MAIL.  To get both use a bit-wise OR.
*/ 
 
int CgiLibHtmlEscape
(
char *PlainString,
int NumberOfChars,
char *EscapedString,
int SizeOfEscapedString
)
{
   if (CGILIB__DEBUG)
      fprintf (stdout, "CgiLibHtmlEscape() |%s| %d\n",
               PlainString, NumberOfChars);

   return (CgiLibAnchorHtmlEscape (PlainString, NumberOfChars,
                                   EscapedString, SizeOfEscapedString,
                                   CGILIB_ANCHOR_NONE));
}

int CgiLibAnchorHtmlEscape
(
char *PlainString,
int NumberOfChars,
char *EscapedString,
int SizeOfEscapedString,
int AnchorUrls
)
{
   static char  EmptyString [] = "";

   int  ccnt, tcnt,
        AtCount,
        CurrentLength,
        EscapedSize,
        PeriodCount,
        UrlEscapedLength,
        WasMailAddress;
   char  *cptr, *sptr, *tptr, *zptr,
         *EscapedPtr,
         *UrlAnchorPtr,
         *UrlEscapedPtr;
   char  UrlOriginal [256];

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

   if (CGILIB__DEBUG)
      fprintf (stdout, "CgiLibAnchorHtmlEscape() |%s| %d %d\n",
               PlainString, NumberOfChars, AnchorUrls);

   if (!*(cptr = PlainString))
   {
      if (EscapedString == NULL)
         return ((int)EmptyString);
      else
      {
         EscapedString[0] = '\0';
         return (0);
      }
   }

   if (EscapedString == NULL)
   {
      sptr = zptr = EscapedPtr = NULL;
      EscapedSize = 0;
   }
   else
   {
      sptr = EscapedPtr = EscapedString;
      if (SizeOfEscapedString == -1)
         zptr = (char*)INT_MAX;
      else
         zptr = sptr + SizeOfEscapedString - 1;
   }

   if (NumberOfChars == -1)
      ccnt = INT_MAX;
   else
      ccnt = NumberOfChars;

   while (*cptr && ccnt)
   {
      if (AnchorUrls != CGILIB_ANCHOR_NONE)
      {
         WasMailAddress = 0;
         UrlOriginal[0] = '\0';

         if ((AnchorUrls & CGILIB_ANCHOR_WEB) &&
             ccnt >= 6 &&
             (cptr[3] == ':' || cptr[4] == ':' ||
              cptr[5] == ':' || cptr[6] == ':') &&
             (!memcmp (cptr, "http://", 7) ||
              !memcmp (cptr, "https://", 8) ||
              !memcmp (cptr, "mailto:", 7) ||
              !memcmp (cptr, "ftp://", 6) ||
              !memcmp (cptr, "gopher://", 9) ||
              !memcmp (cptr, "news://", 7) ||
              !memcmp (cptr, "wais://", 7)))
         {
            /***********/
            /* web URL */
            /***********/

            tptr = UrlOriginal;
            while (ccnt && *cptr && !isspace(*cptr) &&
                   tptr < UrlOriginal+sizeof(UrlOriginal)-1)
            {
               *tptr++ = *cptr++;
               ccnt--;
            }
            if (tptr < UrlOriginal+sizeof(UrlOriginal)-1)
            {
               /* if trailed by what is probably textual punctuation */
               while (tptr > UrlOriginal &&
                      (tptr[-1] == '.' ||
                       tptr[-1] == ',' ||
                       tptr[-1] == ')' ||
                       tptr[-1] == ']' ||
                       tptr[-1] == '>' ||
                       tptr[-1] == '\"' ||
                       tptr[-1] == '\'' ||
                       tptr[-1] == '!'))
               {
                  tptr--;
                  cptr--;
               }
            }
            *tptr = '\0';
         }
         else
         if ((AnchorUrls & CGILIB_ANCHOR_MAIL) &&
             isalnum (*cptr))
         {
            /************************************/
            /* look for a possible mail address */
            /************************************/

            AtCount = PeriodCount = 0;
            tcnt = ccnt;
            tptr = cptr;
            while (ccnt && *cptr &&
                   (isalnum(*cptr) || *cptr == '@' || *cptr == '.' ||
                                      *cptr == '_' || *cptr == '-'))
            {
               if (*cptr == '@')
               {
                  PeriodCount = 0;
                  AtCount++;
               }
               else
               if (*cptr == '.')
                  PeriodCount++;
               ccnt--;
               cptr++;
            }
            /* mail addresses shouldn't have a trailing period! */
            if (*(cptr-1) == '.' && PeriodCount) PeriodCount--;
            ccnt = tcnt;
            cptr = tptr;
            if (WasMailAddress = (AtCount == 1 && PeriodCount >= 1))
            {
               /***************************/
               /* mail address (probably) */
               /***************************/

               tptr = UrlOriginal;
               /* fudge the scheme */
               strcpy (tptr, "mailto:");
               tptr += 7;

               while (ccnt && *cptr &&
                      (isalnum(*cptr) || *cptr == '@' || *cptr == '.' ||
                                         *cptr == '_' || *cptr == '-') &&
                      tptr < UrlOriginal+sizeof(UrlOriginal)-1)
               {
                  ccnt--;
                  *tptr++ = *cptr++;
               }
               /* mail addresses shouldn't have a trailing period! */
               if (*(cptr-1) == '.')
               {
                  ccnt++;
                  cptr--;
                  tptr--;
               }
               *tptr = '\0';
            }
         }

         if (UrlOriginal[0])
         {
            /***************************/
            /* make URL into an anchor */
            /***************************/

            UrlEscapedPtr = (char*)CgiLibHtmlEscape (UrlOriginal, -1, NULL, 0);
            UrlEscapedLength = strlen(UrlEscapedPtr);

            if (CGILIB__DEBUG)
               fprintf (stdout, "Url |%s|%s|\n", UrlOriginal, UrlEscapedPtr);

            tptr = UrlAnchorPtr = calloc (1, (UrlEscapedLength*2)+64);
            if (tptr == NULL) exit (SS$_INSFMEM);
            strcpy (tptr, "<A HREF=\"");
            tptr += 9;
            strcpy (tptr, UrlEscapedPtr);
            tptr += UrlEscapedLength;
            strcpy (tptr, "\" TARGET=_top>");
            tptr += 14;
            if (WasMailAddress)
            {
               /* don't need the introduced "mailto:" in the text */
               strcpy (tptr, UrlEscapedPtr+7);
               tptr += UrlEscapedLength-7;
            }
            else
            {
               strcpy (tptr, UrlEscapedPtr);
               tptr += UrlEscapedLength;
            }
            strcpy (tptr, "</A>");

            if (CGILIB__DEBUG)
               fprintf (stdout, "UrlAnchorPtr |%s|\n", UrlAnchorPtr);

            for (tptr = UrlAnchorPtr; *tptr; *sptr++ = *tptr++)
            {
               if (EscapedString == NULL && sptr >= zptr)
               {
                  /* out of storage (or first use) get more (or some) memory */
                  CurrentLength = sptr - EscapedPtr;
                  if ((EscapedPtr =
                       realloc (EscapedPtr, EscapedSize+CGILIB_ESCAPED_CHUNK))
                      == NULL)
                     exit (SS$_INSFMEM);
                  EscapedSize += CGILIB_ESCAPED_CHUNK;
                  /* recalculate pointers */
                  sptr = EscapedPtr + CurrentLength;
                  zptr = EscapedPtr + EscapedSize - 1;
               }
               else
               if (sptr >= zptr)
                  break;            
            }

            free (UrlAnchorPtr);
            free (UrlEscapedPtr);

            continue;
         }
      }

      /********************/
      /* simple character */
      /********************/

      /* 'sptr+6' is the "worst-case" character substitution */
      if (EscapedString == NULL && sptr+6 >= zptr)
      {
         /* out of storage (or first use) get more (or some) memory */
         CurrentLength = sptr - EscapedPtr;
         if ((EscapedPtr =
              realloc (EscapedPtr, EscapedSize+CGILIB_ESCAPED_CHUNK)) == NULL)
            exit (SS$_INSFMEM);
         EscapedSize += CGILIB_ESCAPED_CHUNK;
         /* recalculate pointers */
         sptr = EscapedPtr + CurrentLength;
         zptr = EscapedPtr + EscapedSize - 1;
      }
      else
      if (sptr+6 >= zptr)
         break;

      switch (*cptr)
      {
         case '<' :
            memcpy (sptr, "&lt;", 4); sptr += 4; cptr++; ccnt--; continue;
         case '>' :
            memcpy (sptr, "&gt;", 4); sptr += 4; cptr++; ccnt--; continue;
         case '&' :
            memcpy (sptr, "&amp;", 5); sptr += 5; cptr++; ccnt--; continue;
         case '"' :
            memcpy (sptr, "&quot;", 6); sptr += 6; cptr++; ccnt--; continue;
         default :
            *sptr++ = *cptr++;
            ccnt--;
       }
   }
   *sptr = '\0';
   if (CGILIB__DEBUG)
      fprintf (stdout, "%d |%s|\n", sptr-EscapedPtr, EscapedPtr);

   if (EscapedString == NULL)
      return ((int)EscapedPtr);
   else
      return (sptr - EscapedPtr);
}
 
/*****************************************************************************/
/*
Convert numeric HTML entities in a string into their character equivalents
(e.g. "&#38" to '&', "&#00; to 0x00, etc.)  Also converts common alphabetic
entities (e.g. "&amp;", &nbsp;", &lt;", etc.) but not all (any that are not
recognised are left untouched).  Does not URL-decode!   Resultant string is
always the same size or smaller so it can be done in-situ!  Returns the size of
the resultant string.
*/

int CgiLibHtmlDeEntify (char *String)

{
   unsigned char  ch;
   char  *cptr, *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibHtmlDeEntify() |%s|\n", String);

   cptr = sptr = String;
   while (*cptr)
   {
      if (*cptr != '&')
      {
         *sptr++ = *cptr++;
         continue;
      }
      cptr++;
      if (*cptr == '#')
      {
         cptr++;
         if (isdigit(*cptr))
            ch = atoi(cptr);
         else
            ch = (unsigned char)256;
         if (ch > 255)
	 {
	    if (CGILIB__DEBUG) fprintf (stdout, "%d |%s|\n", ch, cptr);
            return (-1);
	 }
         *sptr++ = ch & 0xff;
         while (*cptr && *cptr != ';') cptr++;
         if (*cptr) cptr++;
         continue;
      }
      if (!memcmp (cptr, "amp;", 4))
         { *sptr++ = '&'; cptr+= 4; }
      else
      if (!memcmp (cptr, "gt;", 3))
         { *sptr++ = '>'; cptr+= 3; }
      else
      if (!memcmp (cptr, "lt;", 3))
         { *sptr++ = '<'; cptr+= 3; }
      else
      if (!memcmp (cptr, "nbsp;", 5))
         { *sptr++ = ' '; cptr+= 5; }
      else
      if (!memcmp (cptr, "quot;", 5))
         { *sptr++ = '\"'; cptr+= 5; }
      else
         *sptr++ = '&';
   }
   *sptr = '\0';

   if (CGILIB__DEBUG) fprintf (stdout, "|%s|\n", String);
   return (sptr-String);
}

/*****************************************************************************/
/*
This function parses a "application/x-www-form-urlencoded" string into it's
decoded, constituent name and value pairs.  It can be passed *any* urlencoded
string including QUERY_STRING CGI variables and POSTed request bodies.  It
returns a dynamically allocated string comprising a "name=value" pair, in much
the same format as a CgiLibVar("*") call.  When the form fields are exhausted
it returns a pointer to NULL.  The form field name is not prefixed by
"WWW_FORM", or anything else.  This buffer is reused with each call and so
should not be modified, any contents should be copied as with CgiLibVar()
constraints.  The 'Context' parameter should be initialized to zero before the
initial call.  At the end of a parse it is reinitialized to zero.
*/

char* CgiLibFormEncodedParse
(
char *String,
int *Context
)
{
   static int  BufferSize = 0;
   static char  *BufferPtr = NULL;

   char  *cptr, *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibFormEncodedParse()\n");

   /* get the 'current distance from the start of the string' */
   cptr = sptr = String + *Context;
   if (!*cptr)
   {
      /* end of string */
      free (BufferPtr);
      BufferPtr = NULL;
      BufferSize = 0;
      *Context = 0;
      return (NULL);
   }
   while (*sptr && *sptr != '&') sptr++;

   if (BufferSize < sptr - cptr + 1)
   {
      BufferSize = sptr - cptr + 1;
      BufferPtr = realloc (BufferPtr, BufferSize);
      if (BufferPtr == NULL) exit (SS$_INSFMEM);
   }

   memcpy (BufferPtr, cptr, sptr-cptr);
   BufferPtr[sptr-cptr] = '\0';
   CgiLibUrlDecode (BufferPtr);

   /* set the 'new distance from the start of the string' */
   if (*sptr) sptr++;
   *Context = sptr - String;

   return (BufferPtr);
}

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

       /**************************/
#endif /* CGILIB_GENERAL_SUPPORT */
       /**************************/

       /***********************/
#if     CGILIB_STATUS_CODE_TEXT
       /***********************/

/*****************************************************************************/
/*
Return the a pointer to abbreviated meaning of the supplied HTTP status code.
These are typical of those included on the response header status line.
*/

char* CgiLibHttpStatusCodeText (int StatusCode)

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

   if (CGILIB__DEBUG) fprintf (stdout, "HttpStatusCodeText() %d\n", StatusCode);

   switch (StatusCode)
   {
      case 000 : return ("Script Error!");
      case 100 : return ("Continue"); /* (HTTP/1.1) */
      case 101 : return ("Switching Protocols"); /* (HTTP/1.1) */
      case 200 : return ("OK");
      case 201 : return ("Created");
      case 202 : return ("Accepted");
      case 203 : return ("No Content");
      case 204 : return ("Non-authoritative"); /* (HTTP/1.1) */
      case 205 : return ("Reset Content"); /* (HTTP/1.1) */
      case 206 : return ("Partial Content"); /* (HTTP/1.1) */
      case 300 : return ("Multiple Choices"); /* (HTTP/1.1) */
      case 301 : return ("Moved Permanently");
      case 302 : return ("Moved Temporarily");
      case 303 : return ("See Other"); /* (HTTP/1.1) */
      case 304 : return ("Not Modified");
      case 305 : return ("Use Proxy"); /* (HTTP/1.1) */
      case 400 : return ("Bad Request");
      case 401 : return ("Authorization Required");
      case 402 : return ("Payment Required"); /* (HTTP/1.1) */
      case 403 : return ("Forbidden");
      case 404 : return ("Not Found");
      case 405 : return ("Not Allowed"); /* (HTTP/1.1) */
      case 406 : return ("Not Acceptable"); /* (HTTP/1.1) */
      case 407 : return ("Proxy Authentication Required"); /* (HTTP/1.1) */
      case 408 : return ("Request Timeout"); /* (HTTP/1.1) */
      case 409 : return ("Conflict"); /* (HTTP/1.1) */
      case 410 : return ("Gone"); /* (HTTP/1.1) */
      case 411 : return ("Length Required"); /* (HTTP/1.1) */
      case 412 : return ("Precondition Failed"); /* (HTTP/1.1) */
      case 413 : return ("Request entity too large"); /* (HTTP/1.1) */
      case 414 : return ("Request URI Too Long"); /* (HTTP/1.1) */
      case 415 : return ("Unsupported Media Type"); /* (HTTP/1.1) */
      case 500 : return ("Internal Error");
      case 501 : return ("Not Implemented");
      case 502 : return ("Bad Gateway");
      case 503 : return ("Service Unavailable");
      case 504 : return ("Gateway Timeout"); /* (HTTP/1.1) */
      case 505 : return ("HTTP Version Not Supported"); /* (HTTP/1.1) */
      default :  return ("Unknown Code!");
   }
}

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

       /***************************/
#endif /* CGILIB_STATUS_CODE_TEXT */
       /***************************/

       /**********************/
#if     CGILIB_CGIPLUS_SUPPORT
       /**********************/

/*****************************************************************************/
/*
For CGIplus output the "end-of-file" record (end of script output).
*/

void CgiLibCgiPlusEOF ()

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibCgiPlusEOF()\n");

   if (CgiLib__Environment != CGILIB_ENVIRONMENT_CGIPLUS) return;

   /* CGI EOF must be in a record by itself ... flush! */
   fflush (stdout);
   fputs (CgiLib__CgiPlusEofPtr, stdout);
   fflush (stdout);

#if CGILIB_RESPONSE_SUPPORT

   /* reset the response count ready for any subsequent request */
   CgiLib__ResponseCount = 0;

#endif /* CGILIB_RESPONSE_SUPPORT */
}

/*****************************************************************************/
/*
For CGIplus output the "end-of-text" record (end of CGIplus callout).
*/

void CgiLibCgiPlusEOT ()

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibCgiPlusEOT()\n");

   if (CgiLib__Environment != CGILIB_ENVIRONMENT_CGIPLUS) return;

   if (CgiLib__CgiPlusEotPtr == NULL)
      CgiLib__CgiPlusEotPtr = getenv("CGIPLUSEOT");

   /* CGI EOT must be in a record by itself ... flush! */
   fflush (stdout);
   fputs (CgiLib__CgiPlusEotPtr, stdout);
   fflush (stdout);
}

/*****************************************************************************/
/*
For CGIplus output the "escape" record (start of CGIplus callout).
*/

void CgiLibCgiPlusESC ()

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibCgiPlusESC()\n");

   if (CgiLib__Environment != CGILIB_ENVIRONMENT_CGIPLUS) return;

   if (CgiLib__CgiPlusEscPtr == NULL)
      CgiLib__CgiPlusEscPtr = getenv("CGIPLUSESC");

   /* CGI ESC must be in a record by itself ... flush! */
   fflush (stdout);
   fputs (CgiLib__CgiPlusEscPtr, stdout);
   fflush (stdout);
}

/*****************************************************************************/
/*
Read a record (string) from the CGIPLUSIN stream.  If the buffer is too small
the record will be truncated, but will always be null-terminated.  This
function is provided to allow a CGIplus callout to read a single record
response from the server.
*/

void CgiLibCgiPlusInGets
(
char *String,
int SizeOfString
)
{
   /*********/
   /* begin */
   /*********/

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibCgiPlusInGets()\n");

   /* the CGIplus input stream should always have been opened by now! */
   if (CgiLib__CgiPlusInFile == NULL) exit (SS$_BUGCHECK);

   if (fgets (String, SizeOfString, CgiLib__CgiPlusInFile) == NULL)
      exit (vaxc$errno);
   String[SizeOfString-1] = '\0';
}

/*****************************************************************************/
/*
Switch CGIplus variable stream into 'record' mode.  In this mode each CGI
variable "name=value" pair is transmitted as an individual I/O.
*/

void CgiLibCgiPlusSetVarRecord ()

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibCgiPlusSetVarRecord()\n");

   CgiLibCgiPlusESC ();
   fflush (stdout);
   fputs ("!CGIPLUS: record\n", stdout);
   fflush (stdout);
   CgiLibCgiPlusEOT ();
   fflush (stdout);
   CgiLib__CgiPlusVarRecord = 1;
   CgiLib__CgiPlusVarStruct = 0;
}

/*****************************************************************************/
/*
Switch CGIplus variable stream into 'struct' mode.  In this mode all CGI
variables are transmitted in a single I/O.  The "name=value" pairs must be
explicitly parsed from the buffer.
*/

void CgiLibCgiPlusSetVarStruct ()

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibCgiPlusSetVarStruct()\n");

   CgiLibCgiPlusESC ();
   fflush (stdout);
   fputs ("!CGIPLUS: struct\n", stdout);
   fflush (stdout);
   CgiLibCgiPlusEOT ();
   fflush (stdout);
   CgiLib__CgiPlusVarRecord = 0;
   CgiLib__CgiPlusVarStruct = 1;
}

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

       /**************************/
#endif /* CGILIB_CGIPLUS_SUPPORT */
       /**************************/

       /*********************/
#if     CGILIB_CGIVAR_SUPPORT
       /*********************/

/*****************************************************************************/
/*
(jpp@esme.fr)  Return a CGI variable by descriptor.  Takes an optional third
pointer to short int parameter to receive the string length short.
*/

int CgiLibVarByDesc
(
struct dsc$descriptor *NameDsc,
struct dsc$descriptor *ValueDsc,
...
)
{
   int  idx, argcnt;
   char  *name,
         *value;
   unsigned short  *slenptr;
   va_list  argptr;

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

   va_count (argcnt);
   if (argcnt > 2)
   {
      va_start (argptr, ValueDsc);
      slenptr = (unsigned short*)va_arg (argptr, unsigned short*);
   }
   else
      slenptr = NULL;

   name = calloc (1, NameDsc->dsc$w_length+1);
   for (idx = 0; idx < NameDsc->dsc$w_length; idx++)
      name[idx] = (NameDsc->dsc$a_pointer)[idx];
   name[idx] = '\0';
   value = CgiLibVarNull (name);
   free (name);
   if (value)
   {
      for (idx = 0; idx < ValueDsc->dsc$w_length && value[idx]; idx++)
         (ValueDsc->dsc$a_pointer)[idx] = value[idx];
      if (slenptr) *slenptr = (unsigned short)idx;
      memset (ValueDsc->dsc$a_pointer+idx, ' ', ValueDsc->dsc$w_length-idx);
      return (1);
   }
   else
   {
      memset (ValueDsc->dsc$a_pointer, ' ', ValueDsc->dsc$w_length);
      if (slenptr) *slenptr = 0;
      return (0);
   }
}

/*****************************************************************************/
/*
Get a CGI variable from a DCL symbol or from the CGIplus stream.  Returns a
pointer to the string value if the CGI variables exists or a pointer to
'NonePtr' if not.  Adjusts behaviour according to CGI or CGIplus environments
and to whether symbols are prefixd with "WWW_" or not.
*/

char* CgiVar(char *VarName)
{
   return (CgiLib__GetVar (VarName, CgiLib__VarNonePtr));
}

char* CgiLibVar(char *VarName)
{
   return (CgiLib__GetVar (VarName, CgiLib__VarNonePtr));
}

char* CgiLibVarNull (char *VarName)
{
   return (CgiLib__GetVar (VarName, NULL));
}

char* CgiLib__GetVar
(
char *VarName,
char *NonePtr
)
{
   static int  CgiPrefixChecked = 0,
               HttpMethodPost = 0,
               VarStructCallout = 0;
   static char  *HttpMethodPtr = NULL;
   static char  WwwVarName [256] = "WWW_";

   char  *cptr;
   unsigned short  Length;
   int  VersionNumber;

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

/**
15-AUG-2002  MGD  This debug header *should* work!  However ...
it's as if any <stdout> from CGILIB after the end of the previous request
is absorbed, whereas <stdout> from immediately following the CgiLibVar("")
start-of-CGIplus-request isn't.  This is most peculiar.  I cannot resolve
it and *suspect* something in the C-RTL (of course :^)  The usage-counter
shows that this function is definitely being called.  The work-around is
to put the debug content-type header <stdout> into the script proper.
**/

   /* provide debug response header (subsequent requests) */
   if (CGILIB__DEBUG && CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS &&
       VarName && !VarName[0] && CgiLib__CgiPlusUsageCount)
      fputs ("Content-Type: text/plain\n\n", stdout);

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLib__GetVar() |%s|\n", VarName);

   if (!CgiLib__Environment) CgiLib__SetEnvironment ();

   if (CgiLib__Environment != CGILIB_ENVIRONMENT_CGIPLUS)
   {
      /***************************/
      /* non-CGIplus environment */
      /***************************/

      if (VarName != NULL &&
          VarName[0] &&
          VarName[0] != '*' &&
          CgiLib__CgiPrefixWWW &&
          memcmp (VarName, "WWW_", 4))
      {
         /* variable name does not begin with "WWW_", make one that does */
         strncpy (WwwVarName+4, VarName, sizeof(WwwVarName)-5);
         (VarName = WwwVarName)[sizeof(WwwVarName)-1] = '\0';
      }
      else
      if (!CgiLib__CgiPrefixWWW &&
          !memcmp (VarName, "WWW_", 4))
      {
         /* variables do not begin with "WWW_" and this one does */
         VarName += 4;
      }

#if CGILIB_VARLIST
      /* a POSTed body can be turned into _FORM_ variables */
      if (VarName[0] == '*')
         return (CgiLib__VarList (VarName, NULL, NonePtr));
#else
      if (VarName[0] == '*' &&
          CgiLib__Environment != CGILIB_ENVIRONMENT_OSU)
         return (NonePtr);
#endif

#if CGILIB_OSU_SUPPORT
      if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU)
      {
         /*******************/
         /* OSU environment */
         /*******************/

         return (CgiLib__VarList (VarName, NULL, NonePtr));
      }
#endif

      if (CgiLib__Environment == CGILIB_ENVIRONMENT_WASD)
      {
         /********************/
         /* WASD environment */
         /********************/

         if ((cptr = getenv(VarName)) == NULL)
         {

#if CGILIB_VARLIST
            /* a POSTed body can be turned into _FORM_ variables */
            return (CgiLib__VarList (VarName, NULL, NonePtr));
#endif
            if (CGILIB__DEBUG) fprintf (stdout, "|%s|=CGILIB_NONE\n", VarName);
            return (NonePtr);
         }
         else
         {
            if (CGILIB__DEBUG) fprintf (stdout, "|%s=%s|\n", VarName, cptr);
            return (cptr);
         }
      }

      /*************************************/
      /* Purveyor, vanilla CGI environment */
      /*************************************/

      if (!strcmp (VarName, "WWW_PATH_TRANSLATED") ||
          !strcmp (VarName, "PATH_TRANSLATED"))
         return (CgiLib__GetPathTranslated (NonePtr));

      if (!strncmp (VarName, "WWW_KEY_", 8) ||
          !strncmp (VarName, "KEY_", 4) ||
          !strncmp (VarName, "WWW_FORM_", 9) ||
          !strncmp (VarName, "FORM_", 5))
      {
#if CGILIB_VARLIST

         if (HttpMethodPtr == NULL)
         {
            if (CgiLib__CgiPrefixWWW)
               HttpMethodPtr = getenv("WWW_REQUEST_METHOD");
            else
               HttpMethodPtr = getenv("REQUEST_METHOD");
            if (HttpMethodPtr != NULL &&
                !strcmp (HttpMethodPtr, "POST"))
               HttpMethodPost = 1;
            else
               HttpMethodPost = 0;
         }

         if (HttpMethodPost)
         {
            /* a POSTed body can be turned into _FORM_ variables */
            return (CgiLib__VarList (VarName, NULL, NonePtr));
         }

#endif

         if (!strncmp (VarName, "WWW_KEY_", 8) ||
             !strncmp (VarName, "KEY_", 4))
            return (CgiLib__GetKey (VarName, NonePtr));

         if (!strncmp (VarName, "WWW_FORM_", 9) ||
             !strncmp (VarName, "FORM_", 5))
            return (CgiLib__GetForm (VarName, NonePtr));
      }

      if ((cptr = getenv(VarName)) == NULL)
      {
         if (CGILIB__DEBUG) fprintf (stdout, "|%s|=CGILIB_NONE\n", VarName);
         return (NonePtr);
      }
      else
      {
         if (CGILIB__DEBUG) fprintf (stdout, "|%s=%s|\n", VarName, cptr);
         return (cptr);
      }
   }

#if CGILIB_CGIPLUS_SUPPORT

   /***********************/
   /* CGIplus environment */
   /***********************/

   if (VarName == NULL || !VarName[0])
   {
      /* end of request, reinitialize */
      CgiLib__CgiPlusLoadVariables = 1;
      CgiLib__VarList (NULL, NULL, NULL);

      /* if just initializing then return now */
      if (VarName == NULL) return (NULL);
   }

   if (CgiLib__CgiPlusLoadVariables)
   {
      /* read lines containing CGIplus variables from <CGIPLUSIN> */
      int  StructLength;
      char  CgiPlusRecord [CGILIB_CGIPLUS_RECORD_SIZE];

      CgiLib__CgiPlusUsageCount++;

      if (CGILIB__DEBUG)
         fprintf (stdout, "CGIplus usage: %d\n", CgiLib__CgiPlusUsageCount);

      if (CgiLib__CgiPlusInFile == NULL)
         if ((CgiLib__CgiPlusInFile = 
             fopen (getenv("CGIPLUSIN"), "r", "ctx=rec")) == NULL)
            exit (vaxc$errno);

      CgiLib__CgiPlusLoadVariables = 0;

      /* get the starting sentinal record */
      for (;;)
      {
         cptr = fgets (CgiPlusRecord, sizeof(CgiPlusRecord),
                       CgiLib__CgiPlusInFile);
         if (cptr == NULL) exit (vaxc$errno);
         /* if the starting record is detected then break */
         if (*(unsigned short*)cptr == '!\0' ||
             *(unsigned short*)cptr == '!\n' ||
             (*(unsigned short*)cptr == '!!' && isdigit(*(cptr+2)))) break;
      }

      if (*(unsigned short*)cptr == '!!')
      {
         /********************/
         /* CGIplus 'struct' */
         /********************/

         /* CGIplus variables transmitted as 'struct'ure */
         StructLength = atoi(cptr+2);
         if (StructLength <= 0 || StructLength > sizeof(CgiPlusRecord))
         {
            fprintf (stdout, "%%CGILIB-E-STRUCT, CGIplus \'struct\' error\n");
            exit (SS$_BUGCHECK | STS$M_INHIB_MSG);
         }
         if (!fread (CgiPlusRecord, 1, StructLength, CgiLib__CgiPlusInFile))
            exit (vaxc$errno);

         /* explicitly parse the "name=value" pairs from the single I/O */
         cptr = CgiPlusRecord;
         for (;;)
         {
            if (!(Length = *(short*)cptr)) break;
            cptr += sizeof(short);
            CgiLib__VarList (cptr, "", NULL);
            cptr += Length;
         }
      }
      else
      {
         /*******************/
         /* CGIplus records */
         /*******************/

         /* CGIplus variables transmitted in records */
         while (fgets (CgiPlusRecord,
                       sizeof(CgiPlusRecord),
                       CgiLib__CgiPlusInFile) != NULL)
         {
            if (CGILIB__DEBUG) fprintf (stdout, "|%s|\n", CgiPlusRecord);
            /* first empty record (line) terminates variables */
            if (CgiPlusRecord[0] == '\n') break;
            CgiLib__VarList (CgiPlusRecord, "", NULL);
         }
      }

      if (!CgiPrefixChecked)
      {
         /* must have the CGI variables beginning with or without "WWW_" */
         CgiPrefixChecked = 1;
         cptr = CgiLib__VarList ("WWW_GATEWAY_INTERFACE", NULL, NULL);
         if (cptr == NULL)
         {
            cptr = CgiLib__VarList ("GATEWAY_INTERFACE", NULL, NULL);
            if (cptr == NULL)
            {
               fprintf (stdout, "%%CGILIB-E-CGI, no [WWW_]GATEWAY_INTERFACE\n");
               exit (SS$_BUGCHECK | STS$M_INHIB_MSG);
            }
            CgiLib__CgiPrefixWWW = 0;
         }
         else
            CgiLib__CgiPrefixWWW = 1;
      }

      if (!(VarStructCallout ||
            CgiLib__CgiPlusVarStruct ||
            CgiLib__CgiPlusVarRecord))
      {
         /* attempt to switch CGI variable 'record' to 'struct' mode */
         VarStructCallout = 1;
         fflush (stdout);
         CgiLibCgiPlusESC ();
         fflush (stdout);
         /* the leading '!' means we won't need to read a response */
         fputs ("!CGIPLUS: struct\n", stdout);
         fflush (stdout);
         CgiLibCgiPlusEOT ();
         fflush (stdout);
      }
   }

   /* if waiting for next request return now it's arrived */
   if (!VarName[0]) return (NULL);

   if (VarName[0] != '*' &&
       CgiLib__CgiPrefixWWW &&
       memcmp (VarName, "WWW_", 4))
   {
      /* variable name does not begin with "WWW_", make one that does */
      strncpy (WwwVarName+4, VarName, sizeof(WwwVarName)-5);
      (VarName = WwwVarName)[sizeof(WwwVarName)-1] = '\0';
   }
   else
   if (!CgiLib__CgiPrefixWWW &&
       !memcmp (VarName, "WWW_", 4))
   {
      /* variables do not begin with "WWW_" and this one does */
      VarName += 4;
   }

   /* get a CGIplus variable */
   return (CgiLib__VarList (VarName, NULL, NonePtr));

#endif /* CGILIB_CGIPLUS_SUPPORT */
}

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

       /*************************/
#endif /* CGILIB_CGIVAR_SUPPORT */
       /*************************/

       /**************/
#if     CGILIB_VARLIST
       /**************/

/*****************************************************************************/
/*
Set or get a CGI variable.  Used to store CGIplus variables (read from
CGIPLUSIN stream), OSU variables (created individually during dialog phase) and
the pseudo-CGI-variables from a form-URL-encoded body.

Implemented using a simple list in allocated memory.  Each entry comprises a
total-length-of-entry int, followed by a length-of-variable-name int,
followed by a null-terminated string comprising the variable name, an equate
symbol, and the variable value string.

If 'VarValue' is NULL then get 'VarName', returning a pointer to it's string
value, or if not found to CGILIB_NONE (usually "").  Successive calls with
'VarName' set to "*" (via CgiLibVar() of course) returns each of the list's
entries in turn, a NULL indicating end-of-list.  The list can be reset (for
CGIplus use) with 'VarName' equal to NULL.  If 'VarValue' is not NULL then
store the value against the name (duplicated variables names result in
duplicated entries, not overwriting!)
*/

char* CgiLib__VarList 
(
char *VarName,
char *VarValue,
char *NonePtr
)
{
   static int  ListNextOffset = 0,
               ListSize = 0;

   static char  *NextPtr = NULL,
                *ListPtr = NULL;

   int  VarNameLength;
   unsigned int  Length;
   char  *cptr, *sptr, *zptr,
         *LengthPtr,
         *VarNamePtr,
         *VarNameLengthPtr;

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

   if (CGILIB_VARLIST_DEBUG)
      fprintf (stdout, "CgiLib__VarList() |%s|%s|%s|\n",
               VarName, VarValue, NonePtr);

   if (VarName == NULL)
   {
      /* initialize */
      NextPtr = NULL;
      ListNextOffset = 0;
      return (NULL);
   }

   if (VarValue != NULL)
   {
      /******************/
      /* add a variable */
      /******************/

      if (ListPtr == NULL)
      {
         /* always allow extra space (allows sloppier/faster code ;^) */
         ListPtr = realloc (ListPtr, CGILIB_VARLIST_CHUNK+16);
         if (ListPtr == NULL) exit (SS$_INSFMEM);
         ListSize = CGILIB_VARLIST_CHUNK;
      }

      /* point to next "[total-len][var-name-len]var-name=var-value" storage */
      sptr = LengthPtr = ListPtr + ListNextOffset;
      /* now point to where the length of the variable name is stored */
      VarNameLengthPtr = (sptr += sizeof(int));
      /* now point to the start of the "var-name=var-value" string */
      VarNamePtr = (sptr += sizeof(int));
      /* so we don't over-run the list storage */
      zptr = ListPtr + ListSize;

      for (cptr = VarName; *cptr; *sptr++ = *cptr++)
      {
         /* CGIplus, stream has "name=value\n\0" */
         if (*(unsigned short*)cptr == '\n\0') break;
         if (*cptr != '=' || VarNameLengthPtr == NULL) continue;
         /* store the length of the CGIplus variable name */
         *((int*)VarNameLengthPtr) = sptr - VarNamePtr;
         /* indicate we've already done this on the first '=' */
         VarNameLengthPtr = NULL;
         if (sptr < zptr) continue;
         /* out of storage, get more memory */
         Length = sptr - LengthPtr;
         ListPtr = realloc (ListPtr, ListSize+CGILIB_VARLIST_CHUNK+16);
         if (ListPtr == NULL) exit (SS$_INSFMEM);
         ListSize += CGILIB_VARLIST_CHUNK;
         /* recalculate pointers */
         LengthPtr = ListPtr + ListNextOffset;
         sptr = LengthPtr + Length;
         zptr = ListPtr + ListSize - 2;
      }
      if (VarNameLengthPtr != NULL)
      {
         /* store the length of the variable name */
         *((int*)VarNameLengthPtr) = sptr - VarNamePtr;
         *sptr++ = '=';
         /* add the variable value */
         for (cptr = VarValue; *cptr; *sptr++ = *cptr++)
         {
            if (sptr < zptr) continue;
            /* out of storage get more memory */
            Length = sptr - LengthPtr;
            ListPtr = realloc (ListPtr, ListSize+CGILIB_VARLIST_CHUNK+16);
            if (ListPtr == NULL) exit (SS$_INSFMEM);
            ListSize += CGILIB_VARLIST_CHUNK;
            /* recalculate pointers */
            LengthPtr = ListPtr + ListNextOffset;
            sptr = LengthPtr + Length;
            zptr = ListPtr + ListSize;
         }
      }
      *sptr++ = '\0';

      /* insert the length of this name=value pair immediately before it */
      *((int*)LengthPtr) = Length = sptr - LengthPtr;

      /* adjust the value of the current variable storage space used */
      ListNextOffset += Length;

      /* ensure the list is terminated with a zero length entry */
      *((int*)(ListPtr+ListNextOffset)) = 0;

      if (CGILIB_VARLIST_DEBUG)
      {
         fprintf (stdout, "ListPtr: %d ListSize: %d ListNextOffset: %d\n",
                  ListPtr, ListSize, ListNextOffset);
         sptr = ListPtr;
         for (sptr = ListPtr; Length = *((int*)sptr); sptr += Length)
         {
            fprintf (stdout, "%5d %2d |%s|\n",
               Length, *((int*)(sptr+sizeof(int))),
               sptr+sizeof(int)+sizeof(int));
         }
      }

      return (NULL);
   }

   if (ListPtr == NULL)
   {
      if (CGILIB__DEBUG)
         fprintf (stdout, "|%s|=CGILIB_NONE (empty list)\n", VarName);
      if (VarName[0] == '*') return (NULL);
      return (NonePtr);
   }

   if (VarName[0] == '*')
   {
      /*****************************************/
      /* return each stored line one at a time */
      /*****************************************/

      if (NextPtr == NULL) NextPtr = ListPtr;
      for (;;)
      {
         if (!(Length = *((int*)(sptr = NextPtr))))
         {
            if (CGILIB__DEBUG) fprintf (stdout, "|*|=NULL\n");
            return (NextPtr = NULL);
         }
         NextPtr = sptr + Length;
         sptr += sizeof(int) + sizeof(int);
         if (CGILIB_VARLIST_DEBUG) fprintf (stdout, "|%s|\n", sptr);
         if (!VarName[1]) return (sptr);
      }
      return (NULL);
   }

   /*************************************************/
   /* search variable strings for required variable */
   /*************************************************/

   VarNameLength = strlen(VarName);

   for (sptr = LengthPtr = ListPtr;
        Length = *((int*)sptr);
        sptr = LengthPtr = LengthPtr + Length)
   {
      if (CGILIB_VARLIST_DEBUG)
         fprintf (stdout, "%d %d\n", Length, *((int*)sptr+4));
      /* step over the total-length int */
      sptr += sizeof(int);
      if (*((int*)sptr) != VarNameLength) continue;
      /* step over the var-name-length int */
      sptr += sizeof(int);
      /* simple comparison between supplied and in-string variable names */
      cptr = VarName;
      if (CGILIB_VARLIST_DEBUG) fprintf (stdout, "|%s|%s|\n", cptr, sptr);
      while (*cptr && *sptr && *sptr != '=')
      {
         if (toupper(*cptr) != toupper(*sptr)) break;
         cptr++;
         sptr++;
      }
      if (*cptr || *sptr != '=') continue;
      /* found, return a pointer to the value */
      if (CGILIB__DEBUG) fprintf (stdout, "|%s=%s|\n", VarName, sptr+1);
      return (sptr+1);
   }

   /* not found */
   if (CGILIB__DEBUG) fprintf (stdout, "|%s|=CGILIB_NONE\n", VarName);
   return (NonePtr);
}  

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

       /******************/
#endif /* CGILIB_VARLIST */
       /******************/

       /**********************/
#if     CGILIB_CGIVAR_NON_WASD
       /**********************/

/*****************************************************************************/
/*
For "standard" CGI environment (e.g. Netscape FastTrack) derive the
"KEY_COUNT", "KEY_1" ... "KEY_n" variables from "QUERY_STRING".
*/

char* CgiLib__GetKey
(
char *VarName,
char *NonePtr
)
{
   int  KeyCount,
        VarKeyCount;
   char  *aptr, *cptr, *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLib__GetKey() |%s|\n", VarName);

   if (!VarName[0]) return (NonePtr);

   /* step over the "WWW_" */
   if (CgiLib__CgiPrefixWWW) VarName += 4;

   if ((cptr = CgiLib__QueryStringPtr) == NULL)
   {
      if (CgiLib__CgiPrefixWWW)
         CgiLib__QueryStringPtr = cptr = getenv ("WWW_QUERY_STRING");
      else
         CgiLib__QueryStringPtr = cptr = getenv ("QUERY_STRING");
      if (CGILIB__DEBUG) fprintf (stdout, "query_string |%s|\n", cptr);
      if (cptr == NULL) return (NonePtr);
   }

   if (!strcmp (VarName, "KEY_COUNT"))
   {
      KeyCount = 0;
      for (sptr = cptr; *sptr; sptr++)
      {
         if (*sptr == '=') return (NonePtr);
         if (*sptr == '+') KeyCount++;
      }
      if (sptr == cptr) return ("0");
      KeyCount++;
      aptr = calloc (1, 16);
      if (aptr == NULL) exit (SS$_INSFMEM);
      sprintf (aptr, "%d", KeyCount);
      if (CGILIB__DEBUG) fprintf (stdout, "|%s=%s|\n", VarName, aptr);
      return (aptr);
   }
 
   VarKeyCount = atoi (VarName+4);
   if (CGILIB__DEBUG) fprintf (stdout, "VarKeyCount: %d\n", VarKeyCount);
   KeyCount = 1;
   while (*cptr && VarKeyCount)
   {
      if (CGILIB__DEBUG) fprintf (stdout, "|%s|\n", cptr);
      sptr = cptr;
      while (*cptr && *cptr != '+')
      {
         if (*cptr == '=') return (NonePtr);
         cptr++;
      }
      if (KeyCount == VarKeyCount)
      {
         aptr = calloc (1, cptr - sptr + 1);
         if (aptr == NULL) exit (SS$_INSFMEM);
         memcpy (aptr, sptr, cptr-sptr);
         aptr[cptr-sptr] = '\0';
         CgiLibUrlDecode (aptr);
         if (CGILIB__DEBUG) fprintf (stdout, "|%s=%s|\n", VarName, aptr);
         return (aptr);
      }
      if (*cptr) cptr++;
      KeyCount++;
   }

   if (CGILIB__DEBUG) fprintf (stdout, "|%s|=CGILIB_NONE\n", VarName);
   return (NonePtr);
}

/*****************************************************************************/
/*
For "standard" CGI environment (e.g. Netscape FastTrack, Purveyor, Apache)
derive the "FORM_xxxxx" variables from "QUERY_STRING".
*/

char* CgiLib__GetForm
(
char *VarName,
char *NonePtr
)
{
   char  *aptr, *cptr, *sptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLib__GetForm() |%s|\n", VarName);

   if (!VarName[0]) return (NonePtr);

   /* step over the "WWW_" (as necessary) and the "FORM_" */
   if (!strncmp (VarName, "WWW_", 4)) VarName += 4;
   VarName += 5;

   if ((cptr = CgiLib__QueryStringPtr) == NULL)
   {
      if (CgiLib__CgiPrefixWWW)
         CgiLib__QueryStringPtr = cptr = getenv ("WWW_QUERY_STRING");
      else
         CgiLib__QueryStringPtr = cptr = getenv ("QUERY_STRING");
      if (CGILIB__DEBUG) fprintf (stdout, "|%s|\n", cptr);
      if (cptr == NULL) return (NonePtr);
   }

   while (*cptr)
   {
      sptr = cptr;
      while (*cptr && *cptr != '=') cptr++;
      if (!*cptr) return (NonePtr);
      *cptr = '\0';
      aptr = VarName;
      while (*aptr && *sptr && toupper(*aptr) == toupper(*sptr))
      {
         aptr++;
         sptr++;
      }
      if (*aptr || *sptr)
      {
         *cptr++ = '=';
         while (*cptr && *cptr != '&') cptr++;
         if (*cptr) cptr++;
         continue;
      }
      *cptr++ = '=';

      sptr = cptr;
      while (*cptr && *cptr != '&') cptr++;
      aptr = calloc (1, cptr - sptr + 1);
      if (aptr == NULL) exit (SS$_INSFMEM);
      memcpy (aptr, sptr, cptr-sptr);
      aptr[cptr-sptr] = '\0';
      CgiLibUrlDecode (aptr);
      if (CGILIB__DEBUG) fprintf (stdout, "|%s=%s|\n", VarName, aptr);
      return (aptr);
   }

   if (CGILIB__DEBUG) fprintf (stdout, "|%s|=CGILIB_NONE\n", VarName);
   return (NonePtr);
}

/*****************************************************************************/
/*
For vanilla CGI environment (e.g. Netscape FastTrack) get the "PATH_TRANSLATED"
variable and check if it's in Unix-style or VMS-style format.  Convert to a VMS
specification as necessary.
*/

char* CgiLib__GetPathTranslated (char *NonePtr)
   
{
   int  Length;
   char  *aptr, *cptr, *sptr;
   char  PathTranslated [256];

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLib__GetPathTranslated()\n");

   if (CgiLib__CgiPrefixWWW)
      cptr = aptr = getenv ("WWW_PATH_TRANSLATED");
   else
      cptr = aptr = getenv ("PATH_TRANSLATED");
   if (cptr == NULL) return (NonePtr);

   if (*cptr == '/')
   {
      /* unix-style specification */
      sptr = PathTranslated;
      if (*cptr == '/') cptr++;
      while (*cptr && *cptr != '/') *sptr++ = toupper(*cptr++);
      if (*cptr)
      {
         cptr++;
         *sptr++ = ':';
         *sptr++ = '[';
      }
      while (*cptr) *sptr++ = toupper(*cptr++);
      *sptr-- = '\0';
      while (sptr > PathTranslated && *sptr != '/') sptr--;
      if (*sptr == '/')
      {
         *sptr-- = ']';
         while (sptr >= PathTranslated)
         {
            if (*sptr == '/') *sptr = '.';
            if (*(unsigned long*)sptr == '....')
               memmove (sptr, sptr+1, strlen(sptr)); 
            sptr--;
         }
      }
      aptr = calloc (1, Length = strlen(PathTranslated) + 1);
      if (aptr == NULL) exit (SS$_INSFMEM);
      memcpy (aptr, PathTranslated, Length);
   }

   if (CGILIB__DEBUG) fprintf (stdout, "|PATH_TRANSLATED=%s|\n", aptr);
   return (aptr);
}

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

       /**************************/
#endif /* CGILIB_CGIVAR_NON_WASD */
       /**************************/

       /******************/
#if     CGILIB_OSU_SUPPORT
       /******************/

/*****************************************************************************/
/*
Initialize the OSU variable list using the request network dialog phase.
*/

void CgiLibOsuInit
(
int argc,
char *argv[]
)
{
   int  ContentLengthSet,
        ContentTypeSet,
        IpAddress,
        KeyCount;
   char  *cptr, *sptr, *zptr;
   char  PathInfo [256],
         PathTranslated [256],
         Scratch [256],
         ScriptName [256],
         SymbolName [256];

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

   if (argc < 4)
   {
      fprintf (stdout, "%%CGILIB-E-OSU, P1=<method> P2=<path> P3=<protocol>\n");
      exit (SS$_INSFARG | STS$M_INHIB_MSG);
   }

   if (CGILIB__DEBUG)
   {
      /* let's have a look at what's available! */
      fprintf (stdout, "CgiLibOsuInit()\n----------\n");
      CgiLib__OsuQueryNetLink ("<DNETARG>", 1);
      CgiLib__OsuQueryNetLink ("<DNETARG2>", 1);
      CgiLib__OsuQueryNetLink ("<DNETHOST>", 1);
      CgiLib__OsuQueryNetLink ("<DNETID>", 1);
      CgiLib__OsuQueryNetLink ("<DNETID2>", 1);
      CgiLib__OsuQueryNetLink ("<DNETRQURL>", 1);
      CgiLib__OsuQueryNetLink ("<DNETBINDIR>", 1);
      CgiLib__OsuQueryNetLink ("<DNETPATH>", 1);
      CgiLib__OsuQueryNetLink ("<DNETHDR>", 999);
      fprintf (stdout, "----------\n");
   }

   /**********/
   /* basics */
   /**********/

   CgiLib__VarList ("WWW_GATEWAY_INTERFACE", "CGI/1.1", NULL);
   CgiLib__VarList ("WWW_REQUEST_METHOD", argv[1], NULL);
   CgiLib__VarList ("WWW_SERVER_PROTOCOL", argv[3], NULL);

   /*******************************************/
   /* SCRIPT_NAME, PATH_INFO, PATH_TRANSLATED */
   /*******************************************/

   strcpy (sptr = ScriptName, CgiLib__OsuQueryNetLink ("<DNETPATH>", 1));
   cptr = CgiLib__OsuQueryNetLink ("<DNETRQURL>", 1);
   while (*cptr && *cptr && *cptr == *sptr) { cptr++; sptr++; }
   while (*cptr && *cptr != '/' && *cptr != '?') *sptr++ = *cptr++;
   *sptr = '\0';
   CgiLibUrlDecode (ScriptName);
   CgiLib__VarList ("WWW_SCRIPT_NAME", ScriptName, NULL);

   /* continue to get the path information from the request URL */
   sptr = PathInfo;
   while (*cptr && *cptr != '?') *sptr++ = *cptr++;
   *sptr = '\0';
   CgiLibUrlDecode (PathInfo);

   if (*PathInfo && *(unsigned short*)PathInfo != '/\0')
   {
      CgiLib__OsuQueryNetLink ("<DNETXLATE>", 0);
      sptr = CgiLib__OsuQueryNetLink (PathInfo, 1);

      cptr = PathTranslated;
      if (*sptr == '/') sptr++;
      while (*sptr && *sptr != '/') *cptr++ = toupper(*sptr++);
      if (*sptr)
      {
         sptr++;
         *cptr++ = ':';
         *cptr++ = '[';
      }
      while (*sptr) *cptr++ = toupper(*sptr++);
      *cptr-- = '\0';
      while (cptr > PathTranslated && *cptr != '/') cptr--;
      if (*cptr == '/')
      {
         *cptr-- = ']';
         while (cptr >= PathTranslated)
         {
            if (*cptr == '/') *cptr = '.';
            if (*(unsigned long*)cptr == '....')
               memmove (cptr, cptr+1, strlen(cptr)); 
            cptr--;
         }
      }

      CgiLib__VarList ("WWW_PATH_INFO", PathInfo, NULL);
      CgiLib__VarList ("WWW_PATH_TRANSLATED", PathTranslated, NULL);
   }
   else
   {
      CgiLib__VarList ("WWW_PATH_INFO", "/", NULL);
      CgiLib__VarList ("WWW_PATH_TRANSLATED", "", NULL);
   }

   /***********************************/
   /* QUERY_STRING, FORM_..., KEY_... */
   /***********************************/

   cptr = CgiLib__OsuQueryNetLink ("<DNETARG>", 1);
   if (*cptr == '?') cptr++;
   CgiLib__VarList ("WWW_QUERY_STRING", cptr, NULL);

   KeyCount = 0;
   sptr = cptr;
   while (*cptr && *cptr != '=') cptr++;
   if (*cptr)
   {
      /* FORM-formatted query string */
      while (*cptr)
      {
         *cptr++ = '\0';
         sprintf (SymbolName, "WWW_FORM_%s", sptr);
         sptr = cptr;
         while (*cptr && *cptr != '&') cptr++;
         if (*cptr) *cptr++ = '\0';
         CgiLibUrlDecode (SymbolName);
         CgiLibUrlDecode (sptr);
         CgiLib__VarList (SymbolName, sptr, NULL);
         sptr = cptr;
         while (*cptr && *cptr != '=') cptr++;
      }
   }
   else
   {
      /* ISQUERY-formatted query string */
      cptr = sptr;
      while (*cptr && *cptr != '+') cptr++;
      while (*sptr)
      {
         *cptr++ = '\0';
         sprintf (SymbolName, "WWW_KEY_%d", ++KeyCount);
         CgiLibUrlDecode (sptr);
         CgiLib__VarList (SymbolName, sptr, NULL);
         sptr = cptr;
         while (*cptr && *cptr != '+') cptr++;
      }
   }
   sprintf (Scratch, "%d", KeyCount);
   CgiLib__VarList ("WWW_KEY_COUNT", Scratch, NULL);

   /**********************************************/
   /* SERVER_SOFTWARE, SERVER_NAME, SERVER_PORT, */
   /* REMOTE_HOST, REMOTE_ADDR, REMOTE_USER      */
   /**********************************************/

   cptr = sptr = CgiLib__OsuQueryNetLink ("<DNETID2>", 1);

   while (*cptr && !isspace(*cptr)) cptr++;
   if (*cptr) *cptr++ = '\0';
   CgiLib__VarList ("WWW_SERVER_SOFTWARE", sptr, NULL);

   sptr = cptr;
   while (*cptr && !isspace(*cptr)) cptr++;
   if (*cptr) *cptr++ = '\0';
   CgiLib__VarList ("WWW_SERVER_NAME", sptr, NULL);

   sptr = cptr;
   while (*cptr && !isspace(*cptr)) cptr++;
   if (*cptr) *cptr++ = '\0';
   CgiLib__VarList ("WWW_SERVER_PORT", sptr, NULL);

   /* skip over client port */
   while (*cptr && !isspace(*cptr)) cptr++;
   if (*cptr) *cptr++ = '\0';

   /* host address */
   sptr = cptr;
   while (*cptr && !isspace(*cptr)) cptr++;
   if (*cptr) *cptr++ = '\0';
   for (zptr = sptr; *zptr && *zptr != '.'; zptr++);
   if (*zptr != '.')
   {
      IpAddress = atoi(sptr);
      sprintf (Scratch, "%d.%d.%d.%d",
               *(unsigned char*)&IpAddress,
               *((unsigned char*)(&IpAddress)+1),
               *((unsigned char*)(&IpAddress)+2),
               *((unsigned char*)(&IpAddress)+3));
      sptr = Scratch;
   }
   CgiLib__VarList ("WWW_REMOTE_ADDR", sptr, NULL);
   CgiLib__VarList ("WWW_REMOTE_HOST", sptr, NULL);

   sptr = cptr;
   while (*cptr && !isspace(*cptr)) cptr++;
   if (*cptr) *cptr++ = '\0';
   CgiLib__VarList ("WWW_REMOTE_USER", sptr, NULL);

   sptr = cptr;
   while (*cptr && !isspace(*cptr)) cptr++;
   if (*cptr)
   {
      /* DNS-resolved host name */
      *cptr++ = '\0';
      CgiLib__VarList ("WWW_REMOTE_HOST", sptr, NULL);
   }

   /******************************************/
   /* CONTENT_LENGTH, CONTENT_TYPE, HTTP_... */
   /******************************************/

   ContentLengthSet = ContentTypeSet = 0;

   cptr = sptr = CgiLib__OsuQueryNetLink ("<DNETHDR>", 999);
   while (*cptr)
   {
      while (*cptr && *cptr != ':')
      {
         if (isalnum(*cptr))
            *cptr = toupper(*cptr);
         else
            *cptr = '_';
         cptr++;
      }
      if (*cptr)
      {
         *cptr++ = '\0';
         sprintf (SymbolName, "WWW_HTTP_%s", sptr);
         while (*cptr && isspace(*cptr) && *cptr != '\n') *cptr++;
         sptr = cptr;
         while (*cptr && *cptr != '\n') *cptr++;
         if (*cptr) *cptr++ = '\0';
         CgiLibUrlDecode (SymbolName);
         CgiLibUrlDecode (sptr);
         if (!strcmp (SymbolName+9, "CONTENT_TYPE"))
         {
            ContentTypeSet = 1;
            CgiLib__VarList ("WWW_CONTENT_TYPE", sptr, NULL);
         }
         else
         if (!strcmp (SymbolName+9, "CONTENT_LENGTH"))
         {
            ContentLengthSet = 1;
            CgiLib__VarList ("WWW_CONTENT_LENGTH", sptr, NULL);
         }
         else
            CgiLib__VarList (SymbolName, sptr, NULL);
         sptr = cptr;
      }
   }

   if (!ContentTypeSet)
      CgiLib__VarList ("WWW_CONTENT_TYPE", "", NULL);

   if (!ContentLengthSet)
      CgiLib__VarList ("WWW_CONTENT_LENGTH", "0", NULL);
}  

/*****************************************************************************/
/*
Reopen <stdout> to NET_LINK: in binary mode.  This is much more efficient as the
CRTL buffers individual prints into a stream.  Output an appropriate
output-phase tag and establish an exit function to complete the output dialog.
*/

void CgiLibOsuStdoutCgi ()

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibOsuStdoutCgi()\n");

   /* reopen so that '\r' and '\n' are not filtered, maximum record size */
   if ((stdout =
       freopen ("NET_LINK:", "w", stdout, "ctx=bin", "mrs=4096")) == NULL)
      exit (vaxc$errno);
 
   if (CGILIB__DEBUG)
   {
      fputs ("<DNETTEXT>", stdout);
      fflush(stdout);
      fputs ("200 Debug", stdout);
   }
   else
      fputs ("<DNETCGI>", stdout);
   fflush(stdout);

   atexit (&CgiLib__OsuExitCgi);
}  

void CgiLib__OsuExitCgi ()

{
   if (CGILIB__DEBUG) fprintf (stdout, "CgiLib__OsuExitCgi()\n");

   fflush(stdout);
   if (CGILIB__DEBUG)
      fputs ("</DNETTEXT>", stdout);
   else
      fputs ("</DNETCGI>", stdout);
   fflush(stdout);
}  

/*****************************************************************************/
/*
Reopen <stdout> to NET_LINK: in binary mode.  This is much more efficient as the
CRTL buffers individual prints into a stream.  Output an appropriate
output-phase tag and establish an exit function to complete the output dialog.
*/

void CgiLibOsuStdoutRaw ()

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibOsuStdoutRaw()\n");

   /* reopen so that '\r' and '\n' are not filtered, maximum record size */
   if ((stdout =
       freopen ("NET_LINK:", "w", stdout, "ctx=bin", "mrs=4096")) == NULL)
      exit (vaxc$errno);

   if (CGILIB__DEBUG)
   {
      fputs ("<DNETTEXT>", stdout);
      fflush(stdout);
      fputs ("200 Debug", stdout);
   }
   else
      fputs ("<DNETRAW>", stdout);
   fflush (stdout);

   atexit (&CgiLib__OsuExitRaw);
}  

void CgiLib__OsuExitRaw ()

{
   if (CGILIB__DEBUG) fprintf (stdout, "CgiLib__OsuExitRaw()\n");

   fflush (stdout);
   if (CGILIB__DEBUG)
      fputs ("</DNETTEXT>", stdout);
   else
      fputs ("</DNETRAW>", stdout);
   fflush (stdout);
}  

/*****************************************************************************/
/*
Write a single record to the OSU network link, then if expecting a response
wait for one or more records to be returned.  A single line response has no
carriage control.  A multi-line response has each record concatenated into a
single string of newline-separated records.  Returns pointer to static storage!
Open files to the link if first call.
*/

char* CgiLib__OsuQueryNetLink
(
char *Query,
int ResponseLinesExpected
)
{
   static char  Response [4096];
   static FILE  *NetLinkRead,
                *NetLinkWrite;

   int  status;
   char  *cptr;

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

   if (CGILIB__DEBUG)
      fprintf (stdout, "CgiLib__OsuQueryNetLink() |%s|\n", Query);

   if (NetLinkRead == NULL)
   {
      /*************************/
      /* first call, open link */
      /*************************/

      if ((NetLinkRead = fopen ("NET_LINK:", "r", "ctx=rec")) == NULL)
      {
         status = vaxc$errno;
         fprintf (stdout, "%%CGILIB-E-BODYREAD, read open error\n");
         exit (status);
      }
      if ((NetLinkWrite = fopen ("NET_LINK:", "w", "ctx=bin")) == NULL)
      {
         status = vaxc$errno;
         fprintf (stdout, "%%CGILIB-E-BODYREAD, write open error\n");
         exit (status);
      }
   }

   if (Query != NULL)
   {
      /*********/
      /* write */
      /*********/

      if (fputs (Query, NetLinkWrite) == EOF)
      {
         status = vaxc$errno;
         fprintf (stdout, "%%CGILIB-E-BODYREAD, write error\n");
         exit (status);
      }
      fflush (NetLinkWrite);
   }

   if (ResponseLinesExpected > 1)
   {
      /**************************/
      /* multiple line response */
      /**************************/

      cptr = Response;
      for (;;)
      {
         if (fgets (cptr, sizeof(Response)-(cptr-Response), NetLinkRead) == NULL)
         {
            status = vaxc$errno;
            fprintf (stdout, "%%CGILIB-E-BODYREAD, read error\n");
            exit (status);
         }
         /** if (CGILIB__DEBUG) fprintf (stdout, "cptr |%s|\n", cptr); **/
         if (*cptr != '\n')
         {
            while (*cptr) cptr++;
            continue;
         }
         *cptr = '\0';
         break;
      }
   }
   else
   if (ResponseLinesExpected > 0)
   {
      /************************/
      /* single line response */
      /************************/

      if (fgets (Response, sizeof(Response), NetLinkRead) == NULL)
      {
         status = vaxc$errno;
         fprintf (stdout, "%%CGILIB-E-BODYREAD, read error\n");
         exit (status);
      }

      for (cptr = Response; *cptr; cptr++);
      if (cptr > Response)
      {
         if (*--cptr == '\n')
            *cptr = '\0';
         else
            cptr++;
      }
   }
   else
      *(cptr = Response) = '\0';

   if (CGILIB__DEBUG) fprintf (stdout, "%d |%s|\n", cptr-Response, Response);
   return (Response);
}  

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

       /**********************/
#endif /* CGILIB_OSU_SUPPORT */
       /**********************/

       /*******************/
#if     CGILIB_POST_SUPPORT
       /*******************/

/*****************************************************************************/
/*
Read a POSTed request body into a single array of char (doesn't matter whether
it's text or binary).  Returns NULL if it's not a POST, a pointer if it is. 
The caller must free() this memory when finished with it.  Note: the OSU DECnet
scripting stream is NET_LINK:, the WASD CGI DECnet scripting stream is
NET$LINK, the two should not be confused.
*/

char* CgiLibReadRequestBody
(
char **BodyPtrPtr,
int *BodyLengthPtr
)
{
   static int  BodyReadFd = 0,
               BufferCount = 0,
               InitializeEnvironment = 1,
               PrevUsageCount;
   static char  *BufferPtr = NULL;

   int  status,
        BufferSize,
        ContentLength,
        ReadCount;
   char  *cptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibReadRequestBody()\n");

   if (InitializeEnvironment)
   {
      InitializeEnvironment = 0;

      if (!CgiLib__Environment) CgiLib__SetEnvironment ();

      cptr = CgiLib__GetVar ("WWW_REQUEST_METHOD", "");
      if (strcmp (cptr, "POST"))
      {
         *BodyPtrPtr = NULL;
         *BodyLengthPtr = 0;
         if (CGILIB__DEBUG)
            fprintf (stdout, "%d %d\n", *BodyLengthPtr, *BodyPtrPtr);
         return (NULL);
      }

      if (CgiLib__Environment == CGILIB_ENVIRONMENT_WASD)
      {
         if (getenv ("NET$LINK") != NULL)
            BodyReadFd = open ("NET$LINK:", O_RDONLY, 0, "ctx=bin");
         else
            BodyReadFd = open ("HTTP$INPUT:", O_RDONLY, 0, "ctx=bin");
      }
      else
      if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE)
         BodyReadFd = open ("APACHE$INPUT:", O_RDONLY, 0, "ctx=bin");
      else
#if CGILIB_CGIPLUS_SUPPORT
      if (CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS)
         BodyReadFd = open ("HTTP$INPUT:", O_RDONLY, 0, "ctx=bin");
      else
#endif
#if CGILIB_OSU_SUPPORT
      if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU)
         BodyReadFd = open ("NET_LINK:", O_RDONLY, 0, "ctx=bin");
      else
#endif
      /* vanilla CGI environment */
      if (getenv ("WWW_IN") != NULL)
      {
         /* Purveyor, Cern? */
         BodyReadFd = open ("WWW_IN:", O_RDONLY, 0, "ctx=bin");
      }
      else
         BodyReadFd = open ("SYS$INPUT:", O_RDONLY, 0, "ctx=bin");

      if (BodyReadFd == -1)
      {
         status = vaxc$errno;
         fprintf (stdout, "%%CGILIB-E-BODYREAD, binary read open error\n");
         exit (status);
      }
   }
   else
   /* if not CGIplus or if multiple calls in the one CGIplus request */
   if (CgiLib__Environment != CGILIB_ENVIRONMENT_CGIPLUS ||
       CgiLib__CgiPlusUsageCount == PrevUsageCount)
   {
      /* body has already been read by call from CgiLibEnvironmentInit() */
      *BodyPtrPtr = BufferPtr;
      *BodyLengthPtr = BufferCount;
      if (CGILIB__DEBUG)
         fprintf (stdout, "%d %d\n", *BodyLengthPtr, *BodyPtrPtr);
      return (BufferPtr);
   }

   PrevUsageCount = CgiLib__CgiPlusUsageCount;

   if (BufferPtr)
   {
      free (BufferPtr);
      BufferPtr = NULL;
   }

   ContentLength = atoi(CgiLib__GetVar ("WWW_CONTENT_LENGTH", ""));
   if (CGILIB__DEBUG) fprintf (stdout, "ContentLength: %d\n", ContentLength);

   if (!ContentLength)
   {
      *BodyPtrPtr = NULL;
      *BodyLengthPtr = 0;
      return (NULL);
   }

   if ((BufferPtr = calloc (1, ContentLength+1)) == NULL)
      exit (SS$_INSFMEM);
   BufferSize = ContentLength;
   BufferCount = 0;

   while (BufferCount < BufferSize)
   {
#if CGILIB_OSU_SUPPORT
      if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU)
         CgiLib__OsuQueryNetLink ("<DNETINPUT>", 0);
#endif

      ReadCount = read (BodyReadFd,
                        BufferPtr+BufferCount,
                        BufferSize-BufferCount);
      if (CGILIB__DEBUG) fprintf (stdout, "ReadCount: %d\n", ReadCount);
      if (!ReadCount) break;

      if (ReadCount == -1)
      {
         status = vaxc$errno;
         fprintf (stdout, "%%CGILIB-E-BODYREAD, binary read error\n");
         exit (status);
      }

      BufferCount += ReadCount;
   }
   if (CGILIB__DEBUG)
      fprintf (stdout, "size: %d count: %d\n", BufferSize, BufferCount);

   /* terminate with a null in case it's text (not counted in 'BufferCount') */
   BufferPtr[BufferCount] = '\0';
   /** if (CGILIB__DEBUG) fprintf (stdout, "|%s|\n", BufferPtr); **/

   *BodyPtrPtr = BufferPtr;
   *BodyLengthPtr = BufferCount;
   if (CGILIB__DEBUG) fprintf (stdout, "%d %d\n", *BodyLengthPtr, *BodyPtrPtr);

   return (BufferPtr);
}

/*****************************************************************************/
/*
Converts the POSTed body parameter into "WWW_FORM_field-name" CGI variables
accessable from the variable list.  Field names are limited to 255 characters,
after which they are truncated.
*/

int CgiLibFormRequestBody
(
char *BodyPtr,
int BodyLength
)
{
   char  *cptr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLibFormRequestBody()\n");

   if (!BodyLength) return (0);

   cptr = CgiLib__GetVar ("WWW_CONTENT_TYPE", "");

   if (strstr (cptr, "application/x-www-form-urlencoded"))
      return (CgiLib__BodyFormUrlEncoded (BodyPtr, BodyLength));

   if (!strncmp (cptr, "multipart/form-data;", 20))
      return (CgiLib__BodyMultipartFormData (BodyPtr, BodyLength, cptr+20));

   return (0);
}

/*****************************************************************************/
/*
Converts the POSTed url-form-encoded body parameter into "WWW_FORM_field-name"
CGI variables accessable from the variable list.
*/

int CgiLib__BodyFormUrlEncoded
(
char *BodyPtr,
int BodyLength
)
{
   static char  VarName [256] = "WWW_FORM_";

   int  VarCount;
   char  *cptr, *sptr, *zptr,
         *BufferPtr,
         *VarValuePtr;

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLib__FormUrlEncoded()\n");

   if ((cptr = BufferPtr = calloc (1, BodyLength+1)) == NULL)
      exit (SS$_INSFMEM);
   memcpy (cptr, BodyPtr, BodyLength);
   cptr[BodyLength] = '\0';

   VarCount = 0;
   while (*cptr)
   {
      zptr = (sptr = VarName) + sizeof(VarName)-1;
      sptr += 9;
      while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = toupper(*cptr++);
      if (sptr >= zptr) while (*cptr && *cptr != '=') cptr++;
      if (!*cptr)
      {
         free (BufferPtr);
         return (VarCount);
      }
      *sptr = '\0';
      VarValuePtr = ++cptr;
      while (*cptr && *cptr != '&') cptr++;
      if (*cptr) *cptr++ = '\0';
      if (VarName[9]) CgiLibUrlDecode (VarName+9);
      if (VarValuePtr[0]) CgiLibUrlDecode (VarValuePtr);
      if (CgiLib__CgiPrefixWWW)
         CgiLib__VarList (VarName, VarValuePtr, NULL);
      else
         CgiLib__VarList (VarName+4, VarValuePtr, NULL);
      VarCount++;
   }

   free (BufferPtr);
   return (VarCount);
}

/*****************************************************************************/
/*
Converts the POSTed MIME multipart form-data body parameters into
"WWW_FORM_field-name" CGI variables accessable from the variable list.  As it
is possible to POST unencoded characters with this method (all characters
from 0x00 to 0xff are transmitted unencoded) non-printable characters are
HTML-entified (i.e. 0x00 becomes "&#0;", 0x01 "&#1;", etc.) before storage as
variables.  These of course would need to be de-entified before use (with
CgiLibHtmlDeEntify()).  If the field is an upload ("type=file") and there is
an associated content-type and file name (as there should be) these are placed
in variables with the same name as the field with "_MIME_CONTENT_TYPE" and
"_MIME_FILENAME" added.

This is an incomplete MIME decode and may be a bit brain-dead, I don't know
much about it at all.  Seems to work with the big-three browsers I've tried
it on, NetNav, MSIE and Opera.
*/

int CgiLib__BodyMultipartFormData
(
char *BodyPtr,
int BodyLength,
char *BoundaryPtr
)
{
   static int  CheckedEntifyIsprint,
               IsprintEntify;

   static char  VarName [256] = "WWW_FORM_";

   int  BoundaryLength,
        BufferSize,
        ContentLength,
        VarCount,
        VarNameLength;
   char  ch;
   char  *bptr, *cptr, *sptr, *zptr,
         *BufferPtr,
         *ContentTypePtr,
         *EndBodyPtr,
         *EndHeaderPtr,
         *StartHeaderPtr;
   char  Boundary [256],
         ContentType [256],
         FileName [256];

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

   if (CGILIB__DEBUG) fprintf (stdout, "CgiLib__BodyMultipartFormData()\n");

   if (!CheckedEntifyIsprint)
   {
      /* v1.6.7 used isprint(), this allows selected backward compatibility */
      CheckedEntifyIsprint = 1;
      if (getenv ("CGILIB_MULTIPART_ISPRINT")) IsprintEntify = 1;
   }

   zptr = (sptr = Boundary) + sizeof(Boundary)-1;
   for (cptr = BoundaryPtr; *cptr && *cptr != '='; cptr++);
   if (!*cptr) return (0);
   cptr++;
   if (*cptr == '\"')
   {
      cptr++;
      while (*cptr && *cptr != '\"' && sptr < zptr) *sptr++ = *cptr++;
   }
   else
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   BoundaryLength = sptr - Boundary;

   if (CGILIB__DEBUG)
      fprintf (stdout, "Boundary %d |%s|\n",
               BoundaryLength, Boundary);

   /***************************/
   /* loop through body parts */
   /***************************/

   VarCount = 0;
   EndBodyPtr = (cptr = BodyPtr) + BodyLength;
   while (cptr < EndBodyPtr)
   {
      if (CGILIB__DEBUG) fprintf (stdout, "|%.128s|\n", cptr);

      if ((cptr = strstr (cptr, Boundary)) == NULL) return (0);
      cptr += BoundaryLength;
      if (*(unsigned short*)cptr == '--') break;
      if (*(unsigned short*)cptr != '\r\n') return (0);
      cptr += 2;

      /***********************/
      /* process part header */
      /***********************/

      /* note the start and end of the part header information */
      StartHeaderPtr = cptr;
      if ((EndHeaderPtr = strstr (cptr, "\r\n\r\n")) == NULL) return (0);

      if ((cptr = strstr (StartHeaderPtr, "Content-Disposition:")) == NULL)
         return (0);
      cptr += 20;

      if ((cptr = strstr (StartHeaderPtr, "form-data;")) == NULL)
         return (0);
      cptr += 10;

      if ((cptr = strstr (StartHeaderPtr, "name=")) == NULL) return (0);
      if (cptr > EndHeaderPtr) return (0);
      cptr += 5;
      ch = '\0';
      if (*cptr == '\"') ch = *cptr++;
      /* minus 19, one for the terminating null, 18 for max "_MIME_..." */
      zptr = (sptr = VarName) + sizeof(VarName)-19;
      sptr += 9;
      while (*cptr && *cptr != ch && sptr < zptr) *sptr++ = toupper(*cptr++);
      if (sptr >= zptr) while (*cptr && *cptr != ch) cptr++;
      if (*cptr) cptr++;   
      *sptr = '\0'; 
      VarNameLength = sptr - VarName;
      if (CGILIB__DEBUG) fprintf (stdout, "VarName |%s|\n", VarName);

      FileName[0] = '\0';
      if ((cptr = strstr (StartHeaderPtr, "filename=")) != NULL &&
          cptr < EndHeaderPtr)
      {
         cptr += 9;
         ch = '\0';
         if (*cptr == '\"') ch = *cptr++;
         zptr = (sptr = FileName) + sizeof(FileName)-1;
         while (*cptr && *cptr != ch && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr) while (*cptr && *cptr != ch) cptr++;
         if (*cptr) cptr++;   
         *sptr = '\0';
         if (CGILIB__DEBUG) fprintf (stdout, "FileName |%s|\n", FileName);
      }

      ContentType[0] = '\0';
      if ((cptr = strstr (StartHeaderPtr, "Content-Type:")) != NULL &&
          cptr < EndHeaderPtr)
      {
         for (cptr += 13; *cptr && isspace(*cptr); cptr++);
         zptr = (sptr = ContentType) + sizeof(ContentType)-1;
         while (*cptr && !isspace(*cptr) && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
         if (CGILIB__DEBUG)
            fprintf (stdout, "ContentType |%s|\n", ContentType);
      }

      /**************************/
      /* retrieve the part data */
      /**************************/

      sptr = cptr = EndHeaderPtr + 4;
      for (;;)
      {
         /* find the next boundary */
         if (CGILIB__DEBUG) fprintf (stdout, "|%.72s|\n", sptr);
         for (;;)
         {
            if (sptr >= EndBodyPtr) return (0);
            while (sptr < EndBodyPtr && *sptr != '\r') sptr++;
            if (!strncmp (sptr, "\r\n--", 4)) break;
            sptr++;
         }
         /* if end of body, break; */
         if (!sptr[4]) break;
         /* if not end of body then should be a boundary */
         if (!strncmp (sptr+4, Boundary, BoundaryLength)) break;
         /* nope, must have been data */
         sptr += 4;
      }

      if (CGILIB__DEBUG)
         fprintf (stdout, "start: %d end: %d bytes: %d\n",
                  cptr, sptr, sptr-cptr);

      /* get a worst-case value for HTML-entifying all unacceptable chars */
      BufferSize = sptr - cptr + 1;
      if (IsprintEntify)
      {
         /* pre-v1.6.7 */
         for (bptr = cptr; bptr < sptr; bptr++)
            if ((!isprint(*bptr) && !isspace(*bptr)) || *bptr == '&')
               BufferSize += 5;
      }
      else
      {
         /* current default method */
         for (bptr = cptr; bptr < sptr; bptr++)
            if ((unsigned char)*bptr <= 0x1f ||
                ((unsigned char)*bptr >= 0x7f &&
                 (unsigned char)*bptr <= 0x9f) ||
                (unsigned char)*bptr == 0xff ||
                *bptr == '&')
               BufferSize += 5;
      }
      if (CGILIB__DEBUG) fprintf (stdout, "BufferSize: %d\n", BufferSize);

      if ((bptr = BufferPtr = calloc (1, BufferSize)) == NULL)
         exit (SS$_INSFMEM);

      if (IsprintEntify)
      {
         /* pre-v1.6.7 */
         while (cptr < sptr)
         {
            if ((isprint (*cptr) || isspace(*cptr)) && *cptr != '&')
            {
               *bptr++ = *cptr++;
               continue;
            }
            bptr += sprintf (bptr, "&#%u;", *cptr++ & 0xff);
         }
      }
      else
      {
         /* current default method */
         while (cptr < sptr)
         {
            if (!((unsigned char)*cptr <= 0x1f ||
                  ((unsigned char)*cptr >= 0x7f &&
                   (unsigned char)*cptr <= 0x9f) ||
                  (unsigned char)*cptr == 0xff ||
                  *cptr == '&'))
            {
               *bptr++ = *cptr++;
               continue;
            }
            bptr += sprintf (bptr, "&#%u;", *cptr++ & 0xff);
         }
      }
      *bptr = '\0';
      if (CGILIB__DEBUG) fprintf (stdout, "BufferPtr |%s|\n", BufferPtr);

      /******************************/
      /* store associated variables */
      /******************************/

      if (CgiLib__CgiPrefixWWW)
         CgiLib__VarList (VarName, BufferPtr, NULL);
      else
         CgiLib__VarList (VarName+4, BufferPtr, NULL);
      VarCount++;

      free (BufferPtr);

      /* if a file name associated with the field (i.e. and upload) */
      if (ContentType[0])
      {
         strcpy (VarName + VarNameLength, "_MIME_CONTENT_TYPE");
         if (CgiLib__CgiPrefixWWW)
            CgiLib__VarList (VarName, ContentType, NULL);
         else
            CgiLib__VarList (VarName+4, ContentType, NULL);
         VarCount++;
      }

      /* if a file name associated with the field (i.e. and upload) */
      if (FileName[0])
      {
         strcpy (VarName + VarNameLength, "_MIME_FILENAME");
         if (CgiLib__CgiPrefixWWW)
            CgiLib__VarList (VarName, FileName, NULL);
         else
            CgiLib__VarList (VarName+4, FileName, NULL);
         VarCount++;
      }

      /* step over the "\r\n--" */
      cptr += 4;
   }

   return (VarCount);
}

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

       /***********************/
#endif /* CGILIB_POST_SUPPORT */
       /***********************/

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

