/*****************************************************************************/
/*
                                 Conan.c


CGI-compliant script providing access to VMS help and text libraries.
Can be used in the WASD CGI, WASD CGI-plus, OSU and vanilla CGI environments.

"Conan The Librarian"


PAGE LAYOUT
-----------
Page layout and colouration may be specified via the appropriate command-line
qualifiers (or corresponding logical/symbol name). Defaults apply for any not
specified.  See "Qualifiers" section below, and also about the logical name or
symbol "CONAN$PARAM".

An example of changing the page colour to white and the banner to red!

  /PBGCOLOR="#ffffff" /PHBGCOLOR="#ff0000"

Don't like explicitly setting a browser's colours?  A colour may be disabled by
setting it to empty.  The following example disables all colours.

  /PBGCOLOR/PBBGCOLOR/PHBGCOLOR/PHTEXT/PLINK/PTEXT/PVLINK

The script can format a page in either of two layouts.

  1. Tables are used to create a coloured header and button bar (DEFAULT).
     Default colours are white page with grey heading and button outlines.
  2. Textual header, horizontal rules and a textual button bar.
     No default colours.

Select other than the default using the following:

  /PLAYOUT=2

Local information may be included in the header. For layout 1 this should be
integrated with the <TABLE> formatted header and to the right of the header
information. Text, an image logo, just about anything could be included. This
is a example of providing a textual form of a local logo:

  /PHLOCAL="<TD ALIGN=right VALIGN=top><FONT SIZE=-1 COLOR=#ffffff>"-
  "<B>WASD<HyperText Services</B></FONT></TD>"

This is an example of providing a local graphical logo:

  /PHLOCAL="<TD ALIGN=right VALIGN=top><IMG SRC=/local/logo.gif></TD>"

Such local information with layout 2 is included immediately before the header
information at the top of the page.

Button labels are customizable (potentially to non-English language). They
comprise a label, equate symbol and URL-style path suitable for creating a
link. Multiple buttons are separated using the semicolon. Note that any such
button customization must provide escaped HTML-forbidden characters in the
button label and URI-forbidden characters in the path! The backslash
character, "\", escapes characters, including the button-delimitting "=" and
";". There are defaults, see DEFAULT_BUTTONS.

Here is an example of changing the button labels:

  /BUTTON="Close;Help=/conan/-/conanhelp.html"

Always have the equivalent of "Close" for the first button!  Additional buttons
may be created by adding "label=path;" elements to the button string.  In this
way an additional information page could be referenced as follows:

  /BUTTON="Close;Help=/conan/-/conanhelp.html;Other VMS=/vms/"

DIFFICULTY FITTING ALL THESE QUALIFIERS INTO A COMMAND LINE OR LOGICAL?
Use an existing, or create a new, DCL wrapper procedure for the script (same
name and directory) and build up a DCL symbol to provide the information. Up
to 1024 characters can be included in this way. For example:

  $ CONAN$PARAM = "/BUTTON=""Close;Help=/conan/-/conanhelp.html"""
  $ CONAN$PARAM = CONAN$PARAM + "/PBGCOLOR/PLINK/PVLINK"
  $ CONAN$PARAM = CONAN$PARAM + "/PHLOCAL=""<TD VALIGN=top>...</TD>"""
  $ RUN HT_EXE:CONAN

AND REMEMBER ... Conan can be used as a CGIplus script.  This may require the
script to be purged from the server before new startup parameters come into
effect.  Use a command like the following:

  HTTPD /DO=DCL=PURGE


WASD SERVER CONFIGURATION
-------------------------
This script uses URL-style paths for accessing libraries. The HTTP server must
therefore provide a mapping for all directories that libraries will be
accessed. Something like:

  pass /sys$common/syshlp/* /sys$common/syshlp/*

If auto-scripting is available (as with WASD) these should also be a
configuration directive providing this (example from WASD):

  .HLB  application/x-script  /Conan  VMS help library


HELP LIBRARIES
--------------
Help library modules have a reasonably complex internal structure described in 
the "Utility Routines Manual" and the "Command Definition, Librarian, and 
Message Utilities Manual".  The structure will not be described here. 

KeyWord[1]...KeyWord[10] represent the help keywords 1...10 that are used as 
keys into help information.


CGI ENVIRONMENT VARIABLES
-------------------------
?single=1       one module per line (rather than alphabetically grouped)
?extract=1      extract (rather than format) a Help module
                (the final 'e' in 'explode' is a link to this)


LOGICAL NAMES
-------------
CONAN$DBUG             turns on all "if (Debug)" statements
CONAN$PARAM            equivalent to (overrides) the command line
                       parameters/qualifiers (define as a system-wide logical)
HTTPD$GMT              timezone offset from Greenwich Mean Time
                       (e.g. "+09:30" and only needed if DTSS is not in use)


QUALIFIERS
----------
/BUTTONS=       string containing button labels/paths
/CHARSET=       "Content-Type: text/html; charset=...", empty suppress charset
/DBUG           turns on all "if (Debug)" statements
/[NO]ODS5       control extended file specification (basically for testing)
/PBACKGROUND=   <body> background image path
/PBGCOLOR=      <body> background colour
/PBBGCOLOR=     button background color
/PBBORDER=      width of button border
/PHBGCOLOR=     heading background color
/PHBORDER=      width of heading and button-bar border
/PHLOCAL=       local information to be included in header
/PHTEXT=        heading text colour
/PLAYOUT=       1 is coloured header & buttons, 2 is text & horizontal rules
/PLINK=         <body> link colour
/PTEXT=         <body> text colour
/PVLINK=        <body> visited link colour
/SINGLE         one module per line (rather than alphabetically grouped)
                for all requests


BUILD DETAILS
-------------
See BUILD_CONAN.COM procedure.


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


VERSION HISTORY (update SOFTWAREVN as well!)
---------------
23-DEC-2003  MGD  v3.7.3, minor conditional mods to support IA64
12-APR-2003  MGD  v3.7.2, link colour changed to 0000cc
20-AUG-2002  MGD  v3.7.1, allow for HTTP/1.1 'Cache-control:' field used
                          by Mozilla variants (instead of HTTP/1.0 'pragma:')
17-AUG-2002  MGD  v3.7.0, changed help module formatting that absorbed
                          all leading white-space destroying intentional
                          formatting (surprised someone hasn't complained
                          about it long before this - thanks Dave Pampreen.),
                          extract a help module as a plain-text page
                          (click on the final 'e' in 'explode'),
                          some accomodations for CSWS v1.2,
                          bugfix; allow for strings with space separated
                          keywords as help keywords in OpenHelpModule()
01-JUL-2001  MGD  v3.6.1, add 'SkipParameters' for direct OSU support
28-OCT-2000  MGD  v3.6.0, use CGILIB object module
11-APR-2000  MGD  v3.5.0, support extended file specifications (ODS-5),
                          bugfix; eliminate extraneous comma in keyword list
07-AUG-1999  MGD  v3.4.0, use more of the CGILIB functionality
29-JUN-1999  MGD  v3.3.0, optional module name list of one per line
                          (rather than alphabetically grouped)
24-APR-1999  MGD  v3.2.0, use CGILIB.C,
                          standard CGI environment (Netscape FastTrack)
02-OCT-1998  MGD  v3.1.0, provide content-type "; charset=..."
08-AUG-1998  MGD  v3.0.3, OSU output processing reworked,
                          bugfix; TimeSetTimezone() 'Seconds' unsigned->signed
06-AUG-1998  MGD  v3.0.2, accomodation for OSU ... reduce HTTP response
                          header carriage control from <CR><LF> to <LF> only
                          (OSU/IE4 combination problematic)
01-AUG-1998  MGD  v3.0.1, suppress table background colours if empty,
                          accomodations for OSU environment
29-APR-1998  MGD  v3.0.0, remove need for WASD MapUrl() functions
                          (making it a GENERIC CGI script!!),
                           some tidying up and cosmetic changes
26-FEB-1998  MGD  v2.9.2, added HTML table around title in help library,
                          TimeSetGmt() modified to be in line with HTTPd
19-AUG-1997  MGD  v2.9.1, MapUrl() to MapUrl_Map() for conditional mapping
20-JUL-1997  MGD  v2.9.0, added /BODY= qualifier (changed background colour)
20-JUN-1997  MGD  v2.8.0, compatible with standard and CGIplus environments,
                          "Pragma: no-cache" now overrides "If-Modified-Since:"
14-MAR-1997  MGD  v2.7.1, bugfix; absorbtion of leading white-space
07-AUG-1996  MGD  v2.7.0, allow help topic to be specified in path, for example
                          "/conan/run/process" or "/help/delete/file"
16-MAY-1996  MGD  v2.6.2, changed XBMs to tranparaent GIFs
23-FEB-1996  MGD  v2.6.1, bugfix, after modifying the HTTPD$GMT format for the
                          HTTPd server I had forgotten about some scripts
12-OCT-1995  MGD  v2.6.0, added 'Referer:', 'Last-Modified:'/'If-Modified-Since:'
18-AUG-1995  MGD  v2.5.0, added "search" form at appropriate places
20-JUN-1995  MGD  v2.4.0, added "explode" feature, lists all help levels below
                          the key specified in a single page
24-MAY-1995  MGD  v2.3.0, minor changes for AXP compatibility
31-MAR-1995  MGD  v2.2.0, ongoing maintenance
02-FEB-1995  MGD  v2.1.0, add "Conan the Librarian" icon
13-DEC-1994  MGD  v2.0.0, made CGI compliant
09-JUL-1994  MGD  v1.0.0, initial development
*/
/*****************************************************************************/

#define SOFTWAREVN "3.7.3"
#define SOFTWARENM "CONAN"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN
#endif

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/* VMS related header files */
#include <descrip.h>
#include <iodef.h>
#include <jpidef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <lbrdef.h>
#include <lhidef.h>
#include <lnmdef.h>
#include <rms.h>
#include <ssdef.h>
#include <stsdef.h>
#include <syidef.h>

/* application related header files */
#include "enamel.h"
#include <cgilib.h>

#ifndef __VAX
#   pragma nomember_alignment
#endif

#ifndef __VAX
#  ifndef NO_ODS_EXTENDED
#     define ODS_EXTENDED 1
      /* this is smaller than the technical maximum, but still quite large! */
#     define ODS_MAX_FILE_NAME_LENGTH 511
#     define ODS_MAX_FILESYS_NAME_LENGTH 264
#  endif
#endif
#define ODS2_MAX_FILE_NAME_LENGTH 255
#ifndef ODS_MAX_FILE_NAME_LENGTH
#  define ODS_MAX_FILE_NAME_LENGTH ODS2_MAX_FILE_NAME_LENGTH
#endif
#if ODS_MAX_FILE_NAME_LENGTH < ODS2_MAX_FILE_NAME_LENGTH
#  define ODS_MAX_FILE_NAME_LENGTH ODS2_MAX_FILE_NAME_LENGTH
#endif

#define boolean int
#define true 1
#define false 0
 
#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))

#define FI_LI __FILE__, __LINE__

#define DEFAULT_LIBRARY "SYS$HELP:HELPLIB.HLB"

#define DEFAULT_BUTTONS "Close;Help=/conan/-/conanhelp.html"

/* this macro just plugs in some script-specific code into ButtonBar() */
#define SCRIPT_SPECIFIC_BUTTON_CODE \
   ButtonInternal[0] = CgiFormRefererPtr;

#define DEFAULT_PS_BGCOLOR        "#ffffff"
#define DEFAULT_PS_TEXT           "#000000"
#define DEFAULT_PS_LINK           "#0000cc"
#define DEFAULT_PS_VLINK          "#0000cc"
#define DEFAULT_PS_HEADBGCOLOR    "#cccccc"
#define DEFAULT_PS_HEADBORDER     "0"
#define DEFAULT_PS_HEADTEXT       "#000000"
#define DEFAULT_PS_BUTTONBGCOLOR  "#ffffff"
#define DEFAULT_PS_BUTTONBORDER   "1"

#define PS_BACKGROUND     0
#define PS_BGCOLOR        1
#define PS_TEXT           2
#define PS_LINK           3
#define PS_VLINK          4
#define PS_HEADBGCOLOR    5
#define PS_HEADTEXT       6
#define PS_HEADBORDER     7
#define PS_BUTTONBGCOLOR  8
#define PS_BUTTONBORDER   9
#define PS_BODYTAG       10
#define PS_LAYOUT        11
#define PS_HEADLOCAL     12
#define PS_HEADPADDING   13

char  *PageScheme [16];

char  Utility [] = "CONAN";

boolean  Debug,
         CliListSingle,
         DoHeaderInformation,
         DoListLibraries,
         DoLibraryMenu,
         DoSearch,
         DoSearchStatistics,
         ErrorReported,
         ExplodeHelp,
         ExtractHelp,
         KeyNameWildcard,
         ListSingle,
         OsuEnvironment,
         StdCgiEnvironment,
         TimeAheadOfGmt;
         
int  SearchHitCount,
     KeyWordCount,
     OdsExtended,
     RecordsSearched,
     SearchStringLength;

unsigned long  LibraryIndex,
               IndexNumber;

unsigned long  IfModifiedSinceBinaryTime [2],
               TimeGmtDeltaBinary [2];

char  ContentTypeCharset [64],
      DefaultButtons [] = DEFAULT_BUTTONS,
      HtmlFormTitle [256],
      HtmlLibraryTitle [256],
      HtmlReferer [512],
      HtmlSearchString [512],
      LastModifiedGmDateTime [32],
      LibraryDirPath [ODS_MAX_FILE_NAME_LENGTH+1],
      LibraryName [ODS_MAX_FILE_NAME_LENGTH+1],
      LibraryPathInfo [ODS_MAX_FILE_NAME_LENGTH+1],
      LibrarySpec [ODS_MAX_FILE_NAME_LENGTH+1],
      LibraryTitle [256],
      TimeGmtString [32],
      TimeGmtVmsString [32],
      UnixDateTime [64],
      UriFormTitle [256],
      UriReferer [512];

char  *ButtonPtr = DefaultButtons,
      *CgiHttpIfModifiedSincePtr,
      *CgiHttpPragmaPtr,
      *CgiHttpCacheControlPtr,
      *CgiLibEnvironmentPtr,
      *CgiPathInfoPtr,
      *CgiPathTranslatedPtr,
      *CgiRequestMethodPtr,
      *CgiScriptNamePtr,
      *CgiServerSoftwarePtr,
      *CgiFormDoPtr,
      *CgiFormExplodePtr,
      *CgiFormExtractPtr,
      *CgiFormKeyPtr,
      *CgiFormRefererPtr,
      *CgiFormTitlePtr,
      *CgiFormSearchPtr,
      *CgiFormSinglePtr,
      *CliCharsetPtr,
      *CharsetPtr;

/* element 0 is not used, keywords number from 1 to 10 */
char  KeyWord [11][256],
      KeyWordHtml [11][256],
      KeyName [11][256],
      KeyNameHtml [11][256],
      SoftwareID [48];
      
struct lhidef  LibraryHeader;

struct {
   unsigned long  lo32;
   unsigned long  hi32;
} ModuleRFA;

/* CGIplus specific */
int  IsCgiPlus,
     CgiPlusUsageCount;

/* required function prototypes */
char* SearchText (char*, char*, boolean);
int ListHelpModule (struct dsc$descriptor_s*, void*);
int ListTextModule (struct dsc$descriptor_s*, void*);
char* MungeUnderScores (char*);
char* FormatRequestKeys (boolean, int);
int ExtractHelpModule (struct dsc$descriptor_s*, void*);
int OpenHelpModule (struct dsc$descriptor_s*, void*);
int OpenTextModule (struct dsc$descriptor_s*, void*);
int SearchHelpModule (struct dsc$descriptor_s*, void*);
int SearchTextModule (struct dsc$descriptor_s*, void*);

/*****************************************************************************/
/*
'argc' and 'argv' are only required to support CgiLibEnvironmentInit(), in
particular the OSU environment.
*/

main
(
int argc,
char *argv[]
)
{
   int  idx,
        status;
   char  *cptr, *sptr;

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

   sprintf (SoftwareID, "%s (%s)", SOFTWAREID, CgiLibEnvironmentVersion());

   if (getenv ("CONAN$DBUG")) Debug = true;
   CgiLibEnvironmentSetDebug (Debug);

   CgiLibEnvironmentInit (argc, argv, false);
   CgiLibEnvironmentPtr = CgiLibEnvironmentName();

   GetParameters ();
   SetPageScheme ();

   CgiLibResponseSetBody (PageScheme[PS_BODYTAG]);
   CgiLibResponseSetCharset (CliCharsetPtr);
   CgiLibResponseSetSoftwareID (SoftwareID);
   CgiLibResponseSetErrorMessage ("Reported by Conan The Librarian");

   IsCgiPlus = CgiLibEnvironmentIsCgiPlus ();

#ifdef ODS_EXTENDED
   OdsExtended = (GetVmsVersion() >= 72);
   ENAMEL_NAML_SANITY_CHECK
#endif /* ODS_EXTENDED */

   if (IsCgiPlus)
   {
      for (;;)
      {
         /* block waiting for the next request */
         CgiLibVar ("");
         CgiPlusUsageCount++;
         if (Debug) fputs ("Content-Type: text/plain\n\n", stdout);
         ConanTheLibrarian ();
         CgiLibCgiPlusEOF ();
      }
   }
   else
      ConanTheLibrarian ();

   exit (SS$_NORMAL);
}

/*****************************************************************************/
/*
Get "command-line" parameters, whether from the command-line or from a
configuration symbol or logical containing the equivalent.  OSU scripts have
the 'method', 'url' and 'protocol' supplied as P1, P2, P3 (these being detected
and used by CGILIB), and are of no interest to this function.
*/

GetParameters ()

{
   static char  CommandLine [256];
   static unsigned long  Flags = 0;

   int  status,
        SkipParameters;
   unsigned short  Length;
   char  ch;
   char  *aptr, *cptr, *clptr, *sptr;
   $DESCRIPTOR (CommandLineDsc, CommandLine);

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

   if (!(clptr = getenv ("CONAN$PARAM")))
   {
      /* get the entire command line following the verb */
      if (VMSnok (status =
          lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags)))
         exit (status);
      (clptr = CommandLine)[Length] = '\0';
   }

   /* if CSWS (Apache) */
   if (getenv ("APACHE$SHARED_SOCKET"))
   {
      /* look for something non-space outside of quotes */
      for (cptr = clptr; *cptr; cptr++)
      {
         if (isspace(*cptr)) continue;
         if (*cptr != '\"') break;
         cptr++;
         while (*cptr && *cptr != '\"') cptr++;
         if (*cptr) cptr++;
      }
      /* if nothing outside of quotes then ignore the command line */
      if (!*cptr) return;
   }

   /* if OSU environment then skip P1, P2, P3 */
   if (getenv ("WWWEXEC_RUNDOWN_STRING"))
      SkipParameters = 3;
   else
      SkipParameters = 0;

   aptr = NULL;
   ch = *clptr;
   for (;;)
   {
      if (aptr) *aptr = '\0';
      if (!ch) break;

      *clptr = ch;
      if (Debug) fprintf (stdout, "clptr |%s|\n", clptr);
      while (*clptr && isspace(*clptr)) *clptr++ = '\0';
      aptr = clptr;
      if (*clptr == '/') clptr++;
      while (*clptr && !isspace (*clptr) && *clptr != '/')
      {
         if (*clptr != '\"')
         {
            clptr++;
            continue;
         }
         cptr = clptr;
         clptr++;
         while (*clptr)
         {
            if (*clptr == '\"')
               if (*(clptr+1) == '\"')
                  clptr++;
               else
                  break;
            *cptr++ = *clptr++;
         }
         *cptr = '\0';
         if (*clptr) clptr++;
      }
      ch = *clptr;
      if (*clptr) *clptr = '\0';
      if (Debug) fprintf (stdout, "aptr |%s|\n", aptr);
      if (!*aptr) continue;

      if (SkipParameters)
      {
         SkipParameters--;
         continue;
      }

      if (strsame (aptr, "/BUTTONS=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         ButtonPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/CHARSET=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliCharsetPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/DBUG", -1))
      {
         Debug = true;
         continue;
      }
      if (strsame (aptr, "/ODS5", 5))
      {
         OdsExtended = true;
         continue;
      }
      if (strsame (aptr, "/NOODS5", 7))
      {
         OdsExtended = false;
         continue;
      }
      if (strsame (aptr, "/SINGLE", -1))
      {
         CliListSingle = true;
         continue;
      }
      if (GetPageParameter (aptr)) continue;

      if (*aptr != '/')
      {
         fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n",
                  Utility, aptr);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }
      else
      {
         fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n",
                  Utility, aptr+1);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }
   }
}

/*****************************************************************************/
/*
Get command-line parameters associated with page scheme.
*/

boolean GetPageParameter (char *aptr)

{
   char  *cptr;

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

   if (strsame (aptr, "/PBACKGROUND=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_BACKGROUND] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PBGCOLOR=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_BGCOLOR] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PBBGCOLOR=", 5))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_BUTTONBGCOLOR] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PBBORDER=", 5))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_BUTTONBORDER] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PHBGCOLOR=", 5))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_HEADBGCOLOR] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PHBORDER=", 5))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_HEADBORDER] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PHTEXT=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_HEADTEXT] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PLAYOUT=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_LAYOUT] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PLINK=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_LINK] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PHLOCAL=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_HEADLOCAL] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PTEXT=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_TEXT] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PVLINK=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_VLINK] = cptr;
      return (true);
   }
   return (false);
}

/*****************************************************************************/
/*
Set the page layout and colouration.
*/

SetPageScheme ()

{
   int  size;
   char  *sptr;

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

   if (Debug) fprintf (stdout, "SetPageScheme()\n");

   if (!PageScheme[PS_LAYOUT])
      PageScheme[PS_LAYOUT] = "1";

   if (!PageScheme[PS_BACKGROUND])
      PageScheme[PS_BACKGROUND] = "";

   if (!PageScheme[PS_HEADLOCAL])
      PageScheme[PS_HEADLOCAL] = "";

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      if (!PageScheme[PS_BGCOLOR]) PageScheme[PS_BGCOLOR] = "";
      if (!PageScheme[PS_TEXT]) PageScheme[PS_TEXT] = "";
      if (!PageScheme[PS_LINK]) PageScheme[PS_LINK] = "";
      if (!PageScheme[PS_VLINK]) PageScheme[PS_VLINK] = "";
      if (!PageScheme[PS_HEADBGCOLOR]) PageScheme[PS_HEADBGCOLOR] = "";
      if (!PageScheme[PS_HEADBORDER]) PageScheme[PS_HEADBORDER] = "";
      if (!PageScheme[PS_HEADTEXT]) PageScheme[PS_HEADTEXT] = "";
      if (!PageScheme[PS_BUTTONBGCOLOR]) PageScheme[PS_BUTTONBGCOLOR] = "";
      if (!PageScheme[PS_BUTTONBORDER]) PageScheme[PS_BUTTONBORDER] = "";
   }
   else
   {
      if (!PageScheme[PS_BGCOLOR])
         PageScheme[PS_BGCOLOR] = DEFAULT_PS_BGCOLOR;
      if (!PageScheme[PS_TEXT])
         PageScheme[PS_TEXT] = DEFAULT_PS_TEXT;
      if (!PageScheme[PS_LINK])
         PageScheme[PS_LINK] = DEFAULT_PS_LINK;
      if (!PageScheme[PS_VLINK])
         PageScheme[PS_VLINK] = DEFAULT_PS_VLINK;
      if (!PageScheme[PS_HEADBGCOLOR])
         PageScheme[PS_HEADBGCOLOR] = DEFAULT_PS_HEADBGCOLOR;
      if (!PageScheme[PS_HEADBORDER])
         PageScheme[PS_HEADBORDER] = DEFAULT_PS_HEADBORDER;
      if (!PageScheme[PS_HEADTEXT])
         PageScheme[PS_HEADTEXT] = DEFAULT_PS_HEADTEXT;
      if (!PageScheme[PS_BUTTONBGCOLOR])
         PageScheme[PS_BUTTONBGCOLOR] = DEFAULT_PS_BUTTONBGCOLOR;
      if (!PageScheme[PS_BUTTONBORDER])
         PageScheme[PS_BUTTONBORDER] = DEFAULT_PS_BUTTONBORDER;
   }

   /* <BODY> tag attributes */
   size = strlen(PageScheme[PS_BACKGROUND]) +
          strlen(PageScheme[PS_BGCOLOR]) +
          strlen(PageScheme[PS_TEXT]) +
          strlen(PageScheme[PS_LINK]) +
          strlen(PageScheme[PS_VLINK]);
   if (size)
   {
      if (!(sptr = calloc (1, size+64))) exit (vaxc$errno);
      PageScheme[PS_BODYTAG] = sptr;
      if (PageScheme[PS_BACKGROUND][0])
         sptr += sprintf (sptr, " BACKGROUND=\"%s\"", PageScheme[PS_BACKGROUND]);
      if (PageScheme[PS_BGCOLOR][0])
         sptr += sprintf (sptr, " BGCOLOR=\"%s\"", PageScheme[PS_BGCOLOR]);
      if (PageScheme[PS_TEXT][0])
         sptr += sprintf (sptr, " TEXT=\"%s\"", PageScheme[PS_TEXT]);
      if (PageScheme[PS_LINK][0])
         sptr += sprintf (sptr, " LINK=\"%s\"", PageScheme[PS_LINK]);
      if (PageScheme[PS_VLINK][0])
         sptr += sprintf (sptr, " VLINK=\"%s\"", PageScheme[PS_VLINK]);
   }
   else
      PageScheme[PS_BODYTAG] = "";

   if (PageScheme[PS_HEADBGCOLOR][0])
   {
      if (!(sptr = calloc (1, strlen(PageScheme[PS_HEADBGCOLOR])+16)))
         exit (vaxc$errno);
      sprintf (sptr, " BGCOLOR=\"%s\"", PageScheme[PS_HEADBGCOLOR]);
      PageScheme[PS_HEADBGCOLOR] = sptr;
      PageScheme[PS_HEADPADDING] = "10";
   }
   else
      PageScheme[PS_HEADPADDING] = "0";

   if (PageScheme[PS_BUTTONBGCOLOR][0])
   {
      if (!(sptr = calloc (1, strlen(PageScheme[PS_BUTTONBGCOLOR])+16)))
         exit (vaxc$errno);
      sprintf (sptr, " BGCOLOR=\"%s\"", PageScheme[PS_BUTTONBGCOLOR]);
      PageScheme[PS_BUTTONBGCOLOR] = sptr;
   }
}

/*****************************************************************************/
/*
Provides a divider for top and bottom of the content of the page. This can be
a coloured bar (using <TABLE>) or a horizontal rule depending on the page
layout. "Buttons" providing script-internal and/or additional user-specified
links ('ButtonPtr' string) can be placed with(in) this bar. All button labels
are derived from 'ButtonPtr', with script-internal buttons using link-paths
set up via 'ButtonInternal[]' array, or any user-specified path depending on
requirement. An empty path (i.e. PathPtr[0] == '\0') obviously does not have a
link created, it just displays the button label. For a button-bar at the top
of the document use 1, bottom of the document use 2, and for just a bar with
no buttons at all use 0.
*/

ButtonBar (int Top1Bottom2)

{
#define MAX_BUTTON_COUNT 8

   static int  ButtonCount = -1;
   static char  *ButtonInternal [MAX_BUTTON_COUNT],
                *ButtonLabel [MAX_BUTTON_COUNT],
                *ButtonPath [MAX_BUTTON_COUNT];

   int  idx;
   char  *PathPtr;

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

   if (Debug) fprintf (stdout, "ButtonBar() %d\n", Top1Bottom2);

   if (ButtonCount == -1)
   {
      char  *cptr, *sptr;

      if (Debug) fprintf (stdout, "|%s|\n", ButtonPtr);
      cptr = ButtonPtr;
      for (ButtonCount = 0;
           ButtonCount < MAX_BUTTON_COUNT && *cptr;
           ButtonCount++)
      {
         for (sptr = cptr; *sptr && *sptr != '=' && *sptr != ';'; sptr++)
            if (*sptr == '\\') memcpy (sptr, sptr+1, strlen(sptr));
         if (*sptr == '=') *sptr++ = '\0';
         ButtonLabel[ButtonCount] = cptr;
         cptr = sptr;
         for (sptr = cptr; *sptr && *sptr != ';'; sptr++)
            if (*sptr == '\\') memcpy (sptr, sptr+1, strlen(sptr));
         if (*sptr) *sptr++ = '\0';
         ButtonPath[ButtonCount] = cptr;
         cptr = sptr;
      }
   }

   if (Top1Bottom2)
   {
      /***********************************/
      /* set up script-specified buttons */
      /***********************************/

      SCRIPT_SPECIFIC_BUTTON_CODE
   }

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      /************/
      /* format 2 */
      /************/

      if (Top1Bottom2 == 2 || !Top1Bottom2)
      {
         fprintf (stdout, "<HR ALIGN=left SIZE=2 WIDTH=95%%>\n");
         if (!Top1Bottom2) return;
      }

      fprintf (stdout, "<FONT SIZE=-1><NOBR>\n");
      for (idx = 0; idx < ButtonCount; idx++)
      {
         if (!ButtonInternal[idx])
            PathPtr = ButtonPath[idx];
         else
            PathPtr = ButtonInternal[idx];
         if (idx) fprintf (stdout, "&nbsp;");
         if (PathPtr[0])
            fprintf (stdout, "[<A HREF=\"%s\">%s</A>]\n",
                     PathPtr, ButtonLabel[idx]);
         else
            fprintf (stdout, "[%s]\n", ButtonLabel[idx]);
      }
      fprintf (stdout, "</NOBR></FONT>\n");

      if (Top1Bottom2 == 1)
         fprintf (stdout, "<HR ALIGN=left SIZE=2 WIDTH=95%%>\n");
   }
   else
   {
      /************/
      /* format 1 */
      /************/

      fprintf (stdout,
"<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0>\
<TR><TD HEIGHT=2></TD></TR>\
</TABLE>\n\
<TABLE BORDER=%s CELLPADDING=%s CELLSPACING=0 WIDTH=100%%>\n\
<TR><TD%s>\n",
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADBGCOLOR]);

      if (ButtonCount == 0 || !Top1Bottom2)
         fprintf (stdout, "&nbsp;");
      else
      {
         fprintf (stdout,
"<TABLE BORDER=%s CELLPADDING=1 CELLSPACING=0>\n",
            PageScheme[PS_BUTTONBORDER]);

         for (idx = 0; idx < ButtonCount; idx++)
         {
            if (!ButtonInternal[idx])
               PathPtr = ButtonPath[idx];
            else
               PathPtr = ButtonInternal[idx];
            if (PathPtr[0])
               fprintf (stdout,
"<TD ALIGN=center%s><FONT SIZE=-1>\
<NOBR>&nbsp;&nbsp;<A HREF=\"%s\">%s</A>&nbsp;&nbsp;</NOBR></FONT></TD>\n",
                  PageScheme[PS_BUTTONBGCOLOR], PathPtr, ButtonLabel[idx]);
            else
               fprintf (stdout,
"<TD ALIGN=center%s><FONT SIZE=-1>\
<NOBR>&nbsp;&nbsp;%s&nbsp;&nbsp;</NOBR></FONT></TD>\n",
                  PageScheme[PS_BUTTONBGCOLOR], ButtonLabel[idx]);
         }

         fprintf (stdout, "</TR></TABLE>\n");
      }

      fprintf (stdout,
"</TD></TR>\n\
</TABLE>\n\
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0>\
<TR><TD HEIGHT=2></TD></TR>\
</TABLE>\n");
   }
}

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

ConanTheLibrarian ()

{
   int  idx;
   unsigned long  UnixTime;
   char  *cptr, *sptr;
   struct tm  *UnixTmPtr;

   int  status;

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

   time (&UnixTime);
   UnixTmPtr = localtime (&UnixTime);
   if (!strftime (UnixDateTime, sizeof(UnixDateTime),
                  "%a, %d %b %Y %T", UnixTmPtr))
      strcpy (UnixDateTime, "[error]");
   if (Debug) fprintf (stdout, "UnixDateTime |%s|\n", UnixDateTime);

   /* initialize anything specifically required for CGIplus */
   DoHeaderInformation = DoListLibraries = DoLibraryMenu = DoSearch =
      KeyNameWildcard = ExplodeHelp = ExtractHelp = ListSingle =
      ErrorReported = false;
   DoSearchStatistics = true;
   for (idx = 0; idx < 11; idx++)
      KeyWord[idx][0] = KeyWordHtml[idx][0] = 
         KeyName[idx][0] = KeyNameHtml[idx][0] = '\0';

   CgiServerSoftwarePtr = CgiLibVar ("WWW_SERVER_SOFTWARE");
   CgiRequestMethodPtr = CgiLibVar ("WWW_REQUEST_METHOD");
   if (strcmp (CgiRequestMethodPtr, "GET"))
   {
      CgiLibResponseHeader (501, "text/html");
      fprintf (stdout, "Not implemented!\n");
      return;
   }

   CgiPathInfoPtr = CgiLibVar ("WWW_PATH_INFO");
   CgiPathTranslatedPtr = CgiLibVar ("WWW_PATH_TRANSLATED");
   CgiScriptNamePtr = CgiLibVar ("WWW_SCRIPT_NAME");
   CgiHttpPragmaPtr = CgiLibVar ("WWW_HTTP_PRAGMA");
   CgiHttpCacheControlPtr = CgiLibVar ("WWW_HTTP_CACHE_CONTROL");
   CgiHttpIfModifiedSincePtr = CgiLibVar ("WWW_HTTP_IF_MODIFIED_SINCE");

   CgiFormDoPtr = CgiLibVar ("WWW_FORM_DO");
   CgiFormExplodePtr = CgiLibVar ("WWW_FORM_EXPLODE");
   CgiFormExtractPtr = CgiLibVar ("WWW_FORM_EXTRACT");
   CgiFormSinglePtr = CgiLibVar ("WWW_FORM_SINGLE");
   CgiFormKeyPtr = CgiLibVar ("WWW_FORM_KEY");
   CgiFormTitlePtr = CgiLibVar ("WWW_FORM_TITLE");

   CgiFormSearchPtr = CgiLibVar ("WWW_FORM_SEARCH");
   if (!CgiFormSearchPtr[0])
      CgiFormSearchPtr = CgiLibVar ("WWW_KEY_1");

   CgiFormRefererPtr = CgiLibVar ("WWW_FORM_REFERER");
   if (!CgiFormRefererPtr[0])
      CgiFormRefererPtr = CgiLibVar ("WWW_HTTP_REFERER");

   if (CgiFormRefererPtr[0])
   {
      /* re-escape the URL-escaped percentages */
      CgiLibUrlEncode (CgiFormRefererPtr, -1, UriReferer, -1);
      CgiLibHtmlEscape (CgiFormRefererPtr, -1, HtmlReferer, -1);
   }

   if (VMSnok (status = TimeSetGmt ()))
   {
      if (status != SS$_NOLOGNAM)
      {
         CgiLibResponseError (FI_LI, status, CgiScriptNamePtr);
         exit (SS$_NORMAL);
      }
   }

   if (CgiHttpIfModifiedSincePtr[0])
   {
      if (VMSnok (HttpGmTime (CgiHttpIfModifiedSincePtr, 
                              &IfModifiedSinceBinaryTime)))
      {
         if (Debug) fprintf (stdout, "If-Modified-Since: NBG!\n");
         IfModifiedSinceBinaryTime[0] = IfModifiedSinceBinaryTime[0] = 0;
         CgiHttpIfModifiedSincePtr = "";
      }
   }

   /**************/
   /* initialize */
   /**************/

   idx = 0;
   if (CgiFormKeyPtr[0])
   {
      /*****************************************/
      /* get keywords from "key=" query string */
      /*****************************************/

      cptr = CgiFormKeyPtr;
      while (*cptr)
      {
         if (idx <= 10) idx++;
         if (*cptr == '~') cptr++;
         sptr = KeyWord[idx];
         while (*cptr && *cptr != '~') *sptr++ = *cptr++;
         *sptr = '\0';
         CgiLibHtmlEscape (KeyWord[idx], -1, KeyWordHtml[idx], -1);
         if (Debug)
            fprintf (stdout, "KeyWord[idx] |%s|%s|\n",
                     KeyWord[idx], KeyWordHtml[idx]);
      }
   }
   else
   if (!strstr (CgiPathTranslatedPtr, ".HLB") &&
       !strstr (CgiPathTranslatedPtr, ".TLB"))
   {
      /*******************************/
      /* get keywords from path info */
      /*******************************/

      /* make sure the default help library is used */
      CgiPathTranslatedPtr = "";

      idx = 0;
      /* by comparing, scan past the script component of the path */
      sptr = CgiScriptNamePtr;
      cptr = CgiPathInfoPtr;
      while (toupper(*cptr) == toupper(*sptr)) { cptr++; sptr++; }
      while (*cptr)
      {
         if (idx <= 10) idx++;
         if (*cptr == '/') cptr++;
         sptr = KeyWord[idx];
         if (*cptr == '_' || *cptr == '/')
         {
            /*
               Some other HTML help systems use an underscore for qualifiers.
               This implmentation also allows two concurrent forward-slashes
               so that the qualifier may be entered literally!
            */
            *sptr++ = '/';
            cptr++;
         }
         while (*cptr && *cptr != '/') *sptr++ = *cptr++;
         *sptr = '\0';
         CgiLibHtmlEscape (KeyWord[idx], -1, KeyWordHtml[idx], -1);
         if (Debug)
            fprintf (stdout, "KeyWord[idx] |%s|%s|\n",
                     KeyWord[idx], KeyWordHtml[idx]);
      }
      /* finished with the path if we've used it for keywords! */
      CgiPathInfoPtr = "";
   }            
   KeyWordCount = idx;
   while (idx++ < 10) KeyWord[idx][0] = KeyWordHtml[idx][0] = '\0';

   strcpy (LibraryPathInfo, CgiPathInfoPtr);

   /* isolate the directory component of the path */
   strcpy (LibraryDirPath, CgiPathInfoPtr);
   for (cptr = LibraryDirPath; *cptr; cptr++);
   if (cptr > LibraryDirPath) cptr--;
   while (cptr > LibraryDirPath && *cptr != '/') cptr--;
   if (*cptr == '/') cptr++;
   *cptr = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", LibraryDirPath);

   strcpy (LibrarySpec, CgiPathTranslatedPtr);
   for (sptr = LibrarySpec; *sptr && *sptr != '*' && *sptr != '%'; sptr++);
   if (*sptr) DoListLibraries = true;

   switch (toupper(CgiFormDoPtr[0]))
   {
      case 'H' :  DoHeaderInformation = true;  break;
      case 'L' :  DoListLibraries = true;  break;
      case 'M' :  DoLibraryMenu = true;  break;
      case 'S' :  DoSearch = true;  break;
      default :
         for (cptr = CgiPathTranslatedPtr; *cptr; cptr++)
            if (*cptr == '*' || *cptr == '%') DoListLibraries = true;
   }

   if (CgiFormExplodePtr[0])
      ExplodeHelp = true;
   else
      ExplodeHelp = false;

   if (CgiFormExtractPtr[0])
      ExtractHelp = true;
   else
      ExtractHelp = false;

   if (CgiFormSinglePtr[0] || CliListSingle)
      ListSingle = true;
   else
      ListSingle = false;

   if (CgiFormSearchPtr[0])
   {
      DoSearch = true;
      SearchStringLength = strlen(CgiFormSearchPtr);
      CgiLibHtmlEscape (CgiFormSearchPtr, -1, HtmlSearchString, -1);
   }
   if (DoSearch && !CgiFormSearchPtr[0])
   {
      CgiLibResponseError (FI_LI, 0, "Search string not supplied.");
      return;
   }

   if (!KeyWord[1][0])
   {
      /* no module keyname supplied, wildcard to list all modules */
      strcpy (KeyWord[1], "*");
      KeyNameWildcard = true;
   }
   else
   {
      /* check for a wildcard in the keyname, list all modules if there is */
      for (sptr = KeyWord[1]; *sptr && *sptr != '*' && *sptr != '%'; sptr++);
      if (*sptr) KeyNameWildcard = true;
   }

   LibraryTitle[0] = '\0';
   if (!CgiPathTranslatedPtr[0]) CgiFormTitlePtr = "VMS Help";
   if (CgiFormTitlePtr[0]) strcpy (LibraryTitle, CgiFormTitlePtr);
   if (LibraryTitle[0])
      CgiLibHtmlEscape (LibraryTitle, -1, HtmlLibraryTitle, -1);
   else
   {
      sprintf (LibraryTitle, "Library %s", LibraryPathInfo);
      CgiLibHtmlEscape (LibraryTitle, -1, HtmlLibraryTitle, -1);
   }
   if (Debug) fprintf (stdout, "LibraryTitle |%s|\n", LibraryTitle);

   CgiLibUrlEncode (LibraryTitle, -1, UriFormTitle, -1);
   CgiLibHtmlEscape (LibraryTitle, -1, HtmlFormTitle, -1);

   /***********/
   /* process */
   /***********/

   if (DoListLibraries)
      ListLibraries ();
   else
   if (DoLibraryMenu)
      LibraryMenu ();
   else
      Librarian ();
 }

/****************************************************************************/
/*
Search for library files according to the the supplied specification (defaults 
to help libraries; SYS$HELP:*.HLB).  Display the name of each library in lower 
case as a list item.
*/

ListLibraries ()

{
   boolean  SupportedLibraryType;
   int  len, status,
        FileCount = 0;
   char  *cptr, *sptr;
   char  ResFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         ExpFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         String [2048],
         UrlEncoded [ODS_MAX_FILE_NAME_LENGTH+64+1];
   struct FAB  FileFab;
#ifdef ODS_EXTENDED
   struct NAML  FileNaml;
#endif /* ODS_EXTENDED */
   struct NAM  FileNam;

   if (Debug) fprintf (stdout, "ListLibraries()\n");

   /* if no library specifcation provided then default to help libraries */
   if (!LibrarySpec[0]) strcpy (LibrarySpec, "SYS$HELP:*.HLB;0");

   FileFab = cc$rms_fab;
   FileFab.fab$l_fop = FAB$V_NAM;
   FileFab.fab$l_nam = &FileNam;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      FileFab.fab$l_fna = (char*)-1;
      FileFab.fab$b_fns = 0;
      FileFab.fab$l_nam = (struct namdef*)&FileNaml;

      ENAMEL_RMS_NAML(FileNaml)
      FileNaml.naml$l_long_filename = LibrarySpec;
      FileNaml.naml$l_long_filename_size = strlen(LibrarySpec);
      FileNaml.naml$l_long_expand = ExpFileName;
      FileNaml.naml$l_long_expand_alloc = sizeof(ExpFileName)-1;
      FileNaml.naml$l_long_result = ResFileName;
      FileNaml.naml$l_long_result_alloc = sizeof(ResFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      FileFab.fab$l_fna = LibrarySpec;
      FileFab.fab$b_fns = strlen(LibrarySpec);
      FileFab.fab$l_nam = &FileNam;

      FileNam = cc$rms_nam;
      FileNam.nam$l_esa = ExpFileName;
      FileNam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
      FileNam.nam$l_rsa = ResFileName;
      FileNam.nam$b_rss = ODS2_MAX_FILE_NAME_LENGTH;
   }

   if (VMSnok (status = sys$parse (&FileFab, 0, 0)))
   {
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   BeginPage (LibraryPathInfo, LibraryPathInfo);

   while (VMSok (status = sys$search (&FileFab, 0, 0)))
   {
      if (!FileCount++) fprintf (stdout, "<OL>\n");

      /* ignore directories if the file type includes them (numeric ".DIR") */
#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      if (strsame(FileNaml.naml$l_long_type+1, "DIR;", 4)) continue;
   }
   else
#endif /* ODS_EXTENDED */
      if (strsame(FileNam.nam$l_type+1, "DIR;", 4)) continue;

#ifdef ODS_EXTENDED
      if (OdsExtended)
      {
         if (strsame (FileNaml.naml$l_long_type+1, "HLB;", 4) ||
             strsame (FileNaml.naml$l_long_type+1, "TLB;", 4))
            SupportedLibraryType = true;
         else
            SupportedLibraryType = false;
      }
      else
#endif /* ODS_EXTENDED */
      {
         if (strsame (FileNam.nam$l_type+1, "HLB;", 4) ||
             strsame (FileNam.nam$l_type+1, "TLB;", 4))
            SupportedLibraryType = true;
         else
            SupportedLibraryType = false;
      }

      sptr = String;
      strcpy (sptr, "<LI>");
      sptr += 4;
      if (SupportedLibraryType)
      {
#ifdef ODS_EXTENDED
         if (OdsExtended)
         {
            CgiLibUrlEncode (LibraryDirPath, -1, UrlEncoded, -1);
            sptr += sprintf (sptr, "<A HREF=\"%s%s",
                             CgiScriptNamePtr, UrlEncoded);

            *(char*)FileNaml.naml$l_long_ver = '\0';
            for (cptr = FileNaml.naml$l_long_name; *cptr; *sptr++ = *cptr++);
            *(char*)FileNaml.naml$l_long_ver = ';';
         }
         else
#endif /* ODS_EXTENDED */
         {
            sptr += sprintf (sptr, "<A HREF=\"%s%s",
                             CgiScriptNamePtr, LibraryDirPath);

            *(char*)FileNam.nam$l_ver = '\0';
            for (cptr = FileNam.nam$l_name; *cptr; *sptr++ = tolower(*cptr++));
            *(char*)FileNam.nam$l_ver = ';';
         }
         sptr += sprintf (sptr, "?do=menu&title=%s&referer=%s\">",
                          UriFormTitle, UriReferer);
      }

      /* if there was wildcard in the file type then include it */
#ifdef ODS_EXTENDED
      if (OdsExtended)
      {
         if (FileNaml.naml$l_fnb & NAM$M_WILD_TYPE)
            *(char*)FileNaml.naml$l_long_ver = '\0';
         else
            *(char*)FileNaml.naml$l_long_type = '\0';
         cptr = (char*)FileNaml.naml$l_long_name;
         while (*cptr) *sptr++ = tolower(*cptr++);
         *sptr = '\0';
         if (FileNaml.naml$l_fnb & NAM$M_WILD_TYPE)
            *(char*)FileNaml.naml$l_long_ver = ';';
         else
            *(char*)FileNaml.naml$l_long_type = '.';
      }
      else
#endif /* ODS_EXTENDED */
      {
         if (FileNam.nam$l_fnb & NAM$M_WILD_TYPE)
            *(char*)FileNam.nam$l_ver = '\0';
         else
            *(char*)FileNam.nam$l_type = '\0';
         cptr = (char*)FileNam.nam$l_name;
         while (*cptr) *sptr++ = tolower(*cptr++);
         *sptr = '\0';
         if (FileNam.nam$l_fnb & NAM$M_WILD_TYPE)
            *(char*)FileNam.nam$l_ver = ';';
         else
            *(char*)FileNam.nam$l_type = '.';
      }

      if (SupportedLibraryType)
      {
         strcpy (sptr, "</A>");
         sptr += 4;
      }
      *sptr++ = '\n';
      *sptr = '\0';

      fprintf (stdout, "%s", String);
   }

   if (FileCount)
      fprintf (stdout, "</OL>\n");
   else
      fprintf (stdout, "<P>No files found.\n");

   if (status == RMS$_FNF || status == RMS$_NMF) status = SS$_NORMAL;
   if (VMSnok (status))
   {
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   if (ErrorReported) return (status);
   fprintf (stdout, "</BLOCKQUOTE>\n");
   ButtonBar (2);
   fprintf (stdout, "</BODY>\n</HTML>\n");
   return (status);
}

/*****************************************************************************/
/*
For the specified library file provide a menu of four services; librarian, 
searching, library header and help.
*/ 
 
LibraryMenu ()
 
{
   char  *cptr, *sptr;
   char  UrlEncoded [ODS_MAX_FILE_NAME_LENGTH+64+1];

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

   if (Debug) fprintf (stdout, "LibraryMenu() |%s|\n", LibrarySpec);

   strcpy (LibraryTitle, LibraryPathInfo);
   CgiLibHtmlEscape (LibraryPathInfo, -1, HtmlLibraryTitle, -1);
   CgiLibUrlEncode (LibraryTitle, -1, UriFormTitle, -1);
   CgiLibHtmlEscape (LibraryTitle, -1, HtmlFormTitle, -1);

   BeginPage (LibraryTitle, HtmlLibraryTitle);

   CgiLibUrlEncode (LibraryPathInfo, -1, UrlEncoded, -1);

   fprintf (stdout,
"<UL>\n\
<LI><A HREF=\"%s%s?title=%s&referer=%s\">Open Library</A>\n\
<LI><A HREF=\"%s%s?do=header&title=%s&referer=%s\">Library Header</A>\n\
</UL>\n\
<P><FORM ACTION=\"%s%s\">\n\
<INPUT TYPE=hidden NAME=\"referer\" VALUE=\"%s\">\n\
<INPUT TYPE=hidden NAME=\"title\" VALUE=\"%s\">\n\
<INPUT TYPE=hidden NAME=\"do\" VALUE=\"search\">\n\
&nbsp;<INPUT TYPE=\"submit\" VALUE=\"Search for: \">\n\
<INPUT TYPE=\"text\" NAME=\"search\">\n\
<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n\
</FORM>\n",
   CgiScriptNamePtr, UrlEncoded, UriFormTitle, UriReferer,
   CgiScriptNamePtr, UrlEncoded, UriFormTitle, UriReferer,
   CgiScriptNamePtr, UrlEncoded,
   HtmlReferer, HtmlFormTitle);

   if (ErrorReported) return;
   ButtonBar (2);
   fprintf (stdout, "</BODY>\n</HTML>\n");
}

/*****************************************************************************/
/*
Initialise library control.  Open the library specified by LibrarySpec.  Get 
header information from the library so its internal format can be determined 
(help or text, other are not supported).  Call ProcessLibrary() to perform the 
requested access according to the library type.
*/ 
 
Librarian ()

{
   int  status;
   unsigned long  Function = LBR$C_READ,
                  Length;
   char  *cptr;
   char  String [1024];
   $DESCRIPTOR (LibrarySpecDsc, "");
   static struct dsc$descriptor_s 
   LibraryDefDsc = { sizeof(DEFAULT_LIBRARY)-1, DSC$K_DTYPE_T,
                     DSC$K_CLASS_S, DEFAULT_LIBRARY },
   LibraryNameDsc = { sizeof(LibraryName)-1, DSC$K_DTYPE_T,
                      DSC$K_CLASS_S, LibraryName };

   /****************************************/
   /* initialize, open library, get header */
   /****************************************/

   if (Debug) fprintf (stdout, "Librarian()\n");

   if (VMSnok (status = lbr$ini_control (&LibraryIndex, &Function, 0, 0)))
   {
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }
 
   LibrarySpecDsc.dsc$w_length = strlen(LibrarySpec);
   LibrarySpecDsc.dsc$a_pointer = LibrarySpec;
 
   if (VMSnok (status = lbr$open (&LibraryIndex,
                                  &LibrarySpecDsc,
                                  0,
                                  &LibraryDefDsc,
                                  0,
                                  &LibraryNameDsc,
                                  &Length)))
   {
      if (Debug) fprintf (stdout, "lib$open() %%X%08.08X\n", status);
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }
 
   /* terminate, removing the trailing version number */
   LibraryName[Length] = '\0';
   for (cptr = LibraryName; *cptr && *cptr != ';'; cptr++);
   *cptr = '\0';
   if (Debug) fprintf (stdout, "LibraryName |%s|\n", LibraryName);

   if (VMSnok (status = lbr$get_header (&LibraryIndex, &LibraryHeader)))
   {
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   if (CgiHttpIfModifiedSincePtr[0])
   {
      if (VMSnok (ModifiedSince (&IfModifiedSinceBinaryTime,
                                 &LibraryHeader.lhi$l_updtim)))
      {
         /* library has not been modified since the date/time, don't send */
         status = lbr$close (&LibraryIndex);
         return (SS$_NORMAL);
      }
   }

   HttpGmTimeString (LastModifiedGmDateTime, &LibraryHeader.lhi$l_updtim);

   /*******************/
   /* process library */
   /*******************/

   if (DoHeaderInformation)
      HeaderInformation ();
   else
   if (LibraryHeader.lhi$l_type == LBR$C_TYP_TXT)
   {
      if (DoSearch)
         SearchTextLibraryModules ();
      else
      if (KeyNameWildcard)
         ListTextLibraryModules ();
      else
         OpenTextLibraryModule ();
   }
   else
   if (LibraryHeader.lhi$l_type == LBR$C_TYP_HLP)
   {
      if (DoSearch)
         SearchHelpLibraryModules ();
      else
      if (KeyNameWildcard)
         ListHelpLibraryModules ();
      else
         OpenHelpLibraryModule ();
   }
   else
   {
      sprintf (String,
      "Library type %d not supported. (Only HELP and TEXT libraries are valid)",
      LibraryHeader.lhi$l_type);
      CgiLibResponseError (FI_LI, 0, String);
      return (SS$_NORMAL);
   }
 
   status = lbr$close (&LibraryIndex);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Provide selected library header information.
*/ 
 
HeaderInformation ()
 
{
   static char  *LibraryTypes[] =
          { "Unknown", "VAX Object", "Macro", "Help", "Text",
            "VAX Sharable Image", "NCS",
            "Alpha Object", "Alpha Sharable Image", "?" };
   static  $DESCRIPTOR (CreatedFaoDsc,
           "<TR><TH>Created:</TH><TD>!%D</TD></TR>\n");
   static  $DESCRIPTOR (RevisedFaoDsc,
           "<TR><TH>Revised:</TH><TD>!%D</TD></TR>\n");

   unsigned short  Length;
   char  String [1024];
   $DESCRIPTOR (StringDsc, String);

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

   BeginPage (LibraryTitle, LibraryTitle);

   fprintf (stdout,
"<BLOCKQUOTE>\n\
<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=0>\n");

   if (LibraryHeader.lhi$l_type > 8) LibraryHeader.lhi$l_type = 9;
   fprintf (stdout, "<TR><TH ALIGN=right>Type:</TH><TD>%s</TD></TR>\n",
      LibraryTypes[LibraryHeader.lhi$l_type]);

   fprintf (stdout, "<TR><TH ALIGN=right>Creator:</TH><TD>%-*s</TD></TR>\n",
      *(char*)LibraryHeader.lhi$t_lbrver, (char*)LibraryHeader.lhi$t_lbrver+1);

   fprintf (stdout, "<TR><TH ALIGN=right>Format:</TH><TD>%d.%d</TD></TR>\n",
      LibraryHeader.lhi$l_majorid, LibraryHeader.lhi$l_minorid);

   sys$fao (&CreatedFaoDsc, &Length, &StringDsc, &LibraryHeader.lhi$l_credat);
   String[Length] = '\0';
   fprintf (stdout, "%s", String);

   sys$fao (&RevisedFaoDsc, &Length, &StringDsc, &LibraryHeader.lhi$l_updtim);
   String[Length] = '\0';
   fprintf (stdout, "%s", String);

   if (LibraryHeader.lhi$l_libstatus)
      fprintf (stdout,
"<TR><TH ALIGN=right>Status:</TH><TD>OK</TD></TR>\n");
   else
      fprintf (stdout,
"<TR><TH ALIGN=right>Status:</TH>\
<TD><FONT COLOR=\"#ff0000\">PROBLEM</FONT></TD></TR>\n");

   fprintf (stdout, "</TABLE>\n");

   if (ErrorReported) return;
   fprintf (stdout, "</BLOCKQUOTE>\n");
   ButtonBar (2);
   fprintf (stdout, "</BODY>\n</HTML>\n");
}

/*****************************************************************************/
/*
*/
 
int ListHelpLibraryModules ()

{
   int  status;
   char  *cptr, *sptr;
   char  KeyName [256],
         String [1024];
   $DESCRIPTOR (KeyNameDsc, KeyName);
 
   if (Debug) fprintf (stdout, "ListHelpLibraryModules()\n");
 
   cptr = KeyName;
   for (sptr = KeyWord[1]; *sptr; *cptr++ = toupper(*sptr++));
   *cptr = '\0';
   KeyNameDsc.dsc$w_length = cptr - KeyName;

   BeginPage (LibraryTitle, HtmlLibraryTitle);

   fprintf (stdout,
"<P><FORM ACTION=\"%s%s\">\n\
<INPUT TYPE=hidden NAME=\"referer\" VALUE=\"%s\">\n\
<INPUT TYPE=hidden NAME=\"title\" VALUE=\"%s\">\n\
<INPUT TYPE=hidden NAME=\"do\" VALUE=\"search\">\n\
&nbsp;<INPUT TYPE=\"submit\" VALUE=\"Search for: \">\n\
<INPUT TYPE=\"text\" NAME=\"search\">\n\
<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n\
</FORM>\n\
<P><BLOCKQUOTE>\n",
   CgiScriptNamePtr, CgiPathInfoPtr,
   HtmlReferer, HtmlFormTitle);

   fflush (stdout);

   IndexNumber = 1;

   if (VMSnok (status = lbr$get_index (&LibraryIndex,
                                       &IndexNumber,
                                       &ListHelpModule,
                                       &KeyNameDsc)))
   {
      if (Debug) fprintf (stdout, "lib$get_index() %%X%08.08X\n", status);
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   fprintf (stdout, "\n");

   if (ErrorReported) return (status);
   fprintf (stdout, "</BLOCKQUOTE>\n");
   ButtonBar (2);
   fprintf (stdout, "</BODY>\n</HTML>\n");
}
 
/*****************************************************************************/
/*
*/ 
 
int ListHelpModule
(
struct dsc$descriptor_s  *KeyNameDscPtr,
void  *RFAptr
)
{
   static char  PreviousAlphabetic = 0,
                PreviousCharacter = 0xff;

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

   if (Debug) fprintf (stdout, "ListHelpModule()\n");

   strncpy (KeyWord[1],
            KeyNameDscPtr->dsc$a_pointer, KeyNameDscPtr->dsc$w_length);
   KeyWord[1][KeyNameDscPtr->dsc$w_length] = '\0';
   strcpy (KeyName[1], KeyWord[1]);
   CgiLibHtmlEscape (KeyWord[1], -1, KeyNameHtml[1], -1);

   if (Debug) fprintf (stdout, "KeyNameDsc |%s|\n", KeyWord[1]);

   if (ListSingle)
   {
      if ((unsigned char)KeyName[1][0] >= (unsigned char)PreviousCharacter)
         fprintf (stdout, "\n<LI>");
      else
         fprintf (stdout, "<LI>");
   }
   else
   if (!isalpha(KeyName[1][0]) ||
       toupper(KeyName[1][0]) == PreviousAlphabetic)
   {
      if ((unsigned char)KeyName[1][0] >= (unsigned char)PreviousCharacter)
         fprintf (stdout, ",\n");
   }
   else
   {
      if ((unsigned char)KeyName[1][0] >= (unsigned char)PreviousCharacter)
         fprintf (stdout, "\n<P>");
      PreviousAlphabetic = toupper(KeyName[1][0]);
   }

   fprintf (stdout, "<A HREF=\"%s%s?key=%s&title=%s&referer=%s\">%s</A>",
            CgiScriptNamePtr, LibraryPathInfo,
            FormatRequestKeys(true,1),
            UriFormTitle, UriReferer,
            MungeUnderScores(KeyNameHtml[1]));

   PreviousCharacter = KeyName[1][0];

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
*/
 
int OpenHelpLibraryModule ()

{
   int  status;
   char  *cptr, *sptr;
   char  KeyName [256],
         String [512];
   $DESCRIPTOR (KeyNameDsc, KeyName);
 
   if (Debug) fprintf (stdout, "OpenHelpLibraryModule()\n");
 
   cptr = KeyName;
   for (sptr = KeyWord[1]; *sptr; *cptr++ = toupper(*sptr++));
   *cptr = '\0';
   KeyNameDsc.dsc$w_length = cptr - KeyName;
   
   IndexNumber = 1;

   if (ExtractHelp)
   {
      status = lbr$get_index (&LibraryIndex, &IndexNumber,
                              &ExtractHelpModule, &KeyNameDsc);
      if (VMSnok (status))
      {
         if (Debug) fprintf (stdout, "lib$get_index() %%X%08.08X\n", status);
         CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      }
      return (SS$_NORMAL);
   }

   status = lbr$get_index (&LibraryIndex, &IndexNumber,
                           &OpenHelpModule, &KeyNameDsc);
   if (VMSnok (status))
   {
      if (Debug) fprintf (stdout, "lib$get_index() %%X%08.08X\n", status);
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   if (!ErrorReported) ButtonBar (2);
   fprintf (stdout, "</BODY>\n</HTML>\n");
   return (SS$_NORMAL);
}
 
/*****************************************************************************/
/*
This is a reasonably complex function, and reflects the complexities internal 
to help library modules!
*/ 
 
int OpenHelpModule
(
struct dsc$descriptor_s  *KeyNameDscPtr,
void  *RFAptr
)
{
   static int  MatchedCount = 0,
               PreviousCgiPlusUsageCount = 0;

   boolean  AdditionalInformation = true,
            ExplodedHeading = false,
            OutputModuleTitle = true;
   int  status,
        AdditionalInformationCount = 0,
        BlankLineCount = 0,
        Count,
        ExplodeLevel = 0,
        HelpLevel = 0,
        Length,
        MatchedLevel = 0,
        MatchesToLevel = 0,
        PreviousHelpLevel = 0,
        TextLineCount = 0;
   int  KeyCount [11];
   char  PreviousFirstBufferChar = '\0';
   char  *cptr, *sptr;
   char  Buffer [1024],
         String [1024];
   $DESCRIPTOR (InBufferDsc, Buffer);
   $DESCRIPTOR (OutBufferDsc, Buffer);
 
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "OpenHelpModule()\n");

   if (CgiPlusUsageCount != PreviousCgiPlusUsageCount)
   {
      MatchedCount = 0;
      PreviousCgiPlusUsageCount = CgiPlusUsageCount;
   }

   strncpy (KeyName[1],
            KeyNameDscPtr->dsc$a_pointer, KeyNameDscPtr->dsc$w_length);
   KeyName[1][KeyNameDscPtr->dsc$w_length] = '\0';

   if (Debug) fprintf (stdout, "KeyNameDsc |%s|\n", KeyName[1]);

   CgiLibHtmlEscape (KeyName[1], -1, KeyWordHtml[1], -1);
   CgiLibHtmlEscape (KeyName[1], -1, KeyNameHtml[1], -1);
   if (!KeyWordCount)
   {
      strcpy (KeyWord[1], KeyName[1]);
      KeyWordCount = 1;
   }

   if (ExplodeHelp)
      for (Count = 0; Count < 11; KeyCount[Count++] = 0);

   if (VMSnok (status = lbr$lookup_key (&LibraryIndex,
                                        KeyNameDscPtr,
                                        &ModuleRFA)))
   {
      if (Debug) fprintf (stdout, "lib$lookup_key() %%X%08.08X\n", status);
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   /**************************************/
   /* loop through all records in module */
   /**************************************/

   for (;;)
   {
      status = lbr$get_record (&LibraryIndex,
                               &InBufferDsc,
                               &OutBufferDsc);
      if (Debug) fprintf (stdout, "lbr$get_record() %%X%08.08X\n", status);
      if (VMSnok (status)) break;
 
      /* terminate output buffer, removing any trailing white space */
      cptr = Buffer+OutBufferDsc.dsc$w_length-1;
      while (cptr >= Buffer && isspace(*cptr)) cptr--;
      *++cptr = '\0';
 
      /* comment record (line) in module */
      if (Buffer[0] == '!') continue;

      if (Debug) fprintf (stdout, "Buffer |%s|\n", Buffer);

      if ((isdigit(Buffer[0]) && Buffer[1] == ' ') || Buffer[0] == '/')
      {
         /***************************/
         /* help level or qualifier */
         /***************************/
 
         PreviousHelpLevel = HelpLevel;
         if (Buffer[0] == '/')
         {
            /* if no previous qualifier encountered at this help level */
            /* then the qualifier effectively creates a new help level */
            if (PreviousFirstBufferChar != '/') HelpLevel++;
            cptr = Buffer;
            for (sptr = cptr; *sptr && !isspace(*sptr); sptr++);
         }
         else
         {
            /* help level topic (line begins with a digit then a space) */
            HelpLevel = Buffer[0] - '0';
            for (cptr = Buffer + 1; isspace(*cptr); cptr++);
            for (sptr = cptr; *sptr && !isspace(*sptr); sptr++);
         }

         strncpy (KeyName[HelpLevel], cptr, sptr-cptr);
         KeyName[HelpLevel][sptr-cptr] = '\0';
         CgiLibHtmlEscape (cptr, -1, KeyNameHtml[HelpLevel], -1);
         PreviousFirstBufferChar = Buffer[0];

         if (MatchesToLevel >= HelpLevel)
         {
            /* if the topic's been matched and output, finish up */
            if (MatchedLevel) break;

            MatchesToLevel = ExplodeLevel = 0;
            for (Count = 1; Count <= HelpLevel; Count++)
            {
               if (strsame (KeyWord[Count], KeyName[Count], -1))
                  MatchesToLevel = Count;
               else
                  break;
            }
         }
         else
         {
            for (Count = MatchesToLevel + 1; Count <= HelpLevel; Count++)
            {
               if (strsame (KeyWord[Count], KeyName[Count], -1))
                  MatchesToLevel = Count;
               else
                  break;
            }
         }

         if (MatchesToLevel == KeyWordCount && MatchesToLevel == HelpLevel)
         {
            /****************/
            /* module title */
            /****************/

            MatchedCount++;

            sptr = String;
            sptr += sprintf (sptr, "%s<BR>\n", LibraryTitle);
            for (Count = 1; Count <= HelpLevel; Count++)
            {
               if (Count == 1)
                  sptr += sprintf (sptr, "%s",
                             MungeUnderScores(KeyNameHtml[Count]));
               else
                  sptr += sprintf (sptr, ", %s",
                             MungeUnderScores(KeyNameHtml[Count]));
            }

            BeginPage (LibraryTitle, String);

            if (ExplodeHelp)
            {
               ExplodedHeading = true;
               ExplodeLevel = HelpLevel + 1;
            }

            MatchedLevel = HelpLevel;
         }
         else
         if (ExplodeLevel && HelpLevel >= ExplodeLevel)
         {
            /******************/
            /* exploding help */
            /******************/

            if (TextLineCount)
               if (PreviousHelpLevel == ExplodeLevel - 1)
                  fprintf (stdout, "</PRE>\n<P>");
               else
                  fprintf (stdout, "</PRE>\n");
            TextLineCount = 0;

            ExplodedHeading = true;
            for (Count = HelpLevel+1; Count < 11; KeyCount[Count++] = 0);
            KeyCount[HelpLevel]++;

            if (ExplodeLevel < HelpLevel ||
                KeyName[HelpLevel][0] == '/')
               fprintf (stdout, "<P><FONT><B>");
            else
            if (PageScheme[PS_LAYOUT][0] == '2')
               fprintf (stdout, "<P><FONT><B><U>");
            else
               fprintf (stdout,
"<P>\
<TABLE BORDER=%s CELLPADDING=2 CELLSPACING=0 WIDTH=95%%>\n\
<TR><TD%s>&nbsp;\n\
<FONT COLOR=\"%s\"><B>\n",
                  PageScheme[PS_HEADBORDER],
                  PageScheme[PS_HEADBGCOLOR],
                  PageScheme[PS_HEADTEXT]);

            for (Count = ExplodeLevel; Count <= HelpLevel; Count++)
            {
               if (Count < HelpLevel)
                  fprintf (stdout, "&nbsp;%d.", KeyCount[Count]);
               else
                  fprintf (stdout, "%d", KeyCount[Count]);
            }

            if (ExplodeLevel < HelpLevel ||
                KeyName[HelpLevel][0] == '/')
            {
               fprintf (stdout, " - %s</B></FONT>\n",
                  MungeUnderScores(KeyNameHtml[HelpLevel]));
            }
            else
            if (PageScheme[PS_LAYOUT][0] == '2')
            {
               fprintf (stdout, " - %s</U></B></FONT>\n",
                  MungeUnderScores(KeyNameHtml[HelpLevel]));
            }
            else
            {
               fprintf (stdout,
" - %s\n\
</B></FONT>\n\
</TD></TR>\n\
</TABLE>\n",
                  MungeUnderScores(KeyNameHtml[HelpLevel]));
            }
         }
         else
         if (MatchesToLevel == KeyWordCount && HelpLevel == KeyWordCount+1)
         {
            /*************/
            /* subtopics */
            /*************/

            if (AdditionalInformation)
            {
               if (TextLineCount) fprintf (stdout, "</PRE>\n");
               if (HelpLevel == 2)
               {
                  fprintf (stdout,
"<P><FORM ACTION=\"%s%s\">\n\
<INPUT TYPE=hidden NAME=\"referer\" VALUE=\"%s\">\n\
<INPUT TYPE=hidden NAME=\"title\" VALUE=\"%s\">\n\
<INPUT TYPE=hidden NAME=\"do\" VALUE=\"search\">\n\
<INPUT TYPE=hidden NAME=\"key\" VALUE=\"%s\">\n\
&nbsp;<INPUT TYPE=\"submit\" VALUE=\"Search &quot;%s&quot; for: \">\n\
<INPUT TYPE=\"text\" NAME=\"search\">\n\
<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n\
</FORM>\n",
                  CgiScriptNamePtr, CgiPathInfoPtr,
                  HtmlReferer, HtmlFormTitle,
                  FormatRequestKeys(false,HelpLevel-1),
                  KeyWordHtml[1]);
               }
               fprintf (stdout,
"<P>\
&nbsp;&nbsp;<B>Additional Information \
<I>(<A HREF=\"%s%s?key=%s&explode=yes&title=%s&referer=%s\">explod</A>\
<A HREF=\"%s%s?key=%s&extract=yes\">e</A>)</I> \
:</B>\n\
<P>\
<UL>\n",
                  CgiScriptNamePtr, LibraryPathInfo,
                  FormatRequestKeys(true,HelpLevel-1),
                  UriFormTitle, UriReferer,
                  CgiScriptNamePtr, LibraryPathInfo,
                  FormatRequestKeys(true,HelpLevel-1));
               AdditionalInformation = false;
            }
            else
            {
               if (TextLineCount) fprintf (stdout, "</PRE>\n");
            }

            fprintf (stdout,
               "<LI><A HREF=\"%s%s?key=%s&title=%s&referer=%s\">%s</A>\n",
               CgiScriptNamePtr, LibraryPathInfo,
               FormatRequestKeys(true,HelpLevel),
               UriFormTitle, UriReferer,
               MungeUnderScores(KeyNameHtml[HelpLevel]));
            AdditionalInformationCount++;
            TextLineCount = 0;
         }
      }
      else
      {
         /*************/
         /* help text */
         /*************/
 
         if ((ExplodeLevel && HelpLevel >= ExplodeLevel) || 
             (MatchesToLevel == KeyWordCount && HelpLevel == KeyWordCount))
         {
            /* check to see if this module record has any non-space chars */
            for (cptr = Buffer;
                 *cptr && (*cptr == ' ' || *cptr == '\t');
                 cptr++);
            /* ignore special directive for DECwindows on-line help */
            if (*cptr && *cptr != '=')
            {
               if (!TextLineCount) fprintf (stdout, "<P><PRE>");
               sptr = String;
               if (ExplodedHeading)
               {
                  ExplodedHeading = false;
                  BlankLineCount = 0;
               }
               if (BlankLineCount && TextLineCount)
               {
                  /** *sptr++ = '\r'; **/
                  *sptr++ = '\n';
               }
               if (BlankLineCount) BlankLineCount = 0;
               *sptr++ = ' ';
               cptr = Buffer;
               while (*cptr) *sptr++ = *cptr++;
               *sptr++ = '\n';
               *sptr = '\0';
               HtmlOutputString (String, true);
               TextLineCount++;
            }
            else
            if (!*cptr)
               BlankLineCount++;
         }
      }
   }
 
   if (AdditionalInformationCount)
      fprintf (stdout, "</UL>\n");
   else
   if (TextLineCount)
      fprintf (stdout, "</PRE>\n");
   else
   if (!MatchedCount)
      return (0x00268838);

   if (ErrorReported) return (status);
   return (SS$_NORMAL);
}
 
/*****************************************************************************/
/*
Just suck the brains out of the module as a plain-text document.
*/ 
 
int ExtractHelpModule
(
struct dsc$descriptor_s  *KeyNameDscPtr,
void  *RFAptr
)
{
   int  status;
   char  Buffer [1024];
   $DESCRIPTOR (InBufferDsc, Buffer);
   $DESCRIPTOR (OutBufferDsc, Buffer);
 
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "ExtractHelpModule()\n");

   if (VMSnok (status = lbr$lookup_key (&LibraryIndex,
                                        KeyNameDscPtr,
                                        &ModuleRFA)))
   {
      if (Debug) fprintf (stdout, "lib$lookup_key() %%X%08.08X\n", status);
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   /**************************************/
   /* loop through all records in module */
   /**************************************/

   CgiLibResponseHeader (200, "text/plain",
                         "Last-Modified: %s\n", LastModifiedGmDateTime);

   for (;;)
   {
      status = lbr$get_record (&LibraryIndex,
                               &InBufferDsc,
                               &OutBufferDsc);
      if (Debug) fprintf (stdout, "lbr$get_record() %%X%08.08X\n", status);
      if (VMSnok (status)) break;
 
      Buffer[OutBufferDsc.dsc$w_length] = '\n';
      Buffer[OutBufferDsc.dsc$w_length+1] = '\0';
      fputs (Buffer, stdout);
   }
 
   return (SS$_NORMAL);
}
 
/*****************************************************************************/
/*
*/
 
int SearchHelpLibraryModules ()

{
   int  status;
   char  *cptr, *sptr;
   char  KeyName [256],
         String [1024];
   $DESCRIPTOR (KeyNameDsc, KeyName);
 
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "SearchHelpLibraryModules()\n");
 
   cptr = KeyName;
   for (sptr = KeyWord[1]; *sptr; *cptr++ = toupper(*sptr++));
   *cptr = '\0';
   KeyNameDsc.dsc$w_length = cptr - KeyName;
   
   sprintf (String, "Search &quot;%s&quot; for &quot;%s&quot;\n",
            HtmlLibraryTitle, HtmlSearchString);

   BeginPage (String, String);

   fprintf (stdout, "<BLOCKQUOTE>\n");

   RecordsSearched = SearchHitCount = 0;
   IndexNumber = 1;

   if (DoSearchStatistics) lib$init_timer (0);

   if (VMSnok (status = lbr$get_index (&LibraryIndex,
                                       &IndexNumber,
                                       &SearchHelpModule,
                                       &KeyNameDsc)))
   {
      if (Debug) fprintf (stdout, "lib$get_index() %%X%08.08X\n", status);
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   if (!SearchHitCount) fprintf (stdout, "<B>Not found!</B>\n<P>");

   if (DoSearchStatistics) SearchStatistics ();

   fprintf (stdout, "</BLOCKQUOTE>\n");
   ButtonBar (2);
   fprintf (stdout, "</BODY>\n</HTML>\n");
}

/*****************************************************************************/
/*
This is a reasonably complex function, and reflects the complexities internal 
to help library modules!
*/ 
 
int SearchHelpModule
(
struct dsc$descriptor_s  *KeyNameDscPtr,
void  *RFAptr
)
{
   static boolean  HitThisTopic = false;
   static char  PreviousFirstBufferChar = '\0';

   int  status,
        Count,
        HelpLevel = 0,
        Length;
   char  ch;
   char  *cptr, *hptr, *sptr;
   char  Buffer [1024],
         String [8192];
   $DESCRIPTOR (InBufferDsc, Buffer);
   $DESCRIPTOR (OutBufferDsc, Buffer);
 
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "SearchHelpModule()\n");

   strncpy (KeyWord[1],
            KeyNameDscPtr->dsc$a_pointer, KeyNameDscPtr->dsc$w_length);
   KeyWord[1][KeyNameDscPtr->dsc$w_length] = '\0';
   strcpy (KeyName[1], KeyWord[1]);
   CgiLibHtmlEscape (KeyWord[1], -1, KeyWordHtml[1], -1);
   CgiLibHtmlEscape (KeyWord[1], -1, KeyNameHtml[1], -1);
   if (!KeyWordCount) KeyWordCount = 1;

   if (Debug) fprintf (stdout, "KeyNameDsc |%s|\n", KeyWord[1]);

   if (VMSnok (status = lbr$lookup_key (&LibraryIndex,
                                        KeyNameDscPtr,
                                        &ModuleRFA)))
   {
      if (Debug) fprintf (stdout, "lib$lookup_key() %%X%08.08X\n", status);
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   /**************************************/
   /* loop through all records in module */
   /**************************************/

   for (;;)
   {
      status = lbr$get_record (&LibraryIndex,
                               &InBufferDsc,
                               &OutBufferDsc);
      if (Debug) fprintf (stdout, "lbr$get_record() %%X%08.08X\n", status);
      if (VMSnok (status)) break;
 
      /* terminate output buffer, removing any trailing white space */
      cptr = Buffer+OutBufferDsc.dsc$w_length-1;
      while (cptr >= Buffer && isspace(*cptr)) cptr--;
      *++cptr = '\0';
 
      /* comment record (line) in module */
      if (Buffer[0] == '!') continue;

      if (Debug) fprintf (stdout, "Buffer |%s|\n", Buffer);

      if ((isdigit (Buffer[0]) && Buffer[1] == ' ') || Buffer[0] == '/')
      {
         /***************************/
         /* help level or qualifier */
         /***************************/
 
         if (Buffer[0] == '/')
         {
            /* if no previous qualifier encountered at this help level */
            /* then the qualifier effectively creates a new help level */
            if (PreviousFirstBufferChar != '/') HelpLevel++;
            cptr = Buffer;
         }
         else
         {
            /* help level topic (line begins with a digit then a space) */
            HelpLevel = Buffer[0] - '0';
            for (cptr = Buffer + 1; isspace(*cptr); cptr++);
         }

         PreviousFirstBufferChar = Buffer[0];
         strcpy (KeyName[HelpLevel], cptr);
         CgiLibHtmlEscape (cptr, -1, KeyNameHtml[HelpLevel], -1);

         /* if previous topic had a hit then end the list of the hit(s) */
         if (HitThisTopic)
         {
            fprintf (stdout, "</UL>\n");
            fflush (stdout);
         }
         HitThisTopic = false;
      }
      else
      {
         /*************/
         /* help text */
         /*************/
 
         /* check to see if this module record has any non-space chars */
         for (cptr = Buffer; *cptr && isspace(*cptr); cptr++);
         if (*cptr)
         {
            if (*(hptr = SearchText (cptr, CgiFormSearchPtr, false)))
            {
               /********/
               /* hit! */
               /********/

               if (Debug) fprintf (stdout, "Hit |%s|\n", hptr);
               SearchHitCount++;
               sptr = String;

               if (!HitThisTopic)
               {
                  strcpy (sptr, "<B>");
                  sptr += 3;
                  for (Count = 1; Count <= HelpLevel; Count++)
                  {
                     if (Count > 1) { *sptr++ = ','; *sptr++ = ' '; }
                     sptr += sprintf (sptr,
                        "<A HREF=\"%s%s?key=%s&title=%s&referer=%s\">%s</A>",
                        CgiScriptNamePtr, LibraryPathInfo,
                        FormatRequestKeys(true,Count),
                        UriFormTitle, UriReferer,
                        MungeUnderScores(KeyNameHtml[Count]));
                  }
                  strcpy (sptr, "</B>\n<UL>\n<LI>");
                  sptr += 14;
                  HitThisTopic = true;
               }
               else
               {
                  /* same topic, new hit */
                  strcpy (sptr, "<LI>");
                  sptr += 4;
               }
               ch = cptr[hptr-cptr];
               cptr[hptr-cptr] = '\0';
               sptr += CgiLibHtmlEscape (cptr, -1, sptr, -1);
               cptr[hptr-cptr] = ch;
               strcpy (sptr, "<B>");
               sptr += 3;
               strncpy (sptr, hptr, SearchStringLength);
               sptr += SearchStringLength;
               strcpy (sptr, "</B>");
               sptr += 4;
               sptr += CgiLibHtmlEscape (hptr+SearchStringLength, -1, sptr, -1);
               *sptr++ = '\n';
               *sptr = '\0';

               fprintf (stdout, "%s", String);
            }
         }
      }
   }

   if (HitThisTopic)
   {
      fprintf (stdout, "</UL>\n");
      fflush (stdout);
      HitThisTopic = false;
   }

   return (SS$_NORMAL);
}
 
/*****************************************************************************/
/*
String together the keys associated with this help item.
*/ 
 
char* FormatRequestKeys
(
boolean ForUri,
int ToLevel
)
{
   static char  RequestKeys [256];

   int  idx;
   char  *sptr;

   sptr = RequestKeys;
   for (idx = 1; idx <= ToLevel; idx++)
   {
      if (ForUri)
         sptr += CgiLibUrlEncode (KeyName[idx], -1, sptr, -1);
      else
         sptr += CgiLibHtmlEscape (KeyName[idx], -1, sptr, -1);
      *sptr++ = '~';
   }
   /* if needed, get rid of that last '~'! */
   if (idx > 1)
      sptr[-1] = '\0';
   else
      *sptr = '\0';

   return (RequestKeys);
}
 
/*****************************************************************************/
/*
*/
 
int ListTextLibraryModules ()

{
   int  status;
   char  *cptr, *sptr;
   char  KeyName [256],
         String [1024];
   $DESCRIPTOR (KeyNameDsc, KeyName);
 
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "ListTextLibraryModules()\n");
 
   cptr = KeyName;
   for (sptr = KeyWord[1]; *sptr; *cptr++ = toupper(*sptr++));
   *cptr = '\0';
   KeyNameDsc.dsc$w_length = cptr - KeyName;
   
   BeginPage (LibraryTitle, HtmlLibraryTitle);

   fprintf (stdout,
"<P><FORM ACTION=\"%s%s\">\n\
<INPUT TYPE=hidden NAME=\"referer\" VALUE=\"%s\">\n\
<INPUT TYPE=hidden NAME=\"title\" VALUE=\"%s\">\n\
<INPUT TYPE=hidden NAME=\"do\" VALUE=\"search\">\n\
&nbsp;<INPUT TYPE=\"submit\" VALUE=\"Search for: \">\n\
<INPUT TYPE=\"text\" NAME=\"search\">\n\
<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n\
</FORM>\n\
<P><BLOCKQUOTE>\n",
      CgiScriptNamePtr, CgiPathInfoPtr,
      HtmlReferer, HtmlFormTitle);

   IndexNumber = 1;

   if (VMSnok (status = lbr$get_index (&LibraryIndex,
                                       &IndexNumber,
                                       &ListTextModule,
                                       &KeyNameDsc)))
   {
      if (Debug) fprintf (stdout, "lib$get_index() %%X%08.08X\n", status);
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   if (ErrorReported) return (status);
   fprintf (stdout, "</BLOCKQUOTE>\n");
   ButtonBar (2);
   fprintf (stdout, "</BODY>\n</HTML>\n");
}
 
/*****************************************************************************/
/*
*/ 
 
int ListTextModule
(
struct dsc$descriptor_s  *KeyNameDscPtr,
void  *RFAptr
)
{
   static int  PreviousCgiPlusUsageCount = 0;
   static char  PreviousAlphabetic = '\0';

   char  String [1024],
         UriKeyName [256];
 
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "ListTextModule()\n");

   strncpy (KeyWord[1],
            KeyNameDscPtr->dsc$a_pointer,
            KeyNameDscPtr->dsc$w_length);
   KeyWord[1][KeyNameDscPtr->dsc$w_length] = '\0';
   strcpy (KeyName[1], KeyWord[1]);
   CgiLibUrlEncode (KeyWord[1], -1, UriKeyName, -1);
   CgiLibHtmlEscape (KeyWord[1], -1, KeyNameHtml[1], -1);

   if (Debug) fprintf (stdout, "KeyNameDsc |%s|\n", KeyWord[1]);

   if (ListSingle)
   {
      if (CgiPlusUsageCount == PreviousCgiPlusUsageCount)
         fprintf (stdout, "\n<LI>");
      else
         fprintf (stdout, "<LI>");
   }
   else
   if (!isalpha(KeyName[1][0]) ||
        KeyName[1][0] == PreviousAlphabetic)
   {
      if (CgiPlusUsageCount == PreviousCgiPlusUsageCount)
         fprintf (stdout, ",\n");
   }
   else
   {
      if (CgiPlusUsageCount == PreviousCgiPlusUsageCount)
         fprintf (stdout, "\n<P>");
      PreviousAlphabetic = KeyName[1][0];
   }
   fprintf (stdout, "<A HREF=\"%s%s?key=%s&title=%s&referer=%s\">%s</A>",
            CgiScriptNamePtr, LibraryPathInfo, UriKeyName,
            UriFormTitle, UriReferer, KeyNameHtml[1]);

   PreviousCgiPlusUsageCount = CgiPlusUsageCount;
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
*/
 
int OpenTextLibraryModule ()

{
   int  status;
   char  *cptr, *sptr;
   char  KeyName [256],
         String [512];
   $DESCRIPTOR (KeyNameDsc, KeyName);
 
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "OpenTextLibraryModule()\n");
 
   cptr = KeyName;
   for (sptr = KeyWord[1]; *sptr; *cptr++ = toupper(*sptr++));
   *cptr = '\0';
   KeyNameDsc.dsc$w_length = cptr - KeyName;
   
   CgiLibResponseHeader (200, "text/plain",
                         "Last-Modified: %s\n", LastModifiedGmDateTime);

   IndexNumber = 1;

   if (VMSnok (status = lbr$get_index (&LibraryIndex,
                                       &IndexNumber,
                                       &OpenTextModule,
                                       &KeyNameDsc)))
   {
      if (Debug) fprintf (stdout, "lib$get_index() %%X%08.08X\n", status);
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   return (status);
}
 
/*****************************************************************************/
/*
*/ 
 
int OpenTextModule
(
struct dsc$descriptor_s  *KeyNameDscPtr,
void  *RFAptr
)
{
   int  status;
   char  *sptr;
   char  Buffer [1024],
         String [1024];
   $DESCRIPTOR (InBufferDsc, Buffer);
   $DESCRIPTOR (OutBufferDsc, Buffer);
 
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "OpenTextModule()\n");

   strncpy (KeyWord[1],
            KeyNameDscPtr->dsc$a_pointer, KeyNameDscPtr->dsc$w_length);
   KeyWord[1][KeyNameDscPtr->dsc$w_length] = '\0';
   if (Debug) fprintf (stdout, "KeyNameDsc |%s|\n", KeyWord[1]);

   if (VMSnok (status = lbr$lookup_key (&LibraryIndex,
                                        KeyNameDscPtr,
                                        &ModuleRFA)))
   {
      if (Debug) fprintf (stdout, "lib$lookup_key() %%X%08.08X\n", status);
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   /**************************************/
   /* loop through all records in module */
   /**************************************/

   for (;;)
   {
      status = lbr$get_record (&LibraryIndex,
                               &InBufferDsc,
                               &OutBufferDsc);
      if (Debug) fprintf (stdout, "lbr$get_record() %%X%08.08X\n", status);
      if (VMSnok (status)) break;
 
      /* add HTTP required carriage-control */
      Buffer[OutBufferDsc.dsc$w_length] = '\n';
      Buffer[OutBufferDsc.dsc$w_length+1] = '\0';
      fputs (Buffer, stdout);
  }
 
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
*/
 
int SearchTextLibraryModules ()

{
   int  status;
   char  *cptr, *sptr;
   char  KeyName [256],
         String [1024];
   $DESCRIPTOR (KeyNameDsc, KeyName);
 
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "SearchTextLibraryModules()\n");
 
   cptr = KeyName;
   for (sptr = KeyWord[1]; *sptr; *cptr++ = toupper(*sptr++));
   *cptr = '\0';
   KeyNameDsc.dsc$w_length = cptr - KeyName;
   
   sprintf (String, "Search &quot;%s&quot; for &quot;%s&quot;\n",
            HtmlLibraryTitle, HtmlSearchString);

   BeginPage (String, String);

   fprintf (stdout, "<BLOCKQUOTE>\n");

   RecordsSearched = SearchHitCount = 0;
   IndexNumber = 1;

   if (DoSearchStatistics) lib$init_timer (0);

   if (VMSnok (status = lbr$get_index (&LibraryIndex,
                                       &IndexNumber,
                                       &SearchTextModule,
                                       &KeyNameDsc)))
   {
      if (Debug) fprintf (stdout, "lib$get_index() %%X%08.08X\n", status);
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   if (!SearchHitCount) fprintf (stdout, "<B>Not found!</B>\n<P>");

   if (DoSearchStatistics) SearchStatistics ();

   fprintf (stdout, "</BLOCKQUOTE>\n");
   ButtonBar (2);
   fprintf (stdout, "</BODY>\n</HTML>\n");
}

/*****************************************************************************/
/*
*/ 
 
int SearchTextModule
(
struct dsc$descriptor_s  *KeyNameDscPtr,
void  *RFAptr
)
{
   boolean  HitThisModule = false;
   int  status;
   char  ch;
   char  *cptr, *hptr, *sptr;
   char  Buffer [1024],
         String [1024];
   $DESCRIPTOR (InBufferDsc, Buffer);
   $DESCRIPTOR (OutBufferDsc, Buffer);
 
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "SearchTextModule()\n");

   strncpy (KeyName[1],
            KeyNameDscPtr->dsc$a_pointer,
            KeyNameDscPtr->dsc$w_length);
   KeyName[1][KeyNameDscPtr->dsc$w_length] = '\0';
   CgiLibHtmlEscape (KeyName[1], -1, KeyNameHtml[1], -1);
   if (Debug) fprintf (stdout, "KeyNameDsc |%s|\n", KeyName[1]);

   if (VMSnok (status = lbr$lookup_key (&LibraryIndex,
                                        KeyNameDscPtr,
                                        &ModuleRFA)))
   {
      if (Debug) fprintf (stdout, "lib$get_index() %%X%08.08X\n", status);
      CgiLibResponseError (FI_LI, status, HtmlLibraryTitle);
      return (SS$_NORMAL);
   }

   /**************************************/
   /* loop through all records in module */
   /**************************************/

   for (;;)
   {
      status = lbr$get_record (&LibraryIndex,
                               &InBufferDsc,
                               &OutBufferDsc);
      if (Debug) fprintf (stdout, "lbr$get_record() %%X%08.08X\n", status);
      if (VMSnok (status)) break;
 
      Buffer[OutBufferDsc.dsc$w_length] = '\0';
      if (Debug) fprintf (stdout, "Buffer |%s|\n", Buffer);

      /* check to see if this module record has any non-space chars */
      for (cptr = Buffer; *cptr && isspace(*cptr); cptr++);
      if (*cptr)
      {
         if (*(hptr = SearchText (cptr, CgiFormSearchPtr, false)))
         {
            /********/
            /* hit! */
            /********/

            if (Debug) fprintf (stdout, "Hit |%s|\n", sptr);
            SearchHitCount++;
            sptr = String;
            if (!HitThisModule)
            {
               sptr += sprintf (sptr,
"<A HREF=\"%s%s?key=%s&title=%s&referer=%s\"><B>%s</B></A>\n<UL>\n",
                  CgiScriptNamePtr, LibraryPathInfo,
                  KeyName[1], UriFormTitle, UriReferer, KeyNameHtml[1]);
               HitThisModule = true;
            }
            strcpy (sptr, "<LI><NOBR><TT>");
            sptr += 14;
            ch = cptr[hptr-cptr];
            cptr[hptr-cptr] = '\0';
            sptr += CgiLibHtmlEscape (cptr, -1, sptr, -1);
            cptr[hptr-cptr] = ch;
            strcpy (sptr, "<B>");
            sptr += 3;
            strncpy (sptr, hptr, SearchStringLength);
            sptr += SearchStringLength;
            strcpy (sptr, "</B>");
            sptr += 4;
            sptr += CgiLibHtmlEscape (hptr+SearchStringLength, -1, sptr, -1);
            strcpy (sptr, "</TT></NOBR>");
            sptr += 12;
            *sptr++ = '\n';
            *sptr = '\0';

            fprintf (stdout, "%s", String);
         }
      }
   }

   if (HitThisModule)
   {
      fprintf (stdout, "</UL>\n");
      fflush (stdout);
   }

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Case sensistive or case insensistive search of the string pointed to by 'sptr' 
for the string pointed to by 'SearchString'.
*/ 

char* SearchText
( 
char *tptr,
char *SearchString,
boolean CaseSensitive
)
{
   char  *cptr, *sptr;

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

   if (Debug) fprintf (stdout, "SearchText()\n");

   RecordsSearched++;

   sptr = SearchString;
   if (CaseSensitive)
   {
      while (*tptr)
      {
         if (*tptr++ != *sptr) continue;
         /* first character of search string matched record character */
         sptr++;
         cptr = tptr;
         while (*cptr && *sptr)
         {
            if (*cptr != *sptr) break;
            cptr++;
            sptr++;
         }
         if (!*sptr)
         {
            tptr--;
            break;
         }
         sptr = SearchString;
      }
   }
   else
   {
      while (*tptr)
      {
         if (toupper(*tptr++) != toupper(*sptr)) continue;
         /* first character of search string matched record character */
         sptr++;
         cptr = tptr;
         while (*cptr && *sptr)
         {
            if (toupper(*cptr) != toupper(*sptr)) break;
            cptr++;
            sptr++;
         }
         if (!*sptr)
         {
            tptr--;
            break;
         }
         sptr = SearchString;
      }
   }
   return (tptr);
}

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

SearchStatistics ()

{
   static long  LibElapsedTime = 1,
                LibCpuTime = 2;
   static $DESCRIPTOR (ElapsedFaoDsc, "!%T");

   static $DESCRIPTOR (SearchStats1FaoDsc,
"<TABLE BORDER=0 CELLPADDING=2 CELLSPACING=0!AZ>\n\
<TR><TD>\n\
<FONT SIZE=-1 COLOR=\"!AZ\">&nbsp;\n\
<B>Elapsed:</B> !AZ&nbsp; \
<B>CPU:</B> !2ZL:!2ZL.!2ZL&nbsp; \
<B>Records (lines):</B> !UL\n\
&nbsp;</FONT>\n\
</TD></TR>\n\
</TABLE>\n");

   static $DESCRIPTOR (SearchStats2FaoDsc,
"<FONT SIZE=-1>&nbsp;\n\
<U>\
<B>Elapsed:</B> !AZ&nbsp; \
<B>CPU:</B> !2ZL:!2ZL.!2ZL&nbsp; \
<B>Records (lines):</B> !UL\
</U>\n\
&nbsp;</FONT>\n\
</TD></TR>\n\
</FONT>\n");

   unsigned long  ElapsedTime [2];
   unsigned long  CpuTime;
   unsigned short  Length;
   char  Elapsed [32],
         String [512];
   $DESCRIPTOR (ElapsedDsc, Elapsed);
   $DESCRIPTOR (StringDsc, String);

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

   if (Debug) fprintf (stdout, "SearchStatistics()\n");

   lib$stat_timer (&LibElapsedTime, &ElapsedTime, 0);
   lib$stat_timer (&LibCpuTime, &CpuTime, 0);

   sys$fao (&ElapsedFaoDsc, &Length, &ElapsedDsc, &ElapsedTime);
   Elapsed[Length] = '\0';
   if (PageScheme[PS_LAYOUT][0] == '2')
      sys$fao (&SearchStats2FaoDsc, &Length, &StringDsc,
               Elapsed+3,
               CpuTime/6000, CpuTime/100, CpuTime%100,
               RecordsSearched);
   else
      sys$fao (&SearchStats1FaoDsc, &Length, &StringDsc,
               PageScheme[PS_HEADBGCOLOR],
               PageScheme[PS_HEADTEXT],
               Elapsed+3,
               CpuTime/6000, CpuTime/100, CpuTime%100,
               RecordsSearched);
   String[Length] = '\0';
   fprintf (stdout, "%s", String);
}

/*****************************************************************************/
/*
Output the HTTP header and page header.
*/
 
BeginPage
(
char *TitleString,
char *HeaderString
)
{
   char  *cptr, *sptr;

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

   if (Debug) fprintf (stdout, "BeginPage()\n");
 
   CgiLibResponseHeader (200, "text/html",
                         "Last-Modified: %s\n", LastModifiedGmDateTime);
   fprintf (stdout,
"<HTML>\n\
<HEAD>\n\
<META NAME=\"generator\" CONTENT=\"%s\">\n\
<META NAME=\"date\" CONTENT=\"%s\">\n\
<META NAME=\"environment\" CONTENT=\"%s\">\n\
<TITLE>%s</TITLE>\n\
</HEAD>\n\
<BODY%s>\n",
      SoftwareID, UnixDateTime, CgiLibEnvironmentPtr,
      TitleString, PageScheme[PS_BODYTAG]);

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      fprintf (stdout,
"%s<FONT SIZE=+2><B>\n\
%s\n\
</B></FONT>\n\
<BR>\
<FONT SIZE=-1>\n\
&nbsp;<SUP>*</SUP><U>Conan The Librarian</U>\n\
</FONT>\n\
<HR ALIGN=left SIZE=2 WIDTH=95%%>\n\
<P>",
         PageScheme[PS_HEADLOCAL],
         HeaderString);
   }
   else
   {
      fprintf (stdout,
"<TABLE BORDER=%s CELLPADDING=%s CELLSPACING=0 WIDTH=100%%%s>\n\
<TR><TD>\n\
<FONT COLOR=\"%s\" SIZE=+2><B>\n\
%s\n\
</B></FONT>\n\
<BR>\
<FONT COLOR=\"%s\" SIZE=-1>\n\
&nbsp;<SUP>*</SUP><U>Conan The Librarian</U>\n\
</FONT>\n\
</TD>%s</TR>\n\
</TABLE>\n",
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADPADDING],
         PageScheme[PS_HEADBGCOLOR],
         PageScheme[PS_HEADTEXT],
         HeaderString,
         PageScheme[PS_HEADTEXT],
         PageScheme[PS_HEADLOCAL]);
   }

   fflush (stdout);
}
 
/*****************************************************************************/
/*
In a help key string turn underscores into spaces mixed case.
*/

char* MungeUnderScores (char *InString)

{
   static char  OutString [128];

   char  *cptr, *sptr;

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

   if (Debug) fprintf (stdout, "MungeUnderScores()\n");

   sptr = OutString;
   if (InString[0] != '/')
   {
      /* not a qualifier, see if it's all the one case! */
      if (isupper(InString[0]))
      {
         for (cptr = InString; *cptr; cptr++)
            if (isalpha(*cptr) && !isupper(*cptr)) break;
      }
      else
      {
         for (cptr = InString; *cptr; cptr++)
            if (isalpha(*cptr) && !islower(*cptr)) break;
      }
   }
   if (InString[0] != '/' && *cptr)
   {                            
      /* probably an underscore-joined topic string */
      for (cptr = InString; *cptr; cptr++)
      {
         if (*cptr == '_')
            *sptr++ = ' ';
         else
            *sptr++ = *cptr;
      }
   }
   else
   {
      /* all upper-case, probably some particular VMS parameter */
      for (cptr = InString; *cptr; cptr++)
         *sptr++ = *cptr;
   }
   *sptr = '\0';
   return (OutString);
}

/*****************************************************************************/
/*
If the object has been modified since the specified date and time then return 
a normal status indicating that the data transfer is to continue.  If not 
modified then send a "not modified" HTTP header and return an error status to 
indicate the object should not be sent.
*/ 
 
int ModifiedSince
(
unsigned long *SinceBinaryTimePtr,
unsigned long *BinaryTimePtr
)
{
   static unsigned long  OneSecondDelta [2] = { -10000000, -1 };

   int  status;
   unsigned long  AdjustedBinTime [2],
                  ScratchBinTime [2];

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

   if (Debug) fprintf (stdout, "ModifiedSince()\n");

   /* if request asks for a "reload" (not cached) then give it regardless */
   if (strsame (CgiHttpPragmaPtr, "no-cache", -1)) return (SS$_NORMAL);
   if (strsame (CgiHttpCacheControlPtr, "no-cache", 8)) return (SS$_NORMAL);
   if (strsame (CgiHttpCacheControlPtr, "no-store", 8)) return (SS$_NORMAL);
   if (strsame (CgiHttpCacheControlPtr, "max-age=0", 9)) return (SS$_NORMAL);

   /*
      Add one second to the modified time.  Ensures a negative time
      for VMS where fractional seconds may result in inconclusive
      results when the target time is being specified in whole seconds.
   */ 
   if (VMSnok (status =
       lib$add_times (SinceBinaryTimePtr, &OneSecondDelta, &AdjustedBinTime)))
   {
      CgiLibResponseError (FI_LI, status, "IfModifiedSince:");
      return (status);
   }
   if (Debug) fprintf (stdout, "sys$add_times() %%X%08.08X\n", status);

   /* if a positive time results the file has been modified */
   if (VMSok (status =
       lib$sub_times (BinaryTimePtr, &AdjustedBinTime, &ScratchBinTime)))
      return (status);

   if (Debug) fprintf (stdout, "sys$sub_times() %%X%08.08X\n", status);
   if (status != LIB$_NEGTIM)
   {
      CgiLibResponseError (FI_LI, status, "IfModifiedSince:");
      return (status);
   }

   /* not modified */
   CgiLibResponseHeader (304, "text/html");
   fprintf (stdout, "Not modified!\n");

   return (LIB$_NEGTIM);
}

/*****************************************************************************/
/*
Create an HTTP format Greenwich Mean Time (UTC) time string in the storage 
pointed at by 'TimeString', e.g. "Fri, 25 Aug 1995 17:32:40 GMT" (RFC 1123). 
This must be at least 30 characters capacity.  If 'BinTimePtr' is null the 
time string represents the current time.  If it points to a quadword, VMS time 
value the string represents that time.  'TimeString' must point to storage 
large enough for 31 characters.
*/

int HttpGmTimeString
(
char *TimeString,
unsigned long *BinTimePtr
)
{
   static char  *DayNames [] =
      { "", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
   static char  *MonthName [] =
      { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

   static $DESCRIPTOR (HttpTimeFaoDsc, "!AZ, !2ZW !AZ !4ZW !2ZW:!2ZW:!2ZW GMT");
   static $DESCRIPTOR (TimeStringDsc, "");

   int  status;
   unsigned long  BinTime [2],
                  GmTime [2];
   unsigned short  Length;
   unsigned short  NumTime [7];
   unsigned long  DayOfWeek;

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

   if (Debug) fprintf (stdout, "HttpGmTimeString()\n");

   if (!BinTimePtr)
      sys$gettim (&GmTime);
   else
   {
      GmTime[0] = BinTimePtr[0];
      GmTime[1] = BinTimePtr[1];
   }
   if (VMSnok (status = TimeAdjustGMT (true, &GmTime)))
      return (status);

   status = sys$numtim (&NumTime, &GmTime);
   if (Debug)
      fprintf (stdout, "sys$numtim() %%X%08.08X %d %d %d %d %d %d %d\n",
               status, NumTime[0], NumTime[1], NumTime[2],
               NumTime[3], NumTime[4], NumTime[5], NumTime[6]);

   if (VMSnok (status = lib$day_of_week (&GmTime, &DayOfWeek)))
      return (status);
   if (Debug)
      fprintf (stdout, "lib$day_of_week() %%X%08.08X is %d\n",
               status, DayOfWeek);

   /* set the descriptor address and size of the resultant time string */
   TimeStringDsc.dsc$w_length = 30;
   TimeStringDsc.dsc$a_pointer = TimeString;

   if (VMSnok (status =
       sys$fao (&HttpTimeFaoDsc, &Length, &TimeStringDsc,
                DayNames[DayOfWeek], NumTime[2], MonthName[NumTime[1]],
                NumTime[0], NumTime[3], NumTime[4], NumTime[5])))
   {
      if (Debug) fprintf (stdout, "sys$fao() %%X%08.08X\n", status);
      TimeString[0] = '\0';
   }
   else
      TimeString[Length] = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", TimeString);

   return (status);
}

/*****************************************************************************/
/*
Given a string such as "Fri, 25 Aug 1995 17:32:40 GMT" (RFC 1123), or "Friday,
25-Aug-1995 17:32:40 GMT" (RFC 1036), create an internal, local, binary time
with the current GMT offset.  See complementary function HttpGmTimeString().
*/ 

int HttpGmTime
(
char *TimeString,
unsigned long *BinTimePtr
)
{
   static char  *MonthName [] =
      { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

   int  status;
   char  *tptr;
   unsigned short  Length;
   unsigned short  NumTime [7] = { 0,0,0,0,0,0,0 };
   unsigned long  DayOfWeek;

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

   if (Debug) fprintf (stdout, "HttpGmTime() |%s|\n", TimeString);

   tptr = TimeString;
   /* hunt straight for the comma after the weekday name! */
   while (*tptr && *tptr != ',') tptr++;
   if (*tptr) tptr++;
   /* span white space between weekday name and date */
   while (*tptr && isspace(*tptr)) tptr++;
   if (Debug) fprintf (stdout, "tptr |%s|\n", tptr);
   if (!*tptr) return (STS$K_ERROR);

   /* get the date and then skip to month name */
   if (isdigit(*tptr)) NumTime[2] = atoi (tptr);
   while (*tptr && isdigit(*tptr)) tptr++;
   while (*tptr && (*tptr == '-' || isspace(*tptr))) tptr++;
   if (Debug) fprintf (stdout, "tptr |%s|\n", tptr);
   if (!*tptr) return (STS$K_ERROR);

   /* get the month number from the name and skip to the year */
   for (NumTime[1] = 1; NumTime[1] <= 12; NumTime[1]++)
      if (strsame (tptr, MonthName[NumTime[1]], 3)) break;
   if (NumTime[1] > 12) return (STS$K_ERROR);
   while (*tptr && isalpha(*tptr)) tptr++;
   while (*tptr && (*tptr == '-' || isspace(*tptr))) tptr++;
   if (Debug) fprintf (stdout, "tptr |%s|\n", tptr);
   if (!*tptr) return (STS$K_ERROR);

   /* get the year and then skip to the hour */
   if (isdigit(*tptr))
   {
      NumTime[0] = atoi (tptr);
      if (NumTime[0] < 100) NumTime[0] += 1900;
   }
   while (*tptr && isdigit(*tptr)) tptr++;
   while (*tptr && isspace(*tptr)) tptr++;
   if (Debug) fprintf (stdout, "tptr |%s|\n", tptr);
   if (!*tptr) return (STS$K_ERROR);

   /* get the hour, minute and second */
   if (isdigit(*tptr)) NumTime[3] = atoi (tptr);
   while (*tptr && isdigit(*tptr)) tptr++;
   if (*tptr == ':') tptr++;
   if (isdigit(*tptr)) NumTime[4] = atoi (tptr);
   while (*tptr && isdigit(*tptr)) tptr++;
   if (*tptr == ':') tptr++;
   if (isdigit(*tptr)) NumTime[5] = atoi (tptr);
   while (*tptr && isdigit(*tptr)) tptr++;
   if (*tptr == ':') tptr++;
   if (!*tptr) return (STS$K_ERROR);

   /* the only thing remaining should be the "GMT" */
   while (*tptr && isspace(*tptr)) tptr++;
   if (Debug) fprintf (stdout, "tptr |%s|\n", tptr);
   if (!strsame (tptr, "GMT", 3)) return (STS$K_ERROR);

   /*******************************************/
   /* convert what looks like legitimate GMT! */
   /*******************************************/

   if (Debug)
      fprintf (stdout, "NumTime[] %d %d %d %d %d %d %d\n",
               NumTime[0], NumTime[1], NumTime[2], NumTime[3], 
               NumTime[4], NumTime[5], NumTime[6]); 
   status = lib$cvt_vectim (&NumTime, BinTimePtr);
   if (VMSnok (status)) return (status);
   if (Debug) fprintf (stdout, "lib$cvt_vectim() %%X%08.08X\n", status);

   return (TimeAdjustGMT (false, BinTimePtr));
}

/*****************************************************************************/
/*
Determine the offset from GMT (UTC) using either the HTTPD$GMT or
SYS$TIMEZONE_DIFFERENTIAL logicals. If HTTPD$GMT is not defined time should be
set from the timezone differential logical. Function RequestBegin() calls
this every hour to recheck GMT offset and detect daylight saving or other
timezone changes.
*/

int TimeSetGmt ()

{
   static boolean  UseTimezoneDifferential = false;

   int  status;

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

   if (Debug) fprintf (stdout, "TimeSetGmt()\n");

   if (!UseTimezoneDifferential)
   {
      status = TimeSetHttpdGmt();
      /* return if error and that error was not that the name did not exist */
      if (VMSok (status) || status != SS$_NOLOGNAM) return (status);
   }

   UseTimezoneDifferential = true;
   return (TimeSetTimezone());
}

/*****************************************************************************/
/*
The SYS$TIMEZONE_DIFFERENTIAL logical contains the number of seconds offset
from GMT (UTC) as a positive (ahead) or negative (behind) number.  Set the
'TimeGmtString' global storage to a "+hh:mm" or "-hh:mm" string equivalent, and
the 'TimeAheadOfGmt' global boolean and 'TimeGmtVmsString' delta-time global
string.
*/

int TimeSetTimezone ()

{
   static unsigned short  Length;
   static $DESCRIPTOR (TimezoneLogicalNameDsc, "SYS$TIMEZONE_DIFFERENTIAL");
   static $DESCRIPTOR (LnmSystemDsc, "LNM$SYSTEM");
   static $DESCRIPTOR (TimeGmtStringFaoDsc, "!AZ!2ZL:!2ZL");
   static $DESCRIPTOR (TimeGmtVmsStringFaoDsc, "0 !2ZL:!2ZL");
   static $DESCRIPTOR (TimeGmtStringDsc, TimeGmtString);
   static struct {
      short int  buf_len;
      short int  item;
      void  *buf_addr;
      unsigned short  *ret_len;
   } LnmItems [] =
   {
      { sizeof(TimeGmtString)-1, LNM$_STRING, TimeGmtString, &Length },
      { 0,0,0,0 }
   };

   int  status;
   long  Hours,
         Minutes,
         Seconds;
   char  *SignPtr;
   $DESCRIPTOR (TimeGmtVmsStringDsc, TimeGmtVmsString);

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

   if (Debug) fprintf (stdout, "TimeSetTimezone()\n");

   status = sys$trnlnm (0, &LnmSystemDsc, &TimezoneLogicalNameDsc, 0, &LnmItems);
   if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status);
   if (VMSnok (status)) return (status);

   TimeGmtString[Length] = '\0';
   if (Debug) fprintf (stdout, "TimeGmtString |%s|\n", TimeGmtString);
   Seconds = atol(TimeGmtString);
   if (Seconds < 0)
   {
      TimeAheadOfGmt = false;
      Seconds = -Seconds;
      SignPtr = "-";
   }
   else
   {
      TimeAheadOfGmt = true;
      SignPtr = "+";
   }
   Hours = Seconds / 3600;
   Minutes = (Seconds - Hours * 3600) / 60;
   if (Debug)
      fprintf (stdout, "%d %s%d:%d\n", Seconds, SignPtr, Hours, Minutes);
   sys$fao (&TimeGmtStringFaoDsc, &Length, &TimeGmtStringDsc,
            SignPtr, Hours, Minutes);
   TimeGmtString[Length] = '\0';
   if (Debug) fprintf (stdout, "TimeGmtString |%s|\n", TimeGmtString);

   sys$fao (&TimeGmtVmsStringFaoDsc, &Length, &TimeGmtVmsStringDsc,
            Hours, Minutes);
   TimeGmtVmsString[Length] = '\0';
   if (Debug) fprintf (stdout, "TimeGmtVmsString |%s|\n", TimeGmtVmsString);

   TimeGmtVmsStringDsc.dsc$w_length = Length;

   if (VMSnok (status =
       sys$bintim (&TimeGmtVmsStringDsc, &TimeGmtDeltaBinary)))
      return (status);
   if (TimeGmtDeltaBinary[0] || TimeGmtDeltaBinary[1])
      return (status);
   /* time must have been zero, make it one, one-hundreth of a second */
   TimeGmtDeltaBinary[0] = -100000;
   TimeGmtDeltaBinary[1] = -1;
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Translate the logical HTTPD$GMT (defined to be something like "+10:30" or "-
01:15") and convert it into a delta time structure and store in 
'TimeGmtDeltaBinary'.  Store whether it is in advance or behind GMT in boolean 
'TimeAheadOfGmt'.  Store the logical string in 'TimeGmtString'.
*/

int TimeSetHttpdGmt ()

{
   static unsigned short  Length;
   static $DESCRIPTOR (GmtLogicalNameDsc, "HTTPD$GMT");
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
   static struct {
      short int  buf_len;
      short int  item;
      void  *buf_addr;
      unsigned short  *ret_len;
   } LnmItems [] =
   {
      { sizeof(TimeGmtString)-1, LNM$_STRING, TimeGmtString, &Length },
      { 0,0,0,0 }
   };

   int  status;
   char  *cptr, *sptr;
   $DESCRIPTOR (TimeGmtVmsStringDsc, TimeGmtVmsString);

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

   if (Debug) fprintf (stdout, "TimeSetHttpdGmt()\n");

   status = sys$trnlnm (0, &LnmFileDevDsc, &GmtLogicalNameDsc, 0, &LnmItems);
   if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status);
   if (VMSnok (status)) return (status);

   TimeGmtString[Length] = '\0';
   if (Debug) fprintf (stdout, "TimeGmtString |%s|\n", TimeGmtString);

   if (TimeGmtString[0] == '$') return (SS$_NORMAL);

   if (*(cptr = TimeGmtString) == '-')
      TimeAheadOfGmt = false;
   else
      TimeAheadOfGmt = true;
   if (*cptr == '+' || *cptr == '-') cptr++;
   sptr = TimeGmtVmsString;
   *sptr++ = '0';
   *sptr++ = ' ';
   while (*cptr) *sptr++ = *cptr++;
   *sptr = '\0';
   if (Debug) fprintf (stdout, "TimeGmtVmsString |%s|\n", TimeGmtVmsString);

   TimeGmtVmsStringDsc.dsc$w_length = sptr - TimeGmtVmsString;

   if (VMSnok (status =
       sys$bintim (&TimeGmtVmsStringDsc, &TimeGmtDeltaBinary)))
      return (status);
   if (TimeGmtDeltaBinary[0] || TimeGmtDeltaBinary[1])
      return (status);
   /* time must have been zero, make it one, one-hundreth of a second */
   TimeGmtDeltaBinary[0] = -100000;
   TimeGmtDeltaBinary[1] = -1;
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
The GMT is generated by calculating using an offset using 
'TimeGmtDeltaOffset' and boolean 'TimeAheadOfGmt'.  Adjust either to or from 
GMT.
*/ 

int TimeAdjustGMT
(
boolean ToGmTime,
unsigned long *BinTimePtr
)
{
   int  status;
   unsigned long  AdjustedTime [2];

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

   if (Debug) fprintf (stdout, "TimeAdjustGMT() ToGmTime: %d\n", ToGmTime);

   if ((ToGmTime && TimeAheadOfGmt) || (!ToGmTime && !TimeAheadOfGmt))
   {
      /* to GMT from local and ahead of GMT, or to local from GMT and behind */
      status = lib$sub_times (BinTimePtr, &TimeGmtDeltaBinary, &AdjustedTime);
      if (Debug) fprintf (stdout, "lib$sub_times() %%X%08.08X\n", status);
   }
   else
   {
      /* to GMT from local and behind GMT, or to local from GMT and ahead */
      status = lib$add_times (BinTimePtr, &TimeGmtDeltaBinary, &AdjustedTime);
      if (Debug) fprintf (stdout, "lib$add_times() %%X%08.08X\n", status);
   }

   if (Debug)
   {
      unsigned short  Length;
      char  String [64];
      $DESCRIPTOR (AdjustedTimeFaoDsc, "AdjustedTime: |!%D|\n");
      $DESCRIPTOR (StringDsc, String);

      sys$fao (&AdjustedTimeFaoDsc, &Length, &StringDsc, &AdjustedTime);
      String[Length] = '\0';
      fprintf (stdout, "%s", String);
   }

   BinTimePtr[0] = AdjustedTime[0];
   BinTimePtr[1] = AdjustedTime[1];

   return (status);
}

/*****************************************************************************/
/*
Output an HTML-escaped, URL-anchored, and "$ HELP"-anchored string.
*/

HtmlOutputString
(
char *InString,
boolean AnchorUrls
)
{
   char  OutString [1024];
   char  *cptr;

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

   if (Debug) fprintf (stdout, "HtmlOutputString()\n");

   CgiLibAnchorHtmlEscape (InString, -1,
                           OutString, sizeof(OutString),
                           AnchorUrls);
   if (cptr = strstr (OutString, "$ HELP"))
   {
      char  *sptr, *tptr;
      char  Scratch [1024];

      cptr += 2;
      tptr = cptr + 4;
      sptr = Scratch;
      while (*tptr && *tptr != '\n')
      {
         switch (*tptr)
         {
            case ' ' : *sptr++ = '/'; break;
            case '/' : *sptr++ = '/'; *sptr++ = '/'; break;
            default : *sptr++ = *tptr;
         }
         tptr++;
      }
      *sptr = '\0';
      *tptr = '\0';
      fwrite (OutString, cptr-OutString, 1, stdout);
      fprintf (stdout, "<A HREF=\"%s%s\">%s</A>\n",
               CgiScriptNamePtr, Scratch, cptr);

   }
   else
      fputs (OutString, stdout);
}

/****************************************************************************/
/*
Return an integer reflecting the major and minor version of VMS (e.g. 60, 61,
62, 70, 71, 72, etc.)
*/ 

#ifdef ODS_EXTENDED

int GetVmsVersion ()

{
   static char  SyiVersion [16];

   static struct {
      short int  buf_len;
      short int  item;
      void  *buf_addr;
      unsigned short  *ret_len;
   }
   SyiItems [] =
   {
      { 8, SYI$_VERSION, &SyiVersion, 0 },
      { 0,0,0,0 }
   };

   int  status,
        version;

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

   if (Debug) fprintf (stdout, "GetVmsVersion()\n");

   if (VMSnok (status = sys$getsyiw (0, 0, 0, &SyiItems, 0, 0, 0)))
      exit (status);
   SyiVersion[8] = '\0';
   version = ((SyiVersion[1]-48) * 10) + (SyiVersion[3]-48);
   if (Debug) fprintf (stdout, "|%s| %d\n", SyiVersion, version);
   return (version);
}

#endif /* ODS_EXTENDED */

/****************************************************************************/
/*
Does a case-insensitive, character-by-character string compare and returns 
true if two strings are the same, or false if not.  If a maximum number of 
characters are specified only those will be compared, if the entire strings 
should be compared then specify the number of characters as 0.
*/ 

boolean strsame
(
char *sptr1,
char *sptr2,
int  count
)
{
   while (*sptr1 && *sptr2)
   {
      if (toupper (*sptr1++) != toupper (*sptr2++)) return (false);
      if (count)
         if (!--count) return (true);
   }
   if (*sptr1 || *sptr2)
      return (false);
   else
      return (true);
}

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

