/*****************************************************************************/
/*
                                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.7 CGILIB only supports compilation into an object module as
described immediately below.  IT NO LONGER SUPPORTS THE DIRECT #include!
                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.


USING IN YOUR CODE
------------------
Build as an object module, It may then be linked against the rest of the
application.

  $ CC CGILIB /DECC /STANDARD=RELAXED_ANSI /INCLUDE=[]

Once compiled it may linked as an object module directly

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

or if it has been placed into an object library

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

or to the WASD_CGILIBSHR32 sharable image (if installed and defined)

  $ LINK /EXEC=HT_EXE:APPLICATION.EXE APPLICATION,SYS$INPUT/OPTIONS
  WASD_CGILIBSHR32/SHARE


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 
-------------
Functions and global storage beginning 'CgiLib__' are for internal CGILIB use
only, and should not directly be called by script code.


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.

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

The above 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.

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

Each of these functions returns true or false depending on which environment
the script is executing in.

  CgiLibEnvironmentBinaryOut ();
  CgiLibEnvironmentRecordOut ();

The above 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 used for non-textual output.

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

These five functions set the run-time characteristsic of the CGILIB
environment.  The first and second return a string identifying the server and
CGILIB environment.  The third accepts a pointer to either NULL or a string
(most usually ""). The fourth turns CGILIB debug on or off.  The fifth returns
an integer representing the WASD server version (e.g. 80201 for v8.2.1). 

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

This 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.


REQUEST RESPONSES
-----------------
These functions allow HTTP response headers and defacto WASD error and
"success" report pages to be generated.

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

The above function generates an HTTP response header.  In most environments
these will be CGI-compliant responses.  This allows the server to provide
whatever additional header fields it deems required.  On an OSU server this
will always be a full NPH HTTP response.  This function also handles 302
(redirection, see also  'CgiLibResponseRedirect()') and 401 (authentication)
responses.  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 CgiLibResponseRedirect (char *FormatString, ...);

This function 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 CgiLibResponseError (char *SrcFileName,
                           int SrcLineNumber,
                           int StatusValue,
                           char *FormatString,
                           ...);

Generates 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 CgiLibResponseSuccess (char *FormatString, ...);

Generates 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 CgiLibResponseGetStatus ()

Returns the HTTP response status (e.g. 200, 404) after being set by response
generation (or 0).

  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)

The above collection of functions allow various response strings and status
codes to be set at run-time.  Note that these functions do not allocate
storage, whatever they are called with must point to something permanent. 

  int CgiLibResponseSetBufferRecords (int);
  int CgiLibResponseSetRecordMode (int);
  int CgiLibResponseSetStreamMode (int);

Tell the WASD server that these CGI response characteristics should be
enabled/disabled.  Must be used prior to CgiLibResponseHeader().  Returns
previous value.


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 a sure 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.


CGI VARIABLES
-------------
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_".  It 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).

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

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, 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* CgiLibVarNull (char *VarName);

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.

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

This 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.


CGIPLUS
-------
In the WASD 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.

  CgiLibCgiPlusEOF ();

This 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.

  CgiLibCgiPlusESC ();
  CgiLibCgiPlusEOT ();

These two functions output records to delimit the beginning and and
(respectively) of a CGIplus callout.

  void CgiLibCgiPlusInGets (char *String, int SizeOfString);

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.

  char *CgiLibCgiPlusCallout (char *Format, ...);

The above function allows a CGIplus callout to be performed with a format
string and variable number of arguments.  If the format string (callout) does
not begin with a '!' or '#' (no response expected) then a callout response is
read into static storage and and a pointer to that returned.

  void CgiLibCgiPlusSetVarRecord ();
  void CgiLibCgiPlusSetVarStruct ();

The two functions above 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!


FORM-ENCODED PARSE
------------------

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

  int CgiLibUrlDecode (char *String);

Decodes 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.


URL-ENCODE
----------

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

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).

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

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

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

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

This function escapes 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 CgiLibAnchorHtmlEscape (char *PlainString,
                              int NumberOfChars,
                              char *EscapedString,
                              int SizeOfEscapedString,
                              int AnchorUrls);

With this function '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.
Normally this will open the link in the same page.  To split the link into a
left half that opens target="_top" and a right half that opens target="_blank"
bitwise OR in CGILIB_ANCHOR_SPLIT.

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

  int CgiLibHtmlDeEntify (char *String)

Converts 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 CgiLibUrlMaxShorten (int MaxChars)

When non-zero this shortens the displayed portion of an escaped URL to a
maximum of this number of characters.


VEEMEM
------

Virtual memory support functions using the LIB$*_VM_* routines.

Use CgiLibVeeMemInit(), CgiLibVeeMemCalloc(), CgiLibVeeMemRealloc(),
CgiLibVeeMemFree() and CgiLibVeeMemReset() to manage application dynamic
memory.  Using CgiLibVeeMemReset() at the conclusion of processing releases
*all* zone allocated memory in one fell swoop.  This significantly simplifies
memory management and reduces the chances of memory leaks.

CgiLibVeeMemInUse() controls whether applicable CGILIB functions also use
VeeMem.  CGILIB functions that are VeeMem-capable: CgiLibUrlEncode(),
CgiLibUrlEncodeFileName(), CgiLibHtmlEscape(), CgiLibAnchorHtmlEscape(),
CgiLibFormEnocdedParse(), CgiLibFaoPrint(), CgiLibVarByDesc() .

CAUTION: All allocated memory must no longer be in use before zone reset!!

Storage that needs to persist across VeeMemReset() should be calloc()ed.

CgiLibVeeMemContinue() is used to control behaviour on memory allocation
failure.  CGIlib usually just exits with an error status when a memory
allocation fails. With some memory allocation it's better to know that it
failed.  Calling this with either 1 (continue returning the expected result
when failed) or 0 (just exit with the error status) allows fine-grained
behaviour control when using functions that employ dynamic memory.  Works for
both CgiLibVeeMem..() allocated memory and calloc()/realloc() etc.  Returns the
previous state.


MISCELLANEOUS
-------------

  char* CgiLibHttpStatusCodeText (int StatusCode);

Returns a string to a "standard" status code meaning, e.g. 200 "Success".

  char* CgiLibFaoPrint (FILE *StreamPtr, char *FormatString, ...);

This is really just a convenience function and has little to do with CGI
scripting.  VMS $FAO-formatted print statement.  Output is either to the
specified stream, or if stream pointer NULL return a pointer to a dynamic
buffer buffer containing the formatted output (requires caller to dispose of it
as necessary).

  char* CgiLibXUACompatible (char* ContentPtr);

Set and return the MSIE "X-UA-Compatible" compatibility HTML meta field. 

  http://blogs.msdn.com/b/askie/archive/2009/03/23/understanding-compatibility-modes-in-internet-explorer-8.aspx

The meta string is designed to allow being directly included in the response
<head>..</head> data.  It should be the first field following the opening
<head>.  If 'ContentPtr' is NULL return the current value of the meta string
(will be an empty string if uninitialised).  If 'ContentPtr' is to an empty
string it defaults to "IE=edge".  If a non-empty string the meta content=".."
will contain the string parameter.  If the string pointer has the value
(char*)(-1) then reset the meta to NULL.  Setting this value also results in
CGILIB <head>..</head> data containing the meta.


PROCESSING POSTED REQUESTS
--------------------------
The processing POSTed request bodies can often be tiresome to code without the
use of a library.  The functions provided by CGILIB attempt to provide the
basic function required.

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

The above 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.
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.

  int CgiLibFormRequestBody (char *BodyPtr, int BodyLength);

This takes a "application/x-www-form-urlencoded" or "multipart/form-data" body
(which could have been read with the preceding 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 CgiLibBodyIsFormUrlEncoded ();
   int CgiLibBodyIsMultipartFormData ();

These functions return true if the request body is
"application/x-www-form-urlencoded" and "multipart/form-data" respectively.


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.


OSU ENVIRONMENT
---------------
The OSU (DECthreads) HTTP server DECnet-based scripting environment is
supported semi-transparently.

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

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.

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

  CgiLibOsuStdoutRaw ();

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

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.

  CgiLibOsuStdoutCgi ();

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

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.


COPYRIGHT
---------
Copyright (C) 1999-2022 Mark G.Daniel

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


VERSION HISTORY (update CGILIB_VERSION as well!)
---------------
15-JUL-2025  MGD  v2.2.0, CgiLibUrlMaxShorten() the displayed portion of a URL
26-AUG-2023  MGD  v2.1.2, GATEWAY_SYMBOLS allows vanilla CGI CgiVar("*")
28-MAY-2022  MGD  v2.1.1, CgiLibAnchorHtmlEscape() escape single quote
05-JUN-2021  MGD  v2.1.0, CgiLibCgiPlusCallout()
                          bugfix; CgiLibCgiPlusInGets() absorb empty lines
22-MAY-2020  MGD  v1.9.9, bugfix; CgiLib__ApacheSetSockOpt() LIB$M_FIS_MIXEDCASE
08-FEB-2020  MGD  v1.9.8, CgiLibAnchorHtmlEscape() handle NULL string
22-MAY-2018  MGD  v1.9.7, CgiLib__ApacheSetSockOpt() both VSI and HP(E) symbols
23-DEC-2017  MGD  v1.9.6, CgiLibVeeMemContinue() allocation failure behaviour
16-OCT-2013  MGD  v1.9.5, CgiLibXUACompatible() <meta "X-UA-Compatible">
09-JUN-2013  MGD  v1.9.4, tweak HTML a little
25-FEB-2012  MGD  V1.9.3, CgiLibUrlDecode() decode URL-encoded 8 bit UTF-8
04-JAN-2011  MGD  v1.9.2, bugfix; CgiLibHttpStatusCodeText() 203/204 transposed
04-MAR-2010  MGD  v1.9.1, CgiLibReadRequestBody() accomodate CgiLibVeeMem..()
                          CgiLibVeeMem..() to use void* rather than char*
23-MAY-2009  MGD  v1.9.0, CgiLibVeeMem..() functions
19-MAY-2009  MGD  v1.8.21, bugfix; CgiLib__ApacheExitRecordMode()
                             flush stream before reverting to record mode
11-MAR-2009  MGD  v1.8.20, bugfix; CgiLibResponseHeader() make "text/"
                             content-type detection case-insensitive
21-JAN-2009  MGD  v1.8.19, bugfix; CgiLib__ApacheExitRecordMode()
                             allow for both Apache versions 1.n and 2.n 
12-NOV-2008  MGD  v1.8.18, CgiLibHtmlEscape() retain empty URLs as text
19-OCT-2008  MGD  v1.8.17, CgiLibOsuInit() noticed OSU had no SERVER_ADDR
21-JUL-2008  MGD  v1.8.16, CgiLibAnchorHtmlEscape() soyMAIL hack
                             to make "mailto:"s access soyMAIL as mail agent
                           bugfix; CgiLib__BodyMultipartFormData() boundary=
                           bugfix; CgiLib__VarList() use offsets rather than
                             pointers when realloc() exhausted storage
13-APR-2008  MGD  v1.8.15, bugfix; CgiLibResponseSet[Default]Charset()
                           do not allow a null pointer charset to be set
10-APR-2008  JPP  v1.8.14, bugfix; CgiLib__VarList() storage overflow 
28-MAR-2008  MGD  v1.8.13, CgiLibHtmlDeEntify() enhance entity scope
03-APR-2007  MGD  v1.8.12, add CgiLibResponseGetStatus() and assoc code
                           emulate CGI variable REQUEST_URI for OSU
26-MAR-2007  MGD  v1.8.11, bugfix; CgiLibOsuInit() <DNETHDR> no URL-decode
12-JAN-2007  MGD  v1.8.10, CgiLib__VarList() kludge to obfuscate FORM_
                           variables for soyMAIL purposes
30-OCT-2006  MGD  v1.8.9, bugfix; CgiLib__GetPathTranslated()
21-APR-2006  MGD  v1.8.8, CgiLibAnchorHtmlEscape() allow for 'split anchors'
                          (left side of anchor opens target=_top, right side
                          opens target=_blank) using CGILIB_ANCHOR_SPLIT
27-MAR-2006  MGD  v1.8.7, CgiLibResponseRedirect() Purveyor (at least v1.2.2)
                          seems to require a full URL for the "Location:" 
16-MAR-2006  MGD  v1.8.6, bugfix; CgiLibResponseRedirect() OSU seems to need
                          a "Content-Type:.." in with the "Status: 302"!?
02-MAR-2006  MGD  v1.8.5, bugfix; CgiLibEnvironmentInit() for VMS Apache V2.n
                          and later (as with Apache V1.n) freopen("mrs=16384")
25-JAN-2006  MGD  v1.8.4, bugfix; when URL-encoded decoding use unsigned char
                          to prevent sign bit issues with the likes of %FC,
                          bugfix; CgiLibEnvironmentName() static storage
30-SEP-2005  MGD  v1.8.3, only generate a "100 Continue" if there was an
                          appropriate "Expect: 100-continue" request header
07-SEP-2005  MGD  v1.8.2, CgiLibBodyIsFormUrlEncoded()
                          CgiLibBodyIsMultipartFormData()
                          add file:// to CgiLibAnchorHtmlEscape()
19-MAY-2005  MGD  v1.8.1, bugfix; APACHE$INPUT seems to return -1 rather
                          than 0 (as HTTP$INPUT) when input stream exhausted
                          CgiLibReadRequestBody() check read against length
04-MAY-2005  MGD  v1.8.0, support SWS 2.0 along with CSWS 1.2/1.3,
                          CgiLib__ApacheSetSockOpt() for SWS 2.0 BG control
22-JAN-2005  MGD  v1.7.4, CgiLibResponseSetBufferRecords(),
                          CgiLibResponseSetRecordMode(),
                          CgiLibResponseSetStreamMode()
12-DEC-2004  MGD  v1.7.3, bugfix; CgiLib__PrepareRequest() check for
                          content-type "multipart/form-data"
21-OCT-2004  MGD  v1.7.2, detection of WASD server request body unencode
                          using a CGI variable CONTENT_LENGTH set to "?",
                          CgiLibEnvironmentWasdVersion()
07-OCT-2004  JFP  v1.7.1, options file and build procedure for sharable image
25-SEP-2004  MGD  v1.7.0, HTTP/1.1 compliance,
                          obsolete ability to #include CGILIB source
                          new function CgiLibFaoPrint()
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 structure 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
*/
/*****************************************************************************/

#define CGILIB_VERSION "2.2.0"
#ifdef __ALPHA
#   define CGILIB_SOFTWAREID "CGILIB AXP-" CGILIB_VERSION
#endif
#ifdef __ia64
#   define CGILIB_SOFTWAREID "CGILIB IA64-" CGILIB_VERSION
#endif
#ifdef __x86_64
#   define CGILIB_SOFTWAREID "CGILIB X86-" CGILIB_VERSION
#endif

/* 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 <libvmdef.h>
#include <stsdef.h>
#include <ssdef.h>
#include <starlet.h>

/* required for OSU server address */

/**
HP C V7.1-015 on OpenVMS Alpha V8.3 reports the following (apparently) because
of a nesting error in RESOLV.H due to the __CRTL_VER constraint that WASD
applications are built with.  This does not happen with Compaq C V6.4-005 on
OpenVMS Alpha V7.3-2 for instance.  Just avoid the error by pretending the
header file is already loaded.  Doesn't *seem* to cause side-effects.

#pragma __member_alignment __restore
........^
%CC-W-ALIGNPOP, This "restore" has underflowed the member alignment's stack. No corresponding "save" was found.
at line number 451 in module RESOLV of text library SYS$COMMON:[SYSLIB]DECC$RTLD
**/

#define __RESOLV_LOADED 1
#include <netdb.h>

/* module header */
#ifndef CGILIB_OBJECT_MODULE
#define CGILIB_OBJECT_MODULE
#endif
#ifdef CGILIB_OBJECT_MODULE
#include "cgilib.h"
#else
#error   *** CGILIB V1.7 #include at compile time is no longer supported ***
#endif

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

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

int  CgiLib__Debug,
     CgiLib__CgiPrefixWWW,
     CgiLib__CgiPlusLoadVariables = 1,
     CgiLib__CgiPlusUsageCount,
     CgiLib__CgiPlusVarRecord,
     CgiLib__CgiPlusVarStruct,
     CgiLib__Environment,
     CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_RECORD,
     CgiLib__ApacheVersion,
     CgiLib__HttpProtocolVersion,
     CgiLib__BodyIsFormUrlEncoded,
     CgiLib__BodyIsMultipartFormData,
     CgiLib__ResponseBufferRecords,
     CgiLib__ResponseCount,
     CgiLib__ResponseCgi,
     CgiLib__ResponseErrorStatus,
     CgiLib__ResponseRecordMode,
     CgiLib__ResponseStatusCode,
     CgiLib__ResponseStreamMode,
     CgiLib__ResponseSuccessStatus,
     CgiLib__UrlMaxShorten,
     CgiLib__VeeMemCurrent,
     CgiLib__VeeMemInUse,
     CgiLib__VeeMemPeak,
     CgiLib__VeeMemContinue,
     CgiLib__WasdVersion;

unsigned long  CgiLib__VeeMemZoneId;

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

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

FILE  *CgiLib__CgiPlusInFile;

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

                              /**************/
                              /* INITIALIZE */
                              /**************/

/*****************************************************************************/
/*
(jpp@esme.fr)  Wrapper function for transparently 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") ||
          strstr (CgiContentTypePtr, "multipart/form-data"))
      {
         /* 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;
         }
         if (CgiLib__VeeMemInUse)
            CgiLibVeeMemFree (PostBufferPtr);
         else
            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 */
   /*********/

   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;

   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__Environment == CGILIB_ENVIRONMENT_OSU)
   {
      /*******************/
      /* OSU environment */
      /*******************/

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

      CgiLibOsuInit (argc, argv);

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

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

#ifdef BLAH
   if (CgiLib__Debug)
   {
      /* debug output is plain-text */
      fputs (
"Content-Type: text/plain\nScript-Control: X-content-encoding-gzip=0\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.
         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)
      {
         if (CgiLib__ApacheVersion == 1)
            CgiLib__ApacheFixBg(1);
         else
         {
            /* SWS 2.0 (and hopefully later :-) binary mode and buffer size */
            CgiLib__ApacheSetSockOpt(0);
            CgiLib__ApacheSetSockOpt(16384);
         }
         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);
   }

   CgiLib__ResponseCount = 0;
}

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

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

#if CGILIB_DEVELOPMENT

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

#endif

   CgiLib__Environment = 0;

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

   /* 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, CGIplus? */
      if ((CgiLib__CgiPlusEofPtr = getenv("CGIPLUSEOF")) != NULL)
      {
         CgiLib__Environment = CGILIB_ENVIRONMENT_CGIPLUS;
         return;
      }

      CgiLib__Environment = CGILIB_ENVIRONMENT_WASD;
      return;
   }

   /* the first is CSWS 1.2/1.3, the other two for SWS 2.0 */
   if (getenv ("APACHE$SHARED_SOCKET") != NULL ||
       getenv ("APACHE$PARENT_PID") != NULL ||
       getenv ("APACHE$SERVER_TAG") != NULL)
   {
      CgiLib__Environment = CGILIB_ENVIRONMENT_APACHE;
      /* note whether it's CSWS version 1.2/1.3 or SWS 2.0 */
      if (getenv ("APACHE$SHARED_SOCKET") != NULL)
         CgiLib__ApacheVersion = 1;
      else
         CgiLib__ApacheVersion = 2;
      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;
}

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

                         /*************************/
                         /* OUTPUT STREAM CONTROL */
                         /*************************/

/*****************************************************************************/
/*
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__Environment == CGILIB_ENVIRONMENT_OSU) return;

   if (CgiLib__Debug) return;

   if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE)
   {
      if (CgiLib__ApacheVersion == 1)
         CgiLib__ApacheFixBg(1);
      else
         CgiLib__ApacheSetSockOpt(0);
   }

   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__Environment == CGILIB_ENVIRONMENT_OSU) return;

   if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE)
   {
      if (CgiLib__ApacheVersion == 1)
         CgiLib__ApacheFixBg(0);
      else
         CgiLib__ApacheSetSockOpt(1);
   }

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

/*****************************************************************************/
/*
For SWS version 2.0!  Based on information provided in private communication
with Richard Barry (thanks Rick) this function calls the apache$$setsockopt()
function to adjust the BG device's characteristics suitable for binary and
record output.  To select binary mode call with a parameter of 0, record mode
with 1, toggle the modes with -1, and set the BG device buffer size with any
value greater than 255 and less than or equal to 65535.

This is based on a function description provided by Rick, 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$APR_SHRP
shareable image available.
*/

void CgiLib__ApacheSetSockOpt (int OptionValue)

{
#define OPT_CCLBIT    1
#define OPT_BUFSIZ    2
#define OPT_SHAREABLE 3
#define LIB$M_FIS_MIXEDCASE 0x10

   static int  IsBin;
   static unsigned short  SysOutputChannel;
   static int (*ApacheSetSockOpt)(unsigned short, int, int*, int);

   static $DESCRIPTOR (StdOutputDsc, "SYS$OUTPUT");
   static $DESCRIPTOR (ApacheAprShrpImageDsc, "APACHE$APR_SHRP");
   static $DESCRIPTOR (HpeApacheSetSockOptDsc, "APACHE$$SETSOCKOPT");
   static $DESCRIPTOR (VsiApacheSetSockOptDsc, "apr$$setsockopt");

   int  status;
   char  *aptr;

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

   if (CgiLib__Debug)
      fprintf (stdout, "CgiLib__ApacheSetSockOpt() %d\n", OptionValue);

   if (SysOutputChannel)
      status = SS$_NORMAL;
   else
   {
#ifndef __VAX
      /* first look for the VSI Apache symbol */
      aptr = VsiApacheSetSockOptDsc.dsc$a_pointer;
      lib$establish (CgiLib__ApacheSetSockOptHandler);
      status = lib$find_image_symbol (&ApacheAprShrpImageDsc,
                                      &VsiApacheSetSockOptDsc,
                                      &ApacheSetSockOpt, 0,
                                      LIB$M_FIS_MIXEDCASE);
      if (CgiLib__Debug)
         fprintf (stdout, "lib$find_image_symbol() %%X%08.08X\n", status);
      lib$revert ();
      if (!(status & 1))
#endif /* __VAX */
      {
         /* second look for the HP(E) Apache symbol */
         aptr = HpeApacheSetSockOptDsc.dsc$a_pointer;
         lib$establish (CgiLib__ApacheSetSockOptHandler);
         status = lib$find_image_symbol (&ApacheAprShrpImageDsc,
                                         &HpeApacheSetSockOptDsc,
                                         &ApacheSetSockOpt, 0, 0);
         if (CgiLib__Debug)
            fprintf (stdout, "lib$find_image_symbol() %%X%08.08X\n", status);
         lib$revert ();
      }
      if (status & 1)
      {
         status = sys$assign (&StdOutputDsc, &SysOutputChannel, 0, 0);
         atexit (&CgiLib__ApacheExitRecordMode);
      }
   }

   if (status & 1)
   {
      if (OptionValue > 255)
         status = ApacheSetSockOpt (SysOutputChannel, OPT_BUFSIZ, 
                                    &OptionValue, sizeof(OptionValue));
      else
         status = ApacheSetSockOpt (SysOutputChannel, OPT_CCLBIT, 
                                    &OptionValue, sizeof(OptionValue));
   }

   if (status & 1) return;

   /* let's make it a bit more obvious than when delving through error logs */
   fprintf (stdout,
"Content-Type: text/plain\r\n\
\r\n\
*** ERROR ACTIVATING %s in APACHE$APR_SHRP ***\n\
\n", aptr);

   exit (status);

#if 0
   static int  IsBin;
   static unsigned long flags = LIB$M_FIS_MIXEDCASE;
   static unsigned short  SysOutputChannel;
   static int (*ApacheSetSockOpt)(unsigned short, int, int*, int);

   static $DESCRIPTOR (StdOutputDsc, "SYS$OUTPUT");
   static $DESCRIPTOR (ApacheAprShrpImageDsc, "APACHE$APR_SHRP");
   static $DESCRIPTOR (HpeApacheSetSockOptDsc, "APACHE$SETSOCKOPT");
   static $DESCRIPTOR (VsiApacheSetSockOptDsc, "apr$$setsockopt");

   int  status;

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

   if (CgiLib__Debug)
      fprintf (stdout, "CgiLib__ApacheSetSockOpt() %d\n", OptionValue);
/**
CgiLib__Debug = 1;
printf("content-type: text/plain\n");
fflush (stdout);
puts ("\n");
fflush (stdout);
**/

   if (SysOutputChannel)
      status = SS$_NORMAL;
   else
   {
      lib$establish (CgiLib__ApacheSetSockOptHandler);
      status = lib$find_image_symbol (&ApacheAprShrpImageDsc,
                                      &ApacheSetSockOptDsc,
                                      &ApacheSetSockOpt,
                                      0,
                                      flags);
      if (CgiLib__Debug)
         fprintf (stdout, "lib$find_image_symbol() %%X%08.08X\n", status);
      lib$revert ();
      if (status & 1)
      {
         status = sys$assign (&StdOutputDsc, &SysOutputChannel, 0, 0);
         atexit (&CgiLib__ApacheExitRecordMode);
      }
#endif
}

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

int CgiLib__ApacheSetSockOptHandler ()

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

   if (CgiLib__Debug) fprintf (stdout, "CgiLib__ApacheSetSockOptHandler()\n");

   return (SS$_CONTINUE);
}

/*****************************************************************************/
/*
For CSWS version 1.2 and 1.3!  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)
      {
         status = sys$assign (&StdOutputDsc, &SysOutputChannel, 0, 0);
         atexit (&CgiLib__ApacheExitRecordMode);
      }
   }

   if (status & 1) status = ApacheFixBg (SysOutputChannel, 1);

   if (status & 1) return;

   /* let's make it a bit more obvious than when delving through error logs */
   fprintf (stdout,
"Content-Type: text/plain\r\n\
\r\n\
*** ERROR ACTIVATING apache$fixbg() in APACHE$FIXBG ***\n\
\n");

   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");

   fflush (stdout);
   if (CgiLib__ApacheVersion == 1)
      CgiLib__ApacheFixBg(0);
   else
      CgiLib__ApacheSetSockOpt(1);
}

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

                          /**********************/
                          /* SERVER ENVIRONMENT */
                          /**********************/

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

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

/*****************************************************************************/
/*
If WASD, from the SERVER_SOFTWARE containing something like "HTTPd-WASD/8.2.1
OpenVMS/AXP SSL" return a number like 080201 (80,201).
*/

int CgiLibEnvironmentWasdVersion ()

{
   char  *cptr;

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

   if (CgiLib__Debug) fprintf (stdout, "CgiLibEnvironmentWasdVersion()\n");

   if (CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS ||
       CgiLib__Environment == CGILIB_ENVIRONMENT_WASD)
   {
      if (!CgiLib__WasdVersion)
      {
         cptr = CgiLib__GetVar ("WWW_SERVER_SOFTWARE", "");
         if (cptr)
         {
            while (*cptr && !isdigit(*cptr)) cptr++;
            CgiLib__WasdVersion = atoi(cptr) * 10000;
            while (*cptr && isdigit(*cptr)) cptr++;
            if (*cptr) cptr++;
            CgiLib__WasdVersion += atoi(cptr) * 100;
            while (*cptr && isdigit(*cptr)) cptr++;
            if (*cptr) cptr++;
            CgiLib__WasdVersion += atoi(cptr);
         }
      }
   }

   if (CgiLib__Debug) fprintf (stdout, "%d\n", CgiLib__WasdVersion);

   return (CgiLib__WasdVersion);
}

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

/*****************************************************************************/
/*
Returns an integer 11 if the current request HTTP protocol version is 1.1, 10
if it's 1.0, or 0 if it's unknown or otherwise not determined, as provided by
the SERVER_PROTOCOL CGI variable.
*/

int CgiLibHttpProtocolVersion ()

{
   static int  UsageCount;
   char  *cptr;

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

   if (CgiLib__Debug) fprintf (stdout, "CgiLibHttpProtocolVersion()\n");

   if (!UsageCount || UsageCount != CgiLib__CgiPlusUsageCount)
   {
      UsageCount = CgiLib__CgiPlusUsageCount;
      /* a misnomer, it's really request protocol, but never mind */
      cptr = CgiLib__GetVar ("WWW_SERVER_PROTOCOL", "");
      if (!strcmp (cptr, "HTTP/1.1"))
      {
         CgiLib__HttpProtocolVersion = 11;
         CgiLib__HttpProtocolVersionPtr = "HTTP/1.1";
      }
      else
      if (!strcmp (cptr, "HTTP/1.0"))
      {
         CgiLib__HttpProtocolVersion = 10;
         CgiLib__HttpProtocolVersionPtr = "HTTP/1.0";
      }
      else
      {
         CgiLib__HttpProtocolVersion = 0;
         CgiLib__HttpProtocolVersionPtr = "HTTP/?.?";
      }
   }

   if (CgiLib__Debug)
      fprintf (stdout, "|%s| %d\n",
               CgiLib__HttpProtocolVersionPtr,
               CgiLib__HttpProtocolVersion);
   return (CgiLib__HttpProtocolVersion);
}

/*****************************************************************************/
/*
Set and return the MSIE "X-UA-Compatible" compatibility HTML meta field. 
*/

char* CgiLibXUACompatible (char* ContentPtr)

{
   static char  MetaHttpEquiv [] = "<meta http-equiv=\"X-UA-Compatible\" "
                                   "content=\"%s\">\n";
   static char  MetaXUA [sizeof(MetaHttpEquiv)+64];

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

   if (CgiLib__Debug) fprintf (stdout, "CgiLibXUACompatible()\n");

   if (ContentPtr == NULL) return (CgiLib__XUACompatiblePtr ?
                                   CgiLib__XUACompatiblePtr : "");
   if (ContentPtr == (char*)(-1)) return (CgiLib__XUACompatiblePtr = NULL);
   if (!*ContentPtr) ContentPtr = "IE=edge";
   if (strlen(ContentPtr) > 64) return (CgiLib__XUACompatiblePtr = NULL);
   sprintf (CgiLib__XUACompatiblePtr = MetaXUA, MetaHttpEquiv, ContentPtr);
   return (CgiLib__XUACompatiblePtr);
}

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

                             /*****************/
                             /* HTTP RESPONSE */
                             /*****************/

/****************************************************************************/
/*
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)) ||
       (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__ResponseCharsetPtr) == 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__ResponseDefCharsetPtr;
         }
      }
   }
   else
      CharsetPtr = "";

   CgiLib__ResponseStatusCode = StatusCode;

   if (!CgiLib__ResponseCgi &&
       CgiLib__Environment == CGILIB_ENVIRONMENT_OSU)
   {
      /* full response header (only for OSU these days) */
      ServerSoftwarePtr = CgiLib__GetVar ("WWW_SERVER_SOFTWARE", "");
      if (!ServerSoftwarePtr[0])
         ServerSoftwarePtr = CgiLib__ResponseSoftwareIdPtr;
      RequestTimeGmtPtr = CgiLibVarNull ("REQUEST_TIME_GMT");
      CgiLibHttpProtocolVersion ();

      if (StatusCode == 302)
      {
         charcnt = fprintf (ResponseStdout,
"%s 302 %s\n\
Server: %s\n\
%s%s%s\
Location: ",
            CgiLib__HttpProtocolVersionPtr,
            CgiLibHttpStatusCodeText(StatusCode),
            ServerSoftwarePtr,
            RequestTimeGmtPtr == NULL ? "" : "Date: ",
            RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr,
            RequestTimeGmtPtr == NULL ? "" : "\n");
      }
      else
      if (StatusCode == 401)
      {
         charcnt = fprintf (ResponseStdout,
"%s 401 %s\n\
Server: %s\n\
%s%s%s",
            CgiLib__HttpProtocolVersionPtr,
            CgiLibHttpStatusCodeText(StatusCode),
            ServerSoftwarePtr,
            RequestTimeGmtPtr == NULL ? "" : "Date: ",
            RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr,
            RequestTimeGmtPtr == NULL ? "" : "\n");
        if (argcnt > 2) fprintf (stdout, "WWW-Authenticate: ");
      }
      else
      if (ContentTypeText)
      {
         charcnt = fprintf (ResponseStdout,
"%s %d %s\n\
Server: %s\n\
%s%s%s\
Content-Type: %s%s%s%s",
            CgiLib__HttpProtocolVersionPtr,
            StatusCode, CgiLibHttpStatusCodeText(StatusCode),
            ServerSoftwarePtr,
            RequestTimeGmtPtr == NULL ? "" : "Date: ",
            RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr,
            RequestTimeGmtPtr == NULL ? "" : "\n",
            ContentType, CharsetPtr[0] ? "; charset=" : "", CharsetPtr,
            ContentTypeNewLine);
      }
      else
      {
         charcnt = fprintf (ResponseStdout,
"%s %d %s\n\
Server: %s\n\
%s%s%s\
Content-Type: %s\n",
            CgiLib__HttpProtocolVersionPtr,
            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",
            CgiLibHttpStatusCodeText(StatusCode));
        if (argcnt > 2) fprintf (stdout, "WWW-Authenticate: ");
      }
      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 && argcnt > 2))
   {
      fputs ("\n", ResponseStdout);
      charcnt++;
   }

   if (CgiLib__Environment == CGILIB_ENVIRONMENT_WASD ||
       CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS)
   {
      if (CgiLib__ResponseStreamMode > 0)
         charcnt = fprintf (ResponseStdout,
                            "Script-Control: X-stream-mode=1\n");
      else
      if (CgiLib__ResponseStreamMode < 0)
         charcnt = fprintf (ResponseStdout,
                            "Script-Control: X-stream-mode=0\n");
      else
      if (CgiLib__ResponseRecordMode > 0)
         charcnt = fprintf (ResponseStdout,
                            "Script-Control: X-record-mode=1\n");
      else
      if (CgiLib__ResponseRecordMode < 0)
         charcnt = fprintf (ResponseStdout,
                            "Script-Control: X-record-mode=0\n");
      else
      if (CgiLib__EnvironmentStdoutType == CGILIB_ENVIRONMENT_STDOUT_BINARY)
         charcnt = fprintf (ResponseStdout,
                            "Script-Control: X-stream-mode=1\n");
      else
      if (CgiLib__EnvironmentStdoutType == CGILIB_ENVIRONMENT_STDOUT_RECORD)
         charcnt = fprintf (ResponseStdout,
                            "Script-Control: X-record-mode=1\n");

      if (CgiLib__ResponseBufferRecords > 0)
         charcnt = fprintf (ResponseStdout,
                            "Script-Control: X-buffer-records=1\n");
      else
      if (CgiLib__ResponseBufferRecords < 0)
         charcnt = fprintf (ResponseStdout,
                            "Script-Control: X-buffer-records=0\n");
   }

   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  *FormatStringPtr,
         *HttpHostPtr,
         *HostPortPtr,
         *RequestTimeGmtPtr,
         *SchemePtr,
         *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 (*(USHORTPTR)FormatStringPtr == '%!' ||
       CgiLib__Environment == CGILIB_ENVIRONMENT_PURVEYOR)
   {
      if (*(USHORTPTR)FormatStringPtr == '%!') FormatStringPtr += 2;

      if (CgiLib__Environment == CGILIB_ENVIRONMENT_PURVEYOR)
      {
         /* Purveyor requires a full URL (scheme://server:port/path) */
         SchemePtr = CgiLibVarNull ("SECURITY_STATUS");
         if (!SchemePtr)
            SchemePtr = "http://";
         else
         if (strstr (SchemePtr, "SSL"))
            SchemePtr = "https://";
         else
            SchemePtr = "http://";
      }
      else
         SchemePtr = "//";

      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 = SchemePtr = "";

   CgiLib__ResponseStatusCode = 302;

   if (!CgiLib__ResponseCgi &&
       CgiLib__Environment == CGILIB_ENVIRONMENT_OSU)
   {
      /* full response header (only for OSU these days) */
      ServerSoftwarePtr = CgiLib__GetVar ("WWW_SERVER_SOFTWARE", "");
      if (!ServerSoftwarePtr[0])
         ServerSoftwarePtr = CgiLib__ResponseSoftwareIdPtr;
      RequestTimeGmtPtr = CgiLibVarNull ("REQUEST_TIME_GMT");
      CgiLibHttpProtocolVersion ();

      charcnt = fprintf (stdout,
"%s 302 %s\n\
Server: %s\n\
%s%s%s\
Content-Type: text/html\n\
Location: %s%s",
         CgiLib__HttpProtocolVersionPtr,
         CgiLibHttpStatusCodeText(302), ServerSoftwarePtr,
         RequestTimeGmtPtr == NULL ? "" : "Date: ",
         RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr,
         RequestTimeGmtPtr == NULL ? "" : "\n",
         SchemePtr, HostPortPtr);
   }
   else
   {
      /* vanilla CGI environment/response */
      charcnt = fprintf (stdout,
"Status: 302 %s\n\
%s\
Location: %s%s",
         CgiLibHttpStatusCodeText(302),
         CgiLib__Environment == CGILIB_ENVIRONMENT_OSU ?
            "Content-Type: text/html\n" : "",
         SchemePtr, 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)
   {
      CgiLib__ResponseStatusCode = CgiLib__ResponseErrorStatus;
      CgiLibResponseHeader (CgiLib__ResponseErrorStatus, "text/html");
      charcnt += fprintf (stdout,
"<html>\n\
<head>\n\
%s\
<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__XUACompatiblePtr ? CgiLib__XUACompatiblePtr : "",
         CgiLib__ResponseSoftwareIdPtr, FileName, SrcLineNumber,
         CgiLibEnvironmentName(),
         CgiLib__ResponseErrorTitlePtr, CgiLib__ResponseErrorStatus,
         CgiLibHttpStatusCodeText(CgiLib__ResponseErrorStatus),
         CgiLib__ResponseBodyPtr ? " " : "", CgiLib__ResponseBodyPtr);
   }
   else
   /* this is a kludge originally for soyMAIL */
   if (StatusValue != -1)
   {
      charcnt += fprintf (stdout,
"\n<p><hr size=\"3\" width=\"100%%\" noshade>\n\
<!-- SoftwareID: %s Module: %s Line: %d -->\n",
          CgiLib__ResponseSoftwareIdPtr, FileName, SrcLineNumber);
   }

   charcnt += fprintf (stdout,
"<font size=\"+1\"><b>%s %d</b> &nbsp;-&nbsp; %s</font>\n",
      CgiLib__ResponseErrorTitlePtr,
      CgiLib__ResponseErrorStatus,
      CgiLib__ResponseErrorMessagePtr);

   if (StatusValue && StatusValue != -1 /* see kludge above */)
   {
#     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__ResponseErrorInfoPtr != NULL &&
       CgiLib__ResponseErrorInfoPtr[0])
      charcnt += fprintf (stdout, "<p>%s", CgiLib__ResponseErrorInfoPtr);

   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;

   CgiLib__ResponseStatusCode = CgiLib__ResponseSuccessStatus;
   CgiLibResponseHeader (CgiLib__ResponseSuccessStatus, "text/html");
   charcnt += fprintf (stdout,
"<html>\n\
<head>\n\
%s\
<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__XUACompatiblePtr ? CgiLib__XUACompatiblePtr : "",
      CgiLib__ResponseSoftwareIdPtr,
      CgiLibEnvironmentName(),
      CgiLib__ResponseSuccessTitlePtr, CgiLib__ResponseSuccessStatus,
      CgiLibHttpStatusCodeText(CgiLib__ResponseSuccessStatus),
      CgiLib__ResponseBodyPtr ? " " : "", CgiLib__ResponseBodyPtr,
      CgiLib__ResponseSuccessTitlePtr,
      CgiLib__ResponseSuccessStatus,
      CgiLib__ResponseSuccessMsgPtr);

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

/*****************************************************************************/
/*
Return the HTTP status code (e.g. 200, 302) set by response generation.
*/

int CgiLibResponseGetStatus ()

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

   if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseGetStatus()\n");

   return (CgiLib__ResponseStatusCode);
}

/*****************************************************************************/
/*
Tell the WASD server via the CGI response that it should interpret the response
body as being discrete records requiring carriage-control.  Return the previous
value.
*/

int CgiLibResponseSetRecordMode (int enable)

{
   int  prevalue;

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

   if (CgiLib__Debug)
      fprintf (stdout, "CgiLibResponseSetRecordMode() %d\n", enable);

   prevalue = CgiLib__ResponseRecordMode;

   if (enable)
      CgiLib__ResponseRecordMode = 1;
   else
      CgiLib__ResponseRecordMode = -1;
   CgiLib__ResponseStreamMode = 0;

   return (prevalue > 0);
}

/*****************************************************************************/
/*
Tell the WASD server via the CGI response that it should consider the response
body as already containing all required carriage-control.  Return the previous
value.
*/

int CgiLibResponseSetStreamMode (int enable)

{
   int  prevalue;

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

   if (CgiLib__Debug)
      fprintf (stdout, "CgiLibResponseSetStreamMode() %d\n", enable);

   prevalue = CgiLib__ResponseStreamMode;

   if (enable)
      CgiLib__ResponseStreamMode = 1;
   else
      CgiLib__ResponseStreamMode = -1;
   CgiLib__ResponseRecordMode = 0;

   return (prevalue > 0);
}

/*****************************************************************************/
/*
Tell the WASD server via the CGI response to buffer discrete records, flushing
only when full.  Return the previous value.
*/

int CgiLibResponseSetBufferRecords (int enable)

{
   int  prevalue;

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

   if (CgiLib__Debug)
      fprintf (stdout, "CgiLibResponseSetBufferRecords() %d\n", enable);

   prevalue = CgiLib__ResponseBufferRecords;

   if (enable)
      CgiLib__ResponseBufferRecords = 1;
   else
      CgiLib__ResponseBufferRecords = -1;

   return (prevalue > 0);
}

/*****************************************************************************/
/*
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, "CgiLibHttpStatusCodeText() %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 ("Non-authoritative"); /* (HTTP/1.1) */
      case 204 : return ("No Content");
      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 ("Found (Moved Temporarily)");
      case 303 : return ("See Other"); /* (HTTP/1.1) */
      case 304 : return ("Not Modified");
      case 305 : return ("Use Proxy"); /* (HTTP/1.1) */
      case 306 : return ("Temporary Redirect"); /* (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 ("Method 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 416 : return ("Requested Range Not Satisfiable"); /* (HTTP/1.1) */
      case 417 : return ("Expectation Failed"); /* (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!");
   }
}

/*****************************************************************************/
/*
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() |%s|\n", cptr);

   sptr = CgiLib__ResponseCharsetPtr;
   if (!(CgiLib__ResponseCharsetPtr = cptr))
      CgiLib__ResponseCharsetPtr = CgiLib__ResponseDefCharsetPtr;
   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;
   if (!(CgiLib__ResponseDefCharsetPtr = cptr))
      CgiLib__ResponseDefCharsetPtr = CGILIB_RESPONSE_DEFAULT_CHARSET;
   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);
}

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

                        /**************************/
                        /* URL/HTML ENCODE/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)

{
   unsigned char  ch,
                  pch = 0;
   char  *cptr, *sptr;

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

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

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

            *sptr++ = ' ';
            cptr++;
            pch = 0;
            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++;

            /* if URL-encoded 8 bit UTF-8 (e.g. %C3%A9) */
            if (pch == 0xc2 && ch >= 0x80 && ch <= 0xbf)
            {
               *(sptr-1) = ch;
               pch = 0;
            }
            else
            if (pch == 0xc3 && ch >= 0x80 && ch <= 0xbf)
            {
               *(sptr-1) = ch + 0x40;
               pch = 0;
            }
            else
               *sptr++ = pch = ch;

            continue;

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

            cptr++;
            pch = 0;
            continue;

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

      }
   }
   *sptr = '\0';

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

/****************************************************************************/
/*
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 (CgiLib__VeeMemInUse)
            EncodedPtr = CgiLibVeeMemRealloc (EncodedPtr,
                                              EncodedSize+CGILIB_ENCODED_CHUNK);
         else
            EncodedPtr = realloc (EncodedPtr,
                                  EncodedSize+CGILIB_ENCODED_CHUNK);
         if (!EncodedPtr)
            if (CgiLib__VeeMemContinue)
               return ((int)NULL);
            else
               exit (vaxc$errno);
         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;
   unsigned 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 (CgiLib__VeeMemInUse)
            EncodedPtr = CgiLibVeeMemRealloc (EncodedPtr,
                                              EncodedSize+CGILIB_ENCODED_CHUNK);
         else
            EncodedPtr = realloc (EncodedPtr,
                                  EncodedSize+CGILIB_ENCODED_CHUNK);
         if (!EncodedPtr)
            if (CgiLib__VeeMemContinue)
               return ((int)NULL);
            else
               exit (vaxc$errno);
         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);
}

/****************************************************************************/
/*
Set the maximum (displayed) URL length.
Allowed minumum 16 characters, maximum 255 characters, 0 disables shortening.
*/

int CgiLibUrlMaxShorten (int MaxChars)

{
   if (CgiLib__Debug)
      fprintf (stdout, "CgiLibUrlMaxShorten() %d\n", MaxChars);

   if (MaxChars < 16)
      MaxChars = 0;
   else
   if (MaxChars > 255)
      MaxChars = 255;

   return (CgiLib__UrlMaxShorten = MaxChars);
}

/****************************************************************************/
/*
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, len, tcnt,
        AtCount,
        CurrentLength,
        EscapedSize,
        PeriodCount,
        SchemeLength,
        SplitAnchor,
        SplitCount,
        UrlEscapedLength,
        UrlOriginalLength,
        UrlSplitEscapedLength,
        WasMailAddress,
        WasMailtoLink;
   char  *aptr, *cptr, *sptr, *tptr, *zptr,
         *EscapedPtr,
         *UrlAnchorPtr,
         *UrlEscapedPtr,
         *UrlOriginalPtr,
         *UrlSplitEscapedPtr;
   char  UrlOriginal [1024+128];

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

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

   if (!PlainString)
   {
      if (EscapedString == NULL) return ((int)NULL);
      if (SizeOfEscapedString >= 1) *EscapedString = '\0';
      return (0);
   }

   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)
      {
         UrlOriginalLength = WasMailAddress = WasMailtoLink = SchemeLength = 0;
         UrlOriginal[0] = '\0';

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

            if (!memcmp (cptr, "mailto:", 7))
            {
               WasMailAddress = WasMailtoLink = 1;
               if (CgiLib__soyMAILhack)
               {
                  /* don't need the scheme */
                  cptr += SchemeLength;
                  ccnt -= SchemeLength;
               }
            }

            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--;
                  ccnt++;
               }
            }
            *tptr = '\0';
            UrlOriginalLength = tptr - UrlOriginal;

            if (!UrlOriginalLength ||
                UrlOriginalLength == SchemeLength)
            {
               /* just and empty scheme, replace the text */
               cptr -= SchemeLength;
               ccnt += SchemeLength;
               UrlOriginalLength = WasMailAddress = WasMailtoLink = 0;
               UrlOriginal[0] = '\0';
            }
         }
         else
         if ((AnchorUrls & CGILIB_ANCHOR_MASK1 & 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;
               if (!CgiLib__soyMAILhack)
               {
                  /* 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';
               UrlOriginalLength = tptr - UrlOriginal;
            }
         }

         if (UrlOriginalLength)
         {
            /***************************/
            /* make URL into an anchor */
            /***************************/

            if ((len = UrlOriginalLength) > sizeof(UrlOriginal)-1)
               exit (SS$_BUGCHECK);
            UrlOriginalPtr = UrlOriginal;

            if (len > 2)
            {
               if ((!WasMailAddress || (WasMailAddress &&
                                        CgiLib__soyMAILhack)) &&
                   AnchorUrls != CGILIB_ANCHOR_ALL &&
                   AnchorUrls & CGILIB_ANCHOR_MASK2 & CGILIB_ANCHOR_SPLIT)
               {
                  SplitAnchor = 1;
                  SplitCount = 2;
               }
               else
               {
                  SplitAnchor = 0;
                  SplitCount = 1;
               }
            }

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

            while (SplitCount--)
            {
               if (CgiLib__UrlMaxShorten)
               {
                  /* maximum of the first 64 characters of the URL */
                  ulong  max = CgiLib__UrlMaxShorten,
                         char4 = 0;
                  for (aptr = UrlOriginalPtr; *aptr && --max; aptr++);
                  if (*aptr)
                  {
                     char4 = *(ULONGPTR)aptr;
                     *(ULONGPTR)aptr = '...\0';
                  }
                  UrlSplitEscapedPtr = (char*)CgiLibHtmlEscape (UrlOriginalPtr,
                                                                len, NULL, 0);
                  if (char4) *(ULONGPTR)aptr = char4;
               }
               else
                  UrlSplitEscapedPtr = (char*)CgiLibHtmlEscape (UrlOriginalPtr,
                                                                len, NULL, 0);
               UrlSplitEscapedLength = strlen(UrlSplitEscapedPtr);

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

               if (CgiLib__VeeMemInUse)
                  tptr = UrlAnchorPtr =
                     CgiLibVeeMemCalloc (UrlEscapedLength +
                                         UrlSplitEscapedLength + 96);
               else
                  tptr = UrlAnchorPtr =
                     calloc (1, UrlEscapedLength +
                                UrlSplitEscapedLength + 96);

               if (tptr == NULL)
                  if (CgiLib__VeeMemContinue)
                     return ((int)NULL);
                  else
                     exit (vaxc$errno);

               strcpy (tptr, "<a href=\"");
               tptr += 9;

               if (WasMailAddress && CgiLib__soyMAILhack)
               {
                  strcpy (tptr, CgiLib__soyMAILhack); 
                  while (*tptr) tptr++;
               }

               strcpy (tptr, UrlEscapedPtr);
               tptr += UrlEscapedLength;

               if (!SplitAnchor || SplitCount)
               {
                  strcpy (tptr, "\" target=\"_top\">");
                  tptr += 16;
               }
               else
               {
                  strcpy (tptr, "\" target=\"_blank\">");
                  tptr += 18;
               }

               if (WasMailAddress && !WasMailtoLink && !CgiLib__soyMAILhack)
               {
                  /* don't need the introduced "mailto:" in the text */
                  strcpy (tptr, UrlSplitEscapedPtr+7);
                  tptr += UrlSplitEscapedLength-7;
               }
               else
               {
                  if (SplitAnchor && SplitCount)
                     tptr += sprintf (tptr, "%*.*s",
                                      UrlSplitEscapedLength / 2,
                                      UrlSplitEscapedLength / 2,
                                      UrlSplitEscapedPtr);
                  else
                     tptr += sprintf (tptr, "%s",
                                      UrlSplitEscapedPtr +
                                      UrlSplitEscapedLength / 2);
               }

               strcpy (tptr, "</a>");

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

               for (aptr = UrlAnchorPtr; *aptr; *sptr++ = *aptr++)
               {
                  if (EscapedString == NULL && sptr >= zptr)
                  {
                     /* out of storage (or first use) get memory */
                     CurrentLength = sptr - EscapedPtr;
                     if (CgiLib__VeeMemInUse)
                        EscapedPtr = CgiLibVeeMemRealloc (EscapedPtr,
                                                          EscapedSize +
                                                          CGILIB_ESCAPED_CHUNK);
                     else
                        EscapedPtr = realloc (EscapedPtr,
                                              EscapedSize +
                                              CGILIB_ESCAPED_CHUNK);
                     if (!EscapedPtr)
                        if (CgiLib__VeeMemContinue)
                           return ((int)NULL);
                        else
                           exit (vaxc$errno);
                     EscapedSize += CGILIB_ESCAPED_CHUNK;
                     /* recalculate pointers */
                     sptr = EscapedPtr + CurrentLength;
                     zptr = EscapedPtr + EscapedSize - 8;
                  }
                  else
                  if (sptr >= zptr)
                     break;            
               }

               if (CgiLib__VeeMemInUse)
                  CgiLibVeeMemFree (UrlSplitEscapedPtr);
               else
                  free (UrlSplitEscapedPtr);
            }

            if (CgiLib__VeeMemInUse)
               CgiLibVeeMemFree (UrlEscapedPtr);
            else
               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 (CgiLib__VeeMemInUse)
            EscapedPtr = CgiLibVeeMemRealloc (EscapedPtr,
                                              EscapedSize+CGILIB_ESCAPED_CHUNK);
         else
            EscapedPtr = realloc (EscapedPtr,
                                  EscapedSize+CGILIB_ESCAPED_CHUNK);
         if (!EscapedPtr)
            if (CgiLib__VeeMemContinue)
               return ((int)NULL);
            else
               exit (vaxc$errno);
         EscapedSize += CGILIB_ESCAPED_CHUNK;
         /* recalculate pointers */
         sptr = EscapedPtr + CurrentLength;
         zptr = EscapedPtr + EscapedSize - 8;
      }
      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;
         case '\'' :
            memcpy (sptr, "&#39;", 5); sptr += 5; 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)

{
   struct HtmlEntityStruct
   {
      char  *ent;
      int  len,
           val;
   } HtmlEntity [] =
   {
      { "amp;",    4, '&' },
      { "lt;",     3, '<' },
      { "gt;",     3, '>' },
      { "quot;",   5, '\"' },
      { "apos;",   5, '\'' },
      { "nbsp;",   5, 160 },
      { "iexcl;",  6, 161 },
      { "cent;",   5, 162 },
      { "pound;",  6, 163 },
      { "curren;", 7, 164 },
      { "yen;",    4, 165 },
      { "brvbar;", 7, 166 },
      { "sect;",   5, 167 },
      { "uml;",    4, 168 },
      { "copy;",   5, 169 },
      { "ordf;",   5, 170 },
      { "laquo;",  6, 171 },
      { "not;",    4, 172 },
      { "shy;",    4, 173 },
      { "reg;",    4, 174 },
      { "macr;",   5, 175 },
      { "deg;",    4, 176 },
      { "plusmn;", 7, 177 },
      { "sup2;",   5, 178 },
      { "sup3;",   5, 179 },
      { "acute;",  6, 180 },
      { "micro;",  6, 181 },
      { "para;",   5, 182 },
      { "middot;", 7, 183 },
      { "cedil;",  6, 184 },
      { "sup1;",   5, 185 },
      { "ordm;",   5, 186 },
      { "raquo;",  6, 187 },
      { "frac14;", 7, 188 },
      { "frac12;", 7, 189 },
      { "frac34;", 7, 190 },
      { "iquest;", 7, 191 },
      { "Agrave;", 7, 192 },
      { "Aacute;", 7, 193 },
      { "Acirc;",  6, 194 },
      { "Atilde;", 7, 195 },
      { "Auml;",   5, 196 },
      { "Aring;",  6, 197 },
      { "AElig;",  6, 198 },
      { "Ccedil;", 7, 199 },
      { "Egrave;", 7, 200 },
      { "Eacute;", 7, 201 },
      { "Ecirc;",  6, 202 },
      { "Euml;",   5, 203 },
      { "Igrave;", 7, 204 },
      { "Iacute;", 7, 205 },
      { "Icirc;",  6, 206 },
      { "Iuml;",   5, 207 },
      { "ETH;",    4, 208 },
      { "Ntilde;", 7, 209 },
      { "Ograve;", 7, 210 },
      { "Oacute;", 7, 211 },
      { "Ocirc;",  6, 212 },
      { "Otilde;", 7, 213 },
      { "Ouml;",   5, 214 },
      { "times;",  6, 215 },
      { "Oslash;", 7, 216 },
      { "Ugrave;", 7, 217 },
      { "Uacute;", 7, 218 },
      { "Ucirc;",  6, 219 },
      { "Uuml;",   5, 220 },
      { "Yacute;", 7, 221 },
      { "THORN;",  6, 222 },
      { "szlig;",  6, 223 },
      { "agrave;", 7, 224 },
      { "aacute;", 7, 225 },
      { "acirc;",  6, 226 },
      { "atilde;", 7, 227 },
      { "auml;",   5, 228 },
      { "aring;",  6, 229 },
      { "aelig;",  6, 230 },
      { "ccedil;", 7, 231 },
      { "egrave;", 7, 232 },
      { "eacute;", 7, 233 },
      { "ecirc;",  6, 234 },
      { "euml;",   5, 235 },
      { "igrave;", 7, 236 },
      { "iacute;", 7, 237 },
      { "icirc;",  6, 238 },
      { "iuml;",   5, 239 },
      { "eth;",    4, 240 },
      { "ntilde;", 7, 241 },
      { "ograve;", 7, 242 },
      { "oacute;", 7, 243 },
      { "ocirc;",  6, 244 },
      { "otilde;", 7, 245 },
      { "ouml;",   5, 246 },
      { "divide;", 7, 247 },
      { "oslash;", 7, 248 },
      { "ugrave;", 7, 249 },
      { "uacute;", 7, 250 },
      { "ucirc;",  6, 251 },
      { "uuml;",   5, 252 },
      { "yacute;", 7, 253 },
      { "thorn;",  6, 254 },
      { "yuml;",   5, 255 },
      { NULL, 0, 0 }
   };

   int  idx;
   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;
      }
      for (idx = 0; HtmlEntity[idx].ent; idx++)
      {
         if (!memcmp (cptr, HtmlEntity[idx].ent, HtmlEntity[idx].len))
         {
            *sptr++ = HtmlEntity[idx].val;
            cptr += HtmlEntity[idx].len;
            break;
         }
      }
      if (HtmlEntity[idx].ent) continue;
      *sptr++ = '&';
      *sptr++ = *cptr++;
   }
   *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 */
      if (CgiLib__VeeMemInUse)
         CgiLibVeeMemFree (BufferPtr);
      else
         free (BufferPtr);
      BufferPtr = NULL;
      BufferSize = 0;
      *Context = 0;
      return (NULL);
   }
   while (*sptr && *sptr != '&') sptr++;

   if (BufferSize < sptr - cptr + 1)
   {
      BufferSize = sptr - cptr + 1;
      if (CgiLib__VeeMemInUse)
         BufferPtr = CgiLibVeeMemRealloc (BufferPtr, BufferSize);
      else
         BufferPtr = realloc (BufferPtr, BufferSize);
      if (!BufferPtr)
         if (CgiLib__VeeMemContinue)
            return (NULL);
         else
            exit (vaxc$errno);
   }

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

/****************************************************************************/
/*
This is really just a convenience function and has little to do with CGI
scripting.  VMS $FAO-formatted print statement.  Output is either to the
specified stream, or if stream pointer NULL return a pointer to a dynamic
buffer buffer containing the formatted output (requires caller to dispose of it
as necessary).  Will accomodate up to 64 $FAO directives and resultant strings
to 32kB in size.
*/

char* CgiLibFaoPrint
(
FILE *StreamPtr,
char *FormatString,
...
)
{
/* size of allocated buffer */
#define CGILIB_FAO_BUFFER_ARG    64
#define CGILIB_FAO_BUFFER_MIN   256
#define CGILIB_FAO_BUFFER_MAX 32768

   static $DESCRIPTOR (BufferDsc, "");
   static $DESCRIPTOR (FormatFaoDsc, "");

   int  argcnt, blen, status;
   unsigned short  slen;
   unsigned long  *vecptr;
   unsigned long  FaoVector [CGILIB_FAO_BUFFER_ARG];
   char  *bptr;
   va_list  argptr;

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

   va_count (argcnt);

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

   if (argcnt > CGILIB_FAO_BUFFER_ARG+2) return ("[CgiLibFaoPrint():argovf]");

   vecptr = FaoVector;
   va_start (argptr, FormatString);
   for (argcnt -= 2; argcnt; argcnt--)
      *vecptr++ = va_arg (argptr, unsigned long);
   va_end (argptr);

   FormatFaoDsc.dsc$a_pointer = FormatString;
   FormatFaoDsc.dsc$w_length = strlen(FormatString);

   /* start modestly and work up to it if necessary */
   for (blen = CGILIB_FAO_BUFFER_MIN; blen < CGILIB_FAO_BUFFER_MAX; blen *= 2)
   {
      if (CgiLib__VeeMemInUse)
         bptr = CgiLibVeeMemCalloc (blen);
      else
         bptr = calloc (1, blen);
      if (bptr == NULL)
         if (CgiLib__VeeMemContinue)
            return (NULL);
         else
            exit (vaxc$errno);

      BufferDsc.dsc$a_pointer = bptr;
      BufferDsc.dsc$w_length = blen;

      status = sys$faol (&FormatFaoDsc, &slen, &BufferDsc, &FaoVector);
      if (status == SS$_BUFFEROVF)
      {
         if (CgiLib__VeeMemInUse)
            CgiLibVeeMemFree (bptr);
         else
            free (bptr);
         continue;
      }
      if (!(status & 1))
         sprintf (bptr, "[CgiLibFaoPrint():%%X%08.08X]\n", status);
      break;
   }
   if (blen >= CGILIB_FAO_BUFFER_MAX)
   {
      if (CgiLib__VeeMemInUse)
         bptr = CgiLibVeeMemCalloc (64);
      else
         bptr = calloc (1, 64);
      if (bptr == NULL)
         if (CgiLib__VeeMemContinue)
            return (NULL);
         else
            exit (vaxc$errno);
      sprintf (bptr, "[CgiLibFaoPrint():%%X%08.08X]\n", status);
   }

   if (CgiLib__Debug) fprintf (stdout, "|%s|\n", bptr);
   if (StreamPtr != NULL)
   {
      fwrite (bptr, slen, 1, StreamPtr);
      if (CgiLib__VeeMemInUse)
         CgiLibVeeMemFree (bptr);
      else
         free (bptr);
      bptr = NULL;
   }
   return (bptr);

#undef CGILIB_FAO_BUFFER_ARG
#undef CGILIB_FAO_BUFFER_MIN
#undef CGILIB_FAO_BUFFER_MAX
}

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

                           /********************/
                           /* CGIPLUS SPECIFIC */
                           /********************/

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

   /* reset these ready for any subsequent request */
   CgiLib__ResponseCount = CgiLib__ResponseStatusCode = 0;
}

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

/*****************************************************************************/
/*
Provide a callout to WASD server.
*/

char* CgiLibCgiPlusCallout (char *fmt, ...)

{
   static char  CalloutResponse [256];

   int  retval;
   char  *cptr;
   char  buf [256];
   va_list  ap;

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

   if (CgiLib__Debug) fprintf (stdout, "CgiLibCgiPlusCallout()\n");

   if (CgiLib__Environment != CGILIB_ENVIRONMENT_CGIPLUS) return (NULL);

   CgiLibCgiPlusESC();

   va_start (ap, fmt);
   retval = vsnprintf (buf, sizeof(buf), fmt, ap);
   va_end (ap);

   if (retval >= 0) fputs (buf, stdout);

   CgiLibCgiPlusEOT();

   if (buf[0] == '!' || buf[0] == '#') return (NULL);

   memset (CalloutResponse, 0, sizeof(CalloutResponse));
   CgiLibCgiPlusInGets (CalloutResponse, sizeof(CalloutResponse));
   *(strchr (CalloutResponse, '\n')) = '\0';

   return (CalloutResponse);
}

/*****************************************************************************/
/*
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);
   /* absorb intermediate empty lines that seem to be present */
   if (String[0] == '\n' && !String[1])
      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;
}

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

                             /*****************/
                             /* CGI VARIABLES */
                             /*****************/

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

   if (CgiLib__VeeMemInUse)
      name = CgiLibVeeMemCalloc (NameDsc->dsc$w_length+1);
   else
      name = calloc (1, NameDsc->dsc$w_length+1);
   if (!name)
      if (CgiLib__VeeMemContinue)
         return (0);
      else
         exit (vaxc$errno);

   for (idx = 0; idx < NameDsc->dsc$w_length; idx++)
      name[idx] = (NameDsc->dsc$a_pointer)[idx];
   name[idx] = '\0';
   value = CgiLibVarNull (name);
   if (CgiLib__VeeMemInUse)
      CgiLibVeeMemFree (name);
   else
      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;
      if (ValueDsc->dsc$w_length - idx > 0)
         memset (ValueDsc->dsc$a_pointer+idx, ' ', ValueDsc->dsc$w_length-idx);
      return (1);
   }
   else
   {
      if (ValueDsc->dsc$w_length)
         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* CgiLibVeeMemVar (char *VarName)
{
   char  *cptr, *sptr;

   if (!CgiLib__VeeMemZoneId) return (NULL);

   cptr = CgiLib__GetVar (VarName, CgiLib__VarNonePtr);

   sptr = CgiLibVeeMemCalloc (strlen(cptr));
   if (!sptr)
      if (CgiLib__VeeMemContinue)
         return (NULL);
      else
         exit (vaxc$errno);
   strcpy (sptr, cptr);

   if (sptr[0] && CgiLib__BodyIsMultipartFormData &&
       (!strncmp (VarName, "form_", 5) || !strncmp (VarName, "FORM_", 5)))
      CgiLibHtmlDeEntify (sptr);

   return (sptr);
}

char* CgiLibVeeMemVarNull (char *VarName)
{
   char  *cptr, *sptr;

   if (!CgiLib__VeeMemZoneId) return (NULL);

   if (!(cptr = CgiLib__GetVar (VarName, NULL))) return (cptr);
   sptr = CgiLibVeeMemCalloc (strlen(cptr));
   if (!sptr)
      if (CgiLib__VeeMemContinue)
         return (NULL);
      else
         exit (vaxc$errno);
   strcpy (sptr, cptr);

   if (sptr[0] && CgiLib__BodyIsMultipartFormData &&
       (!strncmp (VarName, "form_", 5) || !strncmp (VarName, "FORM_", 5)))
      CgiLibHtmlDeEntify (sptr);

   return (sptr);
}

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, *sptr;
   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\nScript-Control: X-content-encoding-gzip=0\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])
      {
         /* reinitialize */
         CgiLib__VarList (NULL, NULL, NULL);

         if ((cptr = getenv("WWW_GATEWAY_SYMBOLS")) ||
             (cptr = getenv("GATEWAY_SYMBOLS")))
         {
            while (*cptr)
            {
               for (sptr = cptr; *sptr && *sptr != ','; sptr++);
               if (!*sptr) break;
               *sptr = '\0';
               if (CgiLib__Debug) fprintf (stdout, "|%s|\n", cptr);
               CgiLib__VarList (cptr, getenv (cptr), NULL);
               *sptr++ = ',';
               cptr = sptr;
            }
         }
         return (NULL);
      }

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

      /* a POSTed body can be turned into _FORM_ variables */
      if (VarName[0] == '*')
         return (CgiLib__VarList (VarName, NULL, NonePtr));

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

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

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

         if ((cptr = getenv(VarName)) == NULL)
         {
            /* a POSTed body can be turned into _FORM_ variables */
            return (CgiLib__VarList (VarName, NULL, 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 (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));
         }

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

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

   if (VarName == NULL || !VarName[0])
   {
      /* end of request or waiting for next, 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 (*(USHORTPTR)cptr == '!\0' ||
             *(USHORTPTR)cptr == '!\n' ||
             (*(USHORTPTR)cptr == '!!' && isdigit(*(cptr+2)))) break;
      }

      if (*(USHORTPTR)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 = *(USHORTPTR)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));
}

/*****************************************************************************/
/*
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  LengthOffset,
        VarNameLength,
        VarNameLengthOffset,
        VarNameOffset;
   unsigned int  Length;
   char  *cptr, *sptr, *zptr,
         *LengthPtr;

   /*********/
   /* 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)
   {
      /******************************/
      /* manipulate variable values */
      /******************************/

      if (VarName[0] == '!' && !strcmp (VarName, "!FORM_"))
      {
         /****************/
         /* special case */
         /****************/

         /*
            Remove from access the current FORM_ variables, 'hiding' them
            by changing the variable name from 'FORM_' to '!ORM_'. This leaves
            the current values intact and any existing pointers still valid.
            A kludge to allow soyMAIL v1.3 to re-process POSTed content.
         */
         cptr = ListPtr;
         for (;;)
         {
            if (!(Length = *((int*)(sptr = cptr)))) return (NULL);
            cptr = sptr + Length;
            sptr += sizeof(int) + sizeof(int);
            if (CGILIB_VARLIST_DEBUG) fprintf (stdout, "|%s|\n", sptr);
            if (!strncmp (sptr, "FORM_", 5)) *sptr = '!';
         }
         return (NULL);
      }

      /******************/
      /* add a variable */
      /******************/

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

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

      /* CGIplus, stream has "name=value\n\0" */
      cptr = VarName;
      while (*cptr && *(USHORTPTR)cptr != '\n\0')
      {
         while (*cptr && *(USHORTPTR)cptr != '\n\0' && sptr < zptr)
         {
            if (VarNameLengthOffset && *cptr == '=')
            {
               /* store the length of the CGIplus variable name */
               *((int*)(ListPtr+VarNameLengthOffset)) =
                  sptr - ListPtr - VarNameOffset;
               VarNameLengthOffset = 0;
            }
            *sptr++ = *cptr++;
         }
         if (VarNameLengthOffset && !*cptr)
         {
            /* store the length of the CGI variable name */
            *((int*)(ListPtr+VarNameLengthOffset)) =
               sptr - ListPtr - VarNameOffset;
            VarNameLengthOffset = 0;
            *sptr++ = '=';
            cptr = VarValue;
         }
         if (sptr >= zptr)
         {
            /* out of storage, get more memory */
            Length = sptr - ListPtr;
            ListPtr = realloc (ListPtr, ListSize+CGILIB_VARLIST_CHUNK+16);
            if (!ListPtr) exit (SS$_INSFMEM);
            ListSize += CGILIB_VARLIST_CHUNK;
            /* recalculate pointers */
            sptr = ListPtr + Length;
            zptr = ListPtr + ListSize;
         }
      }
      /* terminate the "name=value" */
      *sptr++ = '\0';

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

      /* adjust the value of the current variable storage space used */
      ListNextOffset = sptr - ListPtr;

      /* 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 (VarName[0] == '+')
{
fprintf(stdout,"<!-- %u %d %d\n", ListPtr, ListSize, ListNextOffset);
CgiLibVeeMemShow(NULL);
fprintf(stdout,"-->\n");
}

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

/*****************************************************************************/
/*
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++;
      if (CgiLib__VeeMemInUse)
         aptr = CgiLibVeeMemCalloc (16);
      else
         aptr = calloc (1, 16);
      if (aptr == NULL)
         if (CgiLib__VeeMemContinue)
            return (NonePtr);
         else
            exit (vaxc$errno);
      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)
      {
         if (CgiLib__VeeMemInUse)
            aptr = CgiLibVeeMemCalloc (cptr - sptr + 1);
         else
            aptr = calloc (1, cptr - sptr + 1);
         if (aptr == NULL)
            if (CgiLib__VeeMemContinue)
               return (NonePtr);
            else
               exit (vaxc$errno);
         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++;
      if (CgiLib__VeeMemInUse)
         aptr = CgiLibVeeMemCalloc (cptr - sptr + 1);
      else
         aptr = calloc (1, cptr - sptr + 1);
      if (aptr == NULL)
         if (CgiLib__VeeMemContinue)
            return (NonePtr);
         else
            exit (vaxc$errno);
      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. Apache, OSU) 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  scnt, Length;
   char  *aptr, *cptr, *sptr, *zptr;
   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 */
      scnt = 0;
      for (aptr = cptr; *aptr; aptr++) if (*aptr == '/') scnt++;
      zptr = (sptr = PathTranslated) + sizeof(PathTranslated)-1;
      cptr++;
      if (scnt == 1)
      {
         /* just a filename, e.g. "/file.txt" */
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      }
      else
      if (scnt == 2)
      {
         /* logical plus file name, e.g. "/this/file.txt" */
         while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
         if (sptr < zptr) *sptr++ = ':';
         if (*cptr) cptr++;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      }
      else
      {
         /* full file spec, e.g. "/device/dir1/file.txt" */
         while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
         if (*cptr) cptr++;
         if (sptr < zptr) *sptr++ = ':';
         if (sptr < zptr) *sptr++ = '[';
         while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
         if (*cptr) cptr++;
         for (scnt -= 3; scnt; scnt--)
         {
            if (sptr < zptr) *sptr++ = '.';
            while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
            if (*cptr) cptr++;
         }
         if (sptr < zptr) *sptr++ = ']';
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      }
      *sptr = '\0';
      if (CgiLib__VeeMemInUse)
         aptr = CgiLibVeeMemCalloc (Length = (sptr - PathTranslated) + 1);
      else
         aptr = calloc (1, Length = (sptr - PathTranslated) + 1);
      if (aptr == NULL)
         if (CgiLib__VeeMemContinue)
            return (NULL);
         else
            exit (vaxc$errno);
      strcpy (aptr, PathTranslated);
   }

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

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

                             /****************/
                             /* OSU SPECIFIC */
                             /****************/

/*****************************************************************************/
/*
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 [512],
         PathTranslated [512],
         Scratch [256],
         ScriptName [256],
         SymbolName [256];
   struct hostent  *HostEntryPtr;

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

   strcpy (sptr = ScriptName, CgiLib__OsuQueryNetLink ("<DNETPATH>", 1));
   cptr = CgiLib__OsuQueryNetLink ("<DNETRQURL>", 1);

   CgiLib__VarList ("WWW_REQUEST_URI", cptr, NULL);

   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 && *(USHORTPTR)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 (*(ULONGPTR)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);

   /* just derive the absent server address from the host address */
   if (HostEntryPtr = gethostbyname (sptr))
   {
      memcpy (&IpAddress, HostEntryPtr->h_addr, 4);
      sprintf (Scratch, "%d.%d.%d.%d",
               *(unsigned char*)&IpAddress,
               *((unsigned char*)(&IpAddress)+1),
               *((unsigned char*)(&IpAddress)+2),
               *((unsigned char*)(&IpAddress)+3));
      CgiLib__VarList ("WWW_SERVER_ADDR", Scratch, 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';
/** 26-MAR-2007
         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);
}  

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

                        /***************************/
                        /* REQUEST BODY PROCESSING */
                        /***************************/

/*****************************************************************************/
/*
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 can free() this memory when finished with it or it is implicitly
freed when called again (i.e. CGIplus).  Note: the OSU DECnet scripting stream
is NET_LINK:, the WASD CGI DECnet scripting stream is NET$LINK, the two should
not be confused.  Will processes an RFC2068 (HTTP/1.1) "Transfer-Encoding:
chunked" request body from 'chunked' format into a binary bag of bytes.
*/

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

   int  status,
        BufferSize,
        ChunkLength,
        ContentLength,
        ContentUnEncoded,
        EncodingChunked,
        ReadCount;
   char  *cptr, *sptr, *zptr,
         *RequestTimeGmtPtr,
         *ServerSoftwarePtr;
   char  ChunkString [32];

   /*********/
   /* 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__Environment == CGILIB_ENVIRONMENT_CGIPLUS)
         BodyReadFd = open ("HTTP$INPUT:", O_RDONLY, 0, "ctx=bin");
      else
      if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU)
         BodyReadFd = open ("NET_LINK:", O_RDONLY, 0, "ctx=bin");
      else
      /* 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)
   {
      /* if called again (i.e. CGIplus) */
      free (BufferPtr);
      BufferPtr = NULL;
   }

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

   CgiLib__BodyIsFormUrlEncoded = CgiLib__BodyIsMultipartFormData = 0;
   if (!strncmp (cptr, "application/x-www-form-urlencoded", 33))
      CgiLib__BodyIsFormUrlEncoded = 1;
   else
   if (!strncmp (cptr, "multipart/form-data;", 20))
      CgiLib__BodyIsMultipartFormData = 1;

   cptr = CgiLib__GetVar ("WWW_CONTENT_LENGTH", "");
   if (*cptr == '?')
   {
      ContentUnEncoded = 1;
      ContentLength = 0;
   }
   else
   {
      ContentUnEncoded = 0;
      ContentLength = atoi(cptr);
   }
   if (CgiLib__Debug)
      fprintf (stdout, "ContentLength: %d ContentUnEncoded: %d\n",
               ContentLength, ContentUnEncoded);

   if (ContentUnEncoded)
      EncodingChunked = 0;
   else
   {
      cptr = CgiLib__GetVar ("WWW_HTTP_TRANSFER_ENCODING", "");
      if (cptr[0] && strstr (cptr, "chunked"))
         EncodingChunked = 1;
      else
         EncodingChunked = 0;
   }

   if ((CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS ||
        CgiLib__Environment == CGILIB_ENVIRONMENT_WASD) &&
       CgiLibHttpProtocolVersion() == 11 &&
       !CgiLib__ResponseCount)
   {
      cptr = CgiLib__GetVar ("WWW_HTTP_EXPECT", "");
      if (cptr[0] && strstr (cptr, "100-continue") ||
                     strstr (cptr, "100-Continue"))
      {
         /* supply a "100 Continue" interim response header */
         RequestTimeGmtPtr =  CgiLib__GetVar ("REQUEST_TIME_GMT", NULL); 
         ServerSoftwarePtr =  CgiLib__GetVar ("SERVER_SOFTWARE", "");
         fprintf (stdout,
"%s 100 %s\n\
Server: %s\n\
%s%s%s\
\n",
               CgiLib__HttpProtocolVersionPtr,
               CgiLibHttpStatusCodeText(100),
               ServerSoftwarePtr,
               RequestTimeGmtPtr == NULL ? "" : "Date: ",
               RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr,
               RequestTimeGmtPtr == NULL ? "" : "\n");
         fflush(stdout);
      }
   }

   if (EncodingChunked)
   {
      /************************/
      /* chunked request body */
      /************************/

      BufferPtr = NULL;
      BufferSize = 0;
      for (;;)
      {
         /* read one character at a time until the size string is available */
         for (zptr=(sptr=ChunkString)+sizeof(ChunkString); sptr < zptr; sptr++)
         {
            if (ReadCount = read (BodyReadFd, sptr, 1) < 0)
            {
               status = vaxc$errno;
               fprintf (stdout, "%%CGILIB-E-CHUNK, binary read error\n");
               exit (status);
            }
            if (!isxdigit(*sptr)) break;
         }
         if (sptr >= zptr)
         {
            fprintf (stdout, "%%CGILIB-E-CHUNK, size overflow\n");
            exit (SS$_BUGCHECK);
         }
         *sptr = '\0';
         if (!ChunkString[0])
         {
            fprintf (stdout, "%%CGILIB-E-CHUNK, no size\n");
            exit (SS$_BUGCHECK);
         }

         ChunkLength = strtol (ChunkString, NULL, 16);
         if (CgiLib__Debug)
            fprintf (stdout, "|%s| %d\n", ChunkString, ChunkLength);
         /* zero length chunk, end of chunked body */
         if (!ChunkLength) break;

         /* skip to the end of the chunk size line (one character at a time) */
         for (;;)
         {
            ChunkString[0] = '\0';
            if (read (BodyReadFd, ChunkString, 1) < 0) break;
            if (ChunkString[0] == '\n') break;
         }
         if (ChunkString[0] != '\n')
         {
            fprintf (stdout, "%%CGILIB-E-CHUNK, format error\n");
            exit (SS$_BUGCHECK);
         }

         if (CgiLib__VeeMemInUse)
            BufferPtr = CgiLibVeeMemRealloc (BufferPtr,
                                             BufferSize+ChunkLength+1);
         else
            BufferPtr = realloc (BufferPtr, BufferSize+ChunkLength+1);
         if (!BufferPtr)
            if (CgiLib__VeeMemContinue)
               return (NULL);
            else
               exit (vaxc$errno);
         BufferSize += ChunkLength;

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

            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-CHUNK, binary read error\n");
               exit (status);
            }

            BufferCount += ReadCount;
         }

         /* skip over the trailing <CR><LF> (one character at a time) */
         for (;;)
         {
            ChunkString[0] = '\0';
            if (read (BodyReadFd, ChunkString, 1) < 0) break;
            if (ChunkString[0] == '\n') break;
         }
         if (ChunkString[0] != '\n')
         {
            fprintf (stdout, "%%CGILIB-E-CHUNK, format error\n");
            exit (SS$_BUGCHECK);
         }
      }
   }
   else
   {
      /**************************/
      /* unchunked request body */
      /**************************/

      /* an ambit quantity (it doubles each time it's about the overflow) */
      if (ContentUnEncoded) ContentLength = 8192;

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

      if (CgiLib__VeeMemInUse)
         BufferPtr = CgiLibVeeMemCalloc (ContentLength+1);
      else
         BufferPtr = calloc (1, ContentLength+1);
      if (!BufferPtr)
         if (CgiLib__VeeMemContinue)
            return (NULL);
         else
            exit (vaxc$errno);
      BufferSize = ContentLength;
      BufferCount = 0;

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

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

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

         BufferCount += ReadCount;

         if (ContentUnEncoded)
         {
            if (BufferCount >= BufferSize)
            {
               ContentLength *= 2;
               if (CgiLib__VeeMemInUse)
                  BufferPtr = CgiLibVeeMemRealloc (BufferPtr, ContentLength+1);
               else
                  BufferPtr = realloc (BufferPtr, ContentLength+1);
               if (!BufferPtr)
                  if (CgiLib__VeeMemContinue)
                     return (NULL);
                  else
                     exit (vaxc$errno);
               BufferSize = ContentLength;
            }
         }
         else
         if (BufferCount == ContentLength)
            break;
      }
   }

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

/*****************************************************************************/
/*
Return true if the body of the request is "application/x-www-form-urlencoded".
*/

int CgiLibBodyIsFormUrlEncoded ()
{
   /*********/
   /* begin */
   /*********/

   if (CgiLib__Debug)
      fprintf (stdout, "CgiLibBodyIsFormUrlEncoded() %d\n",
               CgiLib__BodyIsFormUrlEncoded);

   return (CgiLib__BodyIsFormUrlEncoded);
}

/*****************************************************************************/
/*
Return true if the body of the request is "multipart/form-data".
*/

int CgiLibBodyIsMultipartFormData ()
{
   /*********/
   /* begin */
   /*********/

   if (CgiLib__Debug)
      fprintf (stdout, "CgiLibBodyIsMultipartFormData() %d\n",
               CgiLib__BodyIsMultipartFormData);

   return (CgiLib__BodyIsMultipartFormData);
}

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

   /* no need for any CgiLibVeeMem..() functionality */
   cptr = BufferPtr = calloc (1, BodyLength+1);
   if (!BufferPtr) 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;
   unsigned 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 && *(ULONGPTR)cptr != 'ary='; cptr++);
   if (!*cptr) return (0);
   cptr += sizeof(unsigned long);
   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 (*(USHORTPTR)cptr == '--') break;
      if (*(USHORTPTR)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);

      /* no need for any CgiLibVeeMem..() functionality */
      bptr = BufferPtr = calloc (1, BufferSize);
      if (!BufferPtr) 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);
}

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

                           /********************/
                           /* VEEMEM FUNCTIONS */
                           /********************/

/*****************************************************************************/
/*
Enable/disable (1/0) VeeMem for applicable CGILIB functions.  Return the
previous setting.  Any call resets VeeMem memory.  This only enables/disables
VeeMem for CGILIB functions, an application can use VeeMem without CGILIB also
using it (but there is not much point if you're also using the applicable
CGILIB functions - see CGILIB prologue).
*/ 

int CgiLibVeeMemInUse (int OffOn)

{
   int  PrevVeeMemInUse;

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

   if (CgiLib__Debug) fprintf (stdout, "CgiLibVeeMemInUse() %d\n", OffOn);

   PrevVeeMemInUse = CgiLib__VeeMemInUse;

   CgiLibVeeMemInit ();

   CgiLib__VeeMemInUse = OffOn;

   return (PrevVeeMemInUse);
}

/*****************************************************************************/
/*
The first call initialises the virtual memory zone.  Subsequent calls (and
there should be one at the conclusion of each request processed) releases back
into the virtual memory zone all of the individually allocated memory chunks.
*/ 

int CgiLibVeeMemInit ()

{
   static $DESCRIPTOR (ZoneNameDsc, "CGILIB_VEEMEM_ZONE");
   static unsigned long  Algorithm = LIB$K_VM_QUICK_FIT,
                 AlgorithmArg = 64,
                 Flags = LIB$M_VM_BOUNDARY_TAGS |
                         LIB$M_VM_GET_FILL0 |
                         LIB$M_VM_TAIL_LARGE,
                 ExtendSize = 512,
                 InitialSize = 512,
                 BlockSize = 64;

   int  status;

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

   if (CgiLib__Debug)
      fprintf (stdout, "CgiLibVeeMemInit() %d\n", CgiLib__VeeMemZoneId);

   if (CgiLib__VeeMemZoneId)
      status = lib$reset_vm_zone (&CgiLib__VeeMemZoneId);
   else
      status = lib$create_vm_zone (&CgiLib__VeeMemZoneId,
                                   &Algorithm, &AlgorithmArg, &Flags,
                                   &ExtendSize, &InitialSize, &BlockSize,
                                   0, 0, 0, &ZoneNameDsc, 0, 0);

   CgiLib__VeeMemCurrent = CgiLib__VeeMemPeak = 0;

   return (status);
}

/*****************************************************************************/
/*
Allocate memory from the zone.
*/ 

void* CgiLibVeeMemCalloc (unsigned long ChunkSize)

{
   int  status;
   unsigned long  BaseAddress;
   void  *ChunkPtr;

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

   if (CgiLib__Debug) fprintf (stdout, "CgiLibVeeMemCalloc() %u\n", ChunkSize);

   vaxc$errno = 0;
   ChunkSize += sizeof(unsigned long) + CGILIB_VEEMEM_EBROOM;
   status = lib$get_vm (&ChunkSize, &BaseAddress, &CgiLib__VeeMemZoneId);
   if (!(status & 1))
   {
      vaxc$errno = status;
      return (NULL);
   }
   ChunkSize -= sizeof(unsigned long) + CGILIB_VEEMEM_EBROOM;
   *(ULONGPTR)BaseAddress = ChunkSize;
   ChunkPtr = (void*)(BaseAddress + sizeof(unsigned long));

   CgiLib__VeeMemCurrent += ChunkSize;
   if (CgiLib__VeeMemCurrent > CgiLib__VeeMemPeak)
      CgiLib__VeeMemPeak = CgiLib__VeeMemCurrent;
   if (CgiLib__Debug)
      fprintf (stdout, "current:%u peak:%u\n",
               CgiLib__VeeMemCurrent, CgiLib__VeeMemPeak);

   return (ChunkPtr);
}

/*****************************************************************************/
/*
Expand (or even contract) an individual a general-use chunk.  See VmGet().
*/ 

void* CgiLibVeeMemRealloc
(
void *ChunkPtr,
unsigned long ChunkSize
)
{
   int  status,
        CurrentChunkSize;
   unsigned long  BaseAddress,
                  CurrentBaseAddress;
   void  *CurrentChunkPtr;

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

   if (CgiLib__Debug) fprintf (stdout, "CgiLibVeeMemRealloc() %d\n", ChunkSize);

   vaxc$errno = 0;

   if (!ChunkSize) return (NULL);

   if (!ChunkPtr) return (CgiLibVeeMemCalloc (ChunkSize));

   CurrentChunkPtr = ChunkPtr;
   CurrentBaseAddress = (unsigned long)CurrentChunkPtr;
   CurrentBaseAddress -= sizeof(unsigned long);
   CurrentChunkSize = *(ULONGPTR)CurrentBaseAddress;

   /* if already large enough */
   if (CurrentChunkSize >= ChunkSize) return (ChunkPtr);

   /* allocate a new, larger chunk */
   ChunkSize += sizeof(unsigned long) + CGILIB_VEEMEM_EBROOM;
   status = lib$get_vm (&ChunkSize, &BaseAddress, &CgiLib__VeeMemZoneId);
   if (!(status & 1))
   {
      vaxc$errno = status;
      return (NULL);
   }
   ChunkSize -= sizeof(unsigned long) + CGILIB_VEEMEM_EBROOM;
   *(ULONGPTR)BaseAddress = ChunkSize;
   ChunkPtr = (void*)(BaseAddress + sizeof(unsigned long));

   /* copy the existing chunk into the new chunk */
   memcpy (ChunkPtr, CurrentChunkPtr, CurrentChunkSize);

   /* free the previous chunk */
   status = lib$free_vm (0, &CurrentBaseAddress, &CgiLib__VeeMemZoneId);
   if (!(status & 1))
   {
      vaxc$errno = status;
      return (NULL);
   }

   CgiLib__VeeMemCurrent -= CurrentChunkSize;
   CgiLib__VeeMemCurrent += ChunkSize;
   if (CgiLib__VeeMemCurrent > CgiLib__VeeMemPeak)
      CgiLib__VeeMemPeak = CgiLib__VeeMemCurrent;
   if (CgiLib__Debug)
      fprintf (stdout, "current:%u peak:%u\n",
               CgiLib__VeeMemCurrent, CgiLib__VeeMemPeak);

   return (ChunkPtr);
}

/*****************************************************************************/
/*
Explicitly release memory back into the zone.
*/ 

void CgiLibVeeMemFree (void *ChunkPtr)

{
   int  status,
        ChunkSize;
   unsigned long  BaseAddress;

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

   if (CgiLib__Debug) fprintf (stdout, "CgiLibVeeMemFree()\n");

   vaxc$errno = 0;
   if (!ChunkPtr) return;

   BaseAddress = (unsigned long)ChunkPtr;
   BaseAddress -= sizeof(unsigned long);
   ChunkSize = *(ULONGPTR)BaseAddress;

   status = lib$free_vm (0, &BaseAddress, &CgiLib__VeeMemZoneId);
   if (!(status & 1)) vaxc$errno = status;

   CgiLib__VeeMemCurrent -= ChunkSize;
   if (CgiLib__Debug)
      fprintf (stdout, "chunk:%u current:%u peak:%u\n",
               ChunkSize, CgiLib__VeeMemCurrent, CgiLib__VeeMemPeak);
}

/*****************************************************************************/
/*
CGIlib usually just exits with an error status when a memory allocation fails.
With some memory allocation it's better to know that it failed.  Calling this
with either 1 (continue returning the expected result when failed) or 0 (just
exit with the error status) allows fine-grained behaviour control when using
functions that employ dynamic memory.  Works for both CgiLibVeeMem..()
allocated memory and calloc()/realloc() etc.  Returns the previous state.
*/ 

int CgiLibVeeMemContinue (int OffOn)

{
   int  WasOffOn;

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

   if (CgiLib__Debug)
      fprintf (stdout, "CgiLibVeeMemContinue() %d\n", OffOn);

   WasOffOn = CgiLib__VeeMemContinue;
   CgiLib__VeeMemContinue = OffOn;
   return (WasOffOn);
}

/*****************************************************************************/
/*
Show (debug) the virtual memory zone.
(Just not interested in the user-arg parameter.)
Casting from (void*) to avoid compiler message %CC-I-PROTOSCOPE.
*/ 

int CgiLibVeeMemShow (void *StrDscPtr)

{
   static unsigned long  DetailLevel = 3;
   static int  veeMemZone = 0;

   int  status;
   unsigned long  FindContext,
                  ZoneId;
   struct dsc$descriptor  *DscPtr;

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

   if (CgiLib__Debug) fprintf (stdout, "CgiLibVeeMemShow()\n");

   if (StrDscPtr)
   {
      /* action-routine */
      DscPtr = (struct dsc$descriptor*)StrDscPtr;
#if 0
      if (!veeMemZone)
      {
         if (DscPtr->dsc$w_length < 53) return (SS$_NORMAL);
         if (memcmp (DscPtr->dsc$a_pointer+21,
            "Zone name = \"CGILIB_VEEMEM_ZONE\"", 32)) return (SS$_NORMAL);
         veeMemZone = 1;
         fputs ("\n", stdout);
      }
#endif
      fprintf (stdout, "%*.*s\n",
               DscPtr->dsc$w_length, DscPtr->dsc$w_length,
               DscPtr->dsc$a_pointer);
      return (SS$_NORMAL);
   }

   FindContext = 0;
   while ((status = lib$find_vm_zone (&FindContext, &ZoneId)) == SS$_NORMAL)
   {
      status = lib$show_vm_zone (&ZoneId, &DetailLevel, &CgiLibVeeMemShow, 0);
      if (!(status & 1)) break;
   }
   if (status != LIB$_NOTFOU) return (status);
   fflush (stdout);

   veeMemZone = 0;
   return (SS$_NORMAL);
}

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

