/*****************************************************************************/
/*
                              HyperShelf.c

A CGI-compliant script.
Can be supported by any server that supplies the CGI variables listed below.


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 "HYPERSHELF$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=/hypershelf/-/help.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=/hypershelf/-/help.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:

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

AND REMEMBER ... HyperShelf 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


ABOUT HYPERSHELF
----------------

Emulates the "Bookshelf Navigation Utility" (BNU) of 1997ff on-line
documentation CD releases, and the Bookreader shelf navigation capability of
earlier times. It has some additional capabilities not found in either, the
ability to provide access to PostScript, plain-text documents and PDF
documents.

Page colouration may be specified via the appropriate command-line qualifiers
(or corresponding logical name). Defaults for any not specified.  Specifiy as
/BLAH="" to NOT specify the corresponding colour (i.e. leave it to the
browser).

Server mapping must include rules for all file paths (devices) which have
shelves residing on them.  This script munges VMS file paths into URL-style
equivalents.  For example:

  DISK$AXPDOCSEP971:[DATABASE]D39QAAA8.BKB

would be represented as

  /disk$axpdocsep971/database/d39qaaa8.bkb

Therefore the server must map the "/disk$axpdocsep971/*" into the VMS
equivalent, and of course all file paths that can possibly be returned.
Actual a useful rule for all BNU CDs is (depending on your architecture):

  pass /disk$axpdoc*  /disk$axpdoc*
  pass /disk$vaxdoc*  /disk$vaxdoc*

Thanks to Jeff Goodwin of Fairchild Semiconductor for these VMS Apache mappings

  AliasMatch ^/disk\$axpdoc(.+) /disk$axpdoc$1
  AliasMatch ^/disk\$vaxdoc(.+) /disk$vaxdoc$1

Digital Bookreader supports Bookreader format documents.

Digital BNU (currently) supports Bookreader and HTML documents.

WASD Hypershelf supports both plus the following extensions; PostScript, plain-
text and Adobe PDF documents.  It does not support Bristol HyperHelp.

Note that the Hypershelf extensions are preceded by two comment characters,
either "##" (as in the example) or "!!".  The prevents the BNU from barfing on
them.  If a title is the same as the previous entry (or is missing altogether)
the document title becomes a description of the document type.  For examples of
how they behave do a 

  http:///hypershelf/ht_root/exercise/library.odl

  http:///hypershelf/ht_root/exercise/library.decw$bookshelf


HYPERSHELF EXTENSIONS (see examples for examples)
---------------------
##post     PostScript document
##plain    plain-text document
##pdf      Adobe Portable Document Format
##url      relative or full Web URL (e.g. 'http://the.host.name/library/')


EXAMPLE BNU SHELF (extension: .ODL)
-----------------

  Title	VMS Hypertext Services Library (BNU version!)
  book	/ht_root/doc/htd/htd.decw$book	Technical Overview
  html	/ht_root/doc/htd/htd_0000.html	Technical Overview
  ##post	/ht_root/doc/htd/htd.ps		Technical Overview
  ##plain	/ht_root/doc/htd/htd.txt	Technical Overview
  ##pdf	/ht_root/doc/htd/htd.pdf	Technical Overview
  book	/ht_root/doc/env/env.decw$book	Hypertext Environment
  html	/ht_root/doc/env/env_0000.html	Hypertext Environment
  # don't have to specify the title for the same document but different type
  ##post	/ht_root/doc/env/env.ps
  ##plain	/ht_root/doc/env/env.txt
  book	/ht_root/doc/sdm2htm/sdm2htm.decw$book	SDML to HTML Converter
  html	/ht_root/doc/sdm2htm/sdm2htm_0000.html	SDML to HTML Converter
  ##post	/ht_root/doc/sdm2htm/sdm2htm.ps	SDML to HTML Converter
  book	/ht_root/doc/menu_primer/menu_primer.decw$book	Menu Primer
  html	/ht_root/doc/menu_primer/menu_primer_0000.html	Menu Primer
  ##url   http://the.host.name/library/  The Host Name Library
  ##url   /docs/  Local Server Documents


EXAMPLE BOOKREADER SHELF (extension: .DECW$BOOKSHELF or .BKS)
------------------------

  title\\VMS Hypertext Services Library (Bookreader version!)
  book\ht_root:[doc.htd]htd\Technical Overview
  ##html\ht_root:[doc.htd]htd_0000.html\
  ##ps\ht_root:[doc.htd]htd.ps\
  ##plain\ht_root:[doc.htd]htd.txt\
  ##pdf\ht_root:[doc.htd]htd.pdf\
  book\ht_root:[doc.env]env\Hypertext Environment
  ##html\ht_root:[doc.env]env_0000.html\
  ##post\ht_root:[doc.env]env.ps\
  ##plain\ht_root:[doc.env]env.txt\
  book\ht_root:[doc.sdm2htm]sdm2htm\SDML to HTML Converter
  ##html\ht_root:[doc.sdm2htm]sdm2htm_0000.html\
  ##ps\ht_root:[doc.sdm2htm]sdm2htm.ps\
  book\ht_root:[doc.menu_primer]menu_primer\Menu Primer
  ##html\ht_root:[doc.menu_primer]menu_primer_0000.html\Menu Primer
  ##url\http://the.host.name/library/\The Host Name Library
  ##url\/docs/\Local Server Documents


BNU OR BOOKREADER BEHAVIOUR?
----------------------------

HyperShelf adjusts it's behaviour according to the shelf file it is given in
the path.  If the file has a type of .ODL it is considered to be in BNU shelf
format (see above).  Any other type (e.g. .BKS or .DECW$BOOKSHELF) it
considers to be in Bookreader format (again, see above).

If a path to a shelf is not provided HyperShelf looks into the environment for
the DECW$BOOK and DECW$BOOKSHELF logicals.  If it finds DECW$BOOK it considers
it is a Bookreader environment and BNU is not in use.  It they are not found it
considers it a BNU environment.  To specify a default BNU library for when
none is supplied in the path use the /LIBRARY qualifier in a script support
procedure.

In a Bookreader environment HyperShelf will open any library specified by
DECW$BOOKSHELF (or libraries if it has multiple translations).  If not defined
it searches for a file using DECW$BOOK:LIBRARY.*  The first found will become
the default library. This will select all libraries (Digital and third party)
if DECW$BOOK is defined with multiple translations.  The first library
encountered is expanded to display it's contents.  Subsequent libraries have a
shelf link created for them with information appended to the description
indicating it is a library.  The use of search lists can considerably extend
library access (search) times, particularly when slow CD-ROM readers,
InfoServers or DECnet/FAL is involved.  A faster alternative is to incorporate
references to these in an explicit DECW$BOOKSHELF logical.

The BNU environment does not use logical names or logical search lists. 
Everthing appears to be "hard-wired" into the shelves in a URL-style or
Unix-style file path (understandable when you consider part of the BNU viewing
environment is a browser (Netscape Navigator)).  If HyperShelf is not supplied
with a path to an ODL shelf it will search for one of those listed in
'OdlShelfDefaults' storage below.

The qualifiers /BNU and /BOOKREADER can be used to force one behaviour or the
other.


SPECIFYING SHELVES
------------------
Shelves are specified in one of four ways, with the indicated precedence.

  1.  ?file= .......... request query string form field (VMS file spec)
  2.  /path/to/file ... path into VMS file spec via WWW_PATH_TRANSLATED
  3.  /LIBRARY= ....... HYPERSHELF script qualifier (VMS file spec)
  4.  no path ......... DECW$BOOK, DECW$BOOKSHELF, BNU default libraries


OSU ENVIRONMENT
---------------
Script responses are returned in OSU "raw" mode; the script taking care of the
full response header and correctly carriage-controlled data stream, text or
binary!!  Uses the CGILIB.C to engage in the dialog phase generating, storing
and then making available the equivalent of CGI variables.

To use the executable directly (no wrapper procedure), as in

  http://the.host.name/htbin/hypershelf

Define a system-wide logical during server startup

  $ HYPERSHELF_PARAM = -
    "/buttons=""Close;Help=/wasd/runtime/hypershelfhelp.html""" +-
    "/icons=""/wasd/runtime"""
  $ DEFINE /SYSTEM HYPERSHELF$PARAM HYPERSHELF_PARAM


APACHE CGI ENVIRONMENT
----------------------
To use the executable directly (no wrapper procedure), as in

  http://the.host.name/cgi-bin/hypershelf

Define a system-wide logical during server startup

  $ HYPERSHELF_PARAM = -
    "/buttons=""Close;Help=/wasd/runtime/hypershelfhelp.html""" +-
    "/icons=""/wasd/runtime"""
  $ DEFINE /SYSTEM HYPERSHELF$PARAM HYPERSHELF_PARAM


CGI VARIABLES
-------------
Server generated ...

WWW_HTTP_IF_MODIFIED_SINCE      if "304 Not modified" are desired (optional)
WWW_HTTP_PRAGMA         "no-cache" (optional)
WWW_HTTP_REFERER        "close" button becomes active
WWW_PATH_INFO           URL path to shelf
WWW_PATH_TRANSLATED     VMS file specification for shelf
WWW_REQUEST_METHOD      "GET" only is supported
WWW_SCRIPT_NAME         path to script
WWW_SERVER_CHARSET      default character set of server
WWW_SERVER_NAME         host on which the script is executing
WWW_SERVER_SOFTWARE     HTTPd identifying string

HyperShelf generated ...

WWW_FORM_FILE           VMS file name for shelf (when path cannot be supplied)
WWW_FORM_REFERER        overrides the HTTP_REFERER URL
WWW_FORM_TITLE          explicit title of book


QUALIFIERS
----------
/BOOKREADER     Bookreader is the default shelf processor
/BNU            Bookshelf Navigation Utility is the default shelf processor
/BUTTONS=       string containing button names
/CHARSET=       "Content-Type: text/html; charset=...", empty suppresses charset
/DBUG           turns on all "if (Debug)" statements (don't use with OSU)
/HYPERREADER=   name of 'HyperReader' script (defaults to "/hyperreader")
/ICON=          path to icons
/LIBRARY=       specifies the default library (shelf) file (VMS path)
/[NO]ODS5       control extended file specification (basically for testing)
/PRINT=         name of 'Print' script (optional)
/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


LOGICAL NAMES
-------------
HYPERSHELF$DBUG        turns on all "if (Debug)" statements
HYPERSHELF$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)


BUILD DETAILS
-------------
See BUILD_HYPERSHELF.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  v4.7.7, minor conditional mods to support IA64
27-APR-2003  MGD  v4.7.6, change DEFAULT_ICON to all lower-case for CSWS
12-APR-2003  MGD  v4.7.5, link colour changed to 0000cc
20-AUG-2002  MGD  v4.7.4, allow for HTTP/1.1 'Cache-control:' field used
                          by Mozilla variants (instead of HTTP/1.0 'pragma:')
15-AUG-2002  MGD  v4.7.3, make MFD ('000000') suppression WASD-only
30-JUN-2002  MGD  v4.7.2, bugfix; link path in ProcessShelf()
01-JUL-2001  MGD  v4.7.1, add 'SkipParameters' for direct OSU support
28-OCT-2000  MGD  v4.7.0, use CGILIB object module
31-AUG-2000  MGD  v4.6.0, add URL as bookshelf entry type (good suggestion
                          Gerry..Czadowski@@nav-international..com)
18-JAN-2000  MGD  v4.5.0, support extended file specifications (ODS-5)
07-AUG-1999  MGD  v4.4.0, use more of the CGILIB functionality
24-APR-1999  MGD  v4.3.0, use CGILIB.C,
                          standard CGI environment (Netscape FastTrack)
02-OCT-1998  MGD  v4.2.0, provide content-type "; charset=..."
27-AUG-1998  MGD  v4.1.1, iteratively translate DECW$BOOK translations
08-AUG-1998  MGD  v4.1.0, refinements in shelf and library parsing,
                          OSU output processing reworked,
                          bugfix; TimeSetTimezone() 'Seconds' unsigned->signed
06-AUG-1998  MGD  v4.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  v4.0.1, suppress table background colours if empty,
                          accomodations for OSU environment
29-APR-1998  MGD  v4.0.0, remove the need for WASD MapUrl() functions
                          (making it a GENERIC CGI script!!),
                          some tidying up and cosmetic changes
26-FEB-1998  MGD  v3.3.5, TimeSetGmt() modified to be in line with HTTPd
06-NOV-1997  MGD  v3.3.4, discovered "hyperhelp" in latest 7.1 ODL
                          added "unknown" document type
19-AUG-1997  MGD  v3.2.2, MapUrl() to MapUrl_Map() for conditional mapping
01-AUG-1997  MGD  v3.2.1, 'HttpHasBeenOutput' not initialize for CGIplus
20-JUL-1997  MGD  v3.2.0, added /BODY= qualifier, added Adobe PDF document type
07-JUL-1997  MGD  v3.1.0, CGIplus capable
30-JUN-1997  MGD  v3.0.0, major changes to support the BNU format shelves,
                          "Pragma: no-cache" now overrides "If-Modified-Since:"
16-MAY-1996  MGD  v2.2.3, change all .XBMs to transparent .GIFs
23-FEB-1996  MGD  v2.2.2, bugfix; after modifying the HTTPD$GMT format for the
                          HTTPd server I had forgotten about some scripts
22-NOV-1995  MGD  v2.2.1, discovered the TITLE keyword in libraries
12-OCT-1995  MGD  v2.2.0, add 'Referer:', 'Last-Modified:'/'If-Modified-Since:'
24-MAY-1995  MGD  v2.1.0, minor changes for AXP compatibility
15-APR-1995  MGD  v2.0.0, complete rewrite
10-JUN-1994  MGD  v1.0.0, initial development
*/
/*****************************************************************************/

#define SOFTWAREVN "4.7.7"
#define SOFTWARENM "HYPERSHELF"
#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>
#include <unixio.h>

/* VMS-related header files */
#include <descrip.h>
#include <libdef.h>
#include <lnmdef.h>
#include <rms.h>
#include <ssdef.h>
#include <stsdef.h>
#include <syidef.h>

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

#ifndef __VAX
#  pragma nomember_alignment
#endif

#define boolean int
#define true 1
#define false 0

#define FI_LI __FILE__, __LINE__

#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 VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) (!((x) & STS$M_SUCCESS))
 
#define DEFAULT_CGI_HYPERREADER "/cgi-bin/hyperreader"
#define DEFAULT_OSU_HYPERREADER "/htbin/hyperreader"
#define DEFAULT_WASD_HYPERREADER "/hyperreader"

#define DEFAULT_ICON "/hypershelf/-/"

#define DEFAULT_BUTTONS "Close;Help=/hypershelf/-/help.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  *BookshelfTypes [] = { ".ODL", ".BKS", ".DECW$BOOKSHELF", "" };

char OdlShelfDefaults [] =
"DECW$USER_DEFAULTS:LIBRARY.ODL, \
DECW$USER_DEFAULTS:CONTENTS.ODL, \
DECW$SYSTEM_DEFAULTS:LIBRARY.ODL, \
DECW$SYSTEM_DEFAULTS:CONTENTS.ODL";

char  ErrorBookshelfType [] = "Doesn't look like a bookshelf type!";

char  Utility [] = "HYPERSHELF";

boolean  Debug,
         DecwBookDefined,
         DecwBookshelfDefined,
         DoBnu,
         DoBookreader,
         ErrorReported,
         IsCgiPlus,
         StdCgiEnvironment,
         PageBegun,
         TimeAheadOfGmt,
         WasdEnvironment;

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

int  BookshelfCount,
     OdsExtended,
     PrevBookshelfCount;

char  ContentTypeCharset [64],
      DefaultButtons [] = DEFAULT_BUTTONS,
      LastModifiedGmDateTime [32],
      HyperReaderReferer [512],
      PrevTitle [256],
      ShelfPath [ODS_MAX_FILE_NAME_LENGTH+1],
      SoftwareID [48],
      TimeGmtString [32],
      TimeGmtVmsString [32],
      UriReferer [512],
      UriTitle [256],
      ShelfSpec [ODS_MAX_FILE_NAME_LENGTH+1];

char  *ButtonPtr = DefaultButtons,
      *CgiEnvironmentPtr,
      *CgiFormFilePtr,
      *CgiFormRefererPtr,
      *CgiFormTitlePtr,
      *CgiHttpCacheControlPtr,
      *CgiHttpIfModifiedSincePtr,
      *CgiHttpPragmaPtr,
      *CgiPathInfoPtr,
      *CgiPathTranslatedPtr,
      *CgiRequestMethodPtr,
      *CgiScriptNamePtr,
      *CgiServerSoftwarePtr,
      *CharsetPtr,
      *CliCharsetPtr,
      *HyperReaderScriptNamePtr,
      *IconLocationPtr = DEFAULT_ICON,
      *LibraryFileNamePtr = "",
      *PrintScriptNamePtr = ""; 

/* required function prototypes */
char* VmsToPath (char*, char*);

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

main
(
int argc,
char *argv[]
)
{
   /*********/
   /* begin */
   /*********/

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

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

   CgiLibEnvironmentInit (argc, argv, false);

   WasdEnvironment = CgiLibEnvironmentIsWasd();

   GetParameters ();
   SetPageScheme ();

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

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

   /* if not CLI set and old bookreader logicals then bookreader */
   DecwBookDefined = (boolean)(getenv ("DECW$BOOK"));
   DecwBookshelfDefined = (boolean)(getenv ("DECW$BOOKSHELF"));
   if (!(DoBnu || DoBookreader) && (DecwBookDefined || DecwBookshelfDefined))
   {
      DoBookreader = true;
      DoBnu = false;
   }
   else
   {
      DoBnu = true;
      DoBookreader = false;
   }
   if (Debug)
      fprintf (stdout,
"DecwBookDefined: %d DecwBookshelfDefined: %d DoBookreader: %d DoBnu: %d\n",
         DecwBookDefined, DecwBookshelfDefined, DoBookreader, DoBnu);

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

   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 ("HYPERSHELF$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, "/DBUG", -1))
      {
         Debug = true;
         continue;
      }
      if (strsame (aptr, "/BNU", -1))
      {
         DoBookreader = false;
         DoBnu = true;
         continue;
      }
      if (strsame (aptr, "/BOOKREADER", 4))
      {
         DoBookreader = true;
         DoBnu = false;
         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, "/HYPERREADER=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         HyperReaderScriptNamePtr = cptr;
         continue;
      }
      if (strsame (aptr, "/ICON=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         IconLocationPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/LIBRARY=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         LibraryFileNamePtr = cptr;
         continue;
      }
      if (strsame (aptr, "/ODS5", 5))
      {
         OdsExtended = true;
         continue;
      }
      if (strsame (aptr, "/NOODS5", 7))
      {
         OdsExtended = false;
         continue;
      }
      if (strsame (aptr, "/PRINT=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         PrintScriptNamePtr = cptr;
         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  *cptr, *sptr,
         *PathPtr;

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

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

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

   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;\n");
      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");
   }
}

/*****************************************************************************/
/*
*/ 
 
ProcessRequest ()

{
   int  status,
        idx;
   char  c;
   char  *cptr, *sptr,
         *ShelfFilePtr;
   char  ExpFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         ResFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         Scratch [256],
         SearchDefault [ODS_MAX_FILE_NAME_LENGTH+1];
   struct FAB  SearchFab;
#ifdef ODS_EXTENDED
   struct NAML  SearchNaml;
#endif /* ODS_EXTENDED */
   struct NAM  SearchNam;

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

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

   CgiEnvironmentPtr = CgiLibEnvironmentName ();

   ErrorReported = false;

   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");
   CgiHttpCacheControlPtr = CgiLibVar ("WWW_HTTP_CACHE_CONTROL");
   CgiHttpPragmaPtr = CgiLibVar ("WWW_HTTP_PRAGMA");

   CgiFormTitlePtr = CgiLibVar ("WWW_FORM_TITLE");
   CgiFormFilePtr = CgiLibVar ("WWW_FORM_FILE");
   if (!CgiFormFilePtr[0]) CgiFormFilePtr = CgiLibVar ("WWW_FORM_SHELF");

   if (VMSnok (status = TimeSetGmt ()))
   {
      if (status != SS$_NOLOGNAM)
      {
         CgiLibResponseError (FI_LI, status, "GMT offset");
         return;
      }
   }
   CgiHttpIfModifiedSincePtr = CgiLibVar ("WWW_HTTP_IF_MODIFIED_SINCE");
   if (CgiHttpIfModifiedSincePtr[0])
   {
      if (VMSnok (HttpGmTime (CgiHttpIfModifiedSincePtr, 
                              &IfModifiedSinceBinaryTime)))
      {
         if (Debug) fprintf (stdout, "If-Modified-Since: NBG!\n");
         IfModifiedSinceBinaryTime[0] = IfModifiedSinceBinaryTime[0] = 0;
         CgiHttpIfModifiedSincePtr = "";
      }
   }

   if (!HyperReaderScriptNamePtr)
   {
      if (CgiLibEnvironmentIsWasd() ||
          CgiLibEnvironmentIsCgiPlus())
         HyperReaderScriptNamePtr = DEFAULT_WASD_HYPERREADER;
      else
      if (CgiLibEnvironmentIsOsu())
         HyperReaderScriptNamePtr = DEFAULT_OSU_HYPERREADER;
      else
         HyperReaderScriptNamePtr = DEFAULT_CGI_HYPERREADER;
   }

   if (CgiPathInfoPtr[0] == '/' && !CgiPathInfoPtr[1])
      CgiPathInfoPtr = CgiPathTranslatedPtr = "";

   if (!CgiFormTitlePtr[0]) CgiFormTitlePtr = "Library";

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

   ShelfSpec[0] = '\0';
   if (!ShelfSpec[0]) strcpy (ShelfSpec, CgiFormFilePtr);
   if (!ShelfSpec[0]) strcpy (ShelfSpec, CgiPathTranslatedPtr);
   if (!ShelfSpec[0]) strcpy (ShelfSpec, LibraryFileNamePtr);
   if (Debug) fprintf (stdout, "ShelfSpec |%s|\n", ShelfSpec);

   if (ShelfSpec[0])
   {
      /* find file type (extension) */
      for (cptr = ShelfSpec; *cptr; cptr++);
      if (*(cptr-1) == ';') *(cptr-1) = '\0';
      while (cptr > ShelfSpec && *cptr != '.' &&
             *cptr != ']' && *cptr != ':') cptr--;
      if (*cptr == '.')
      {
         /* check if looks like a bookshelf file type */
         for (idx = 0; BookshelfTypes[idx][0]; idx++)
         {
            if (Debug) fprintf (stdout, "|%s|%s|\n", BookshelfTypes[idx], cptr);
            if (strsame (BookshelfTypes[idx], cptr, -1)) break;
         }
         if (!BookshelfTypes[idx][0])
         {
            CgiLibResponseError (FI_LI, 0, ErrorBookshelfType);
            return;
         }
      }
   }

   PageBegun = false;
   BookshelfCount = PrevBookshelfCount = 0;

   if (DoBookreader)
   {
      /**************/
      /* Bookreader */
      /**************/

      idx = 0;
      for (;;)
      {
         SearchFab = cc$rms_fab;

         if (ShelfSpec[0])
         {
            SearchFab.fab$l_dna = "DECW$BOOK:.DECW$BOOKSHELF";
            SearchFab.fab$b_dns = 25;
         }
         else
         {
            if (DecwBookshelfDefined)
            {
               SysTrnLnm ("DECW$BOOKSHELF", idx++, SearchDefault);
               if (!SearchDefault[0]) break;
            }
            else
            {
               SysTrnLnm ("DECW$BOOK", idx++, SearchDefault);
               if (!SearchDefault[0]) break;
               for (;;)
               {
                  SysTrnLnm (SearchDefault, 0, Scratch);
                  if (!Scratch[0]) break;
                  strcpy (SearchDefault, Scratch);
               }
               strcat (SearchDefault,"LIBRARY.*;");
            }
            SearchFab.fab$l_dna = SearchDefault;
            SearchFab.fab$b_dns = strlen(SearchDefault);
         }

         SearchFab.fab$b_shr = FAB$M_SHRGET;

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

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

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

         status = sys$parse (&SearchFab, 0, 0);
         if (Debug) fprintf (stdout, "sys$parse() %%X%08.08X\n", status);
         if (VMSnok (status))
         {
            /* subsequent translations ignore directories not being there! */
            if (status == RMS$_DNF && idx > 0) continue;

            if (ShelfSpec[0])
               cptr = ShelfSpec;
            else
               cptr = SearchFab.fab$l_dna;

            CgiLibResponseError (FI_LI, status, cptr);

            return (status);
         }

#ifdef ODS_EXTENDED
         if (OdsExtended)
            SearchNaml.naml$l_long_ver[SearchNaml.naml$l_long_ver_size] = '\0';
         else
#endif /* ODS_EXTENDED */
            SearchNam.nam$l_ver[SearchNam.nam$b_ver] = '\0';
         if (Debug)
            fprintf (stdout, "ExpFileName |%s|\n", ExpFileName);

         status = sys$search (&SearchFab, 0, 0);
         if (Debug) fprintf (stdout, "sys$search() %%X%08.08X\n", status);
         if (VMSok (status))
         {
#ifdef ODS_EXTENDED
            if (OdsExtended)
               *SearchNaml.naml$l_long_ver = '\0';
            else
#endif /* ODS_EXTENDED */
               *SearchNam.nam$l_ver = '\0';
            if (Debug) fprintf (stdout, "ResFileName |%s|\n", ResFileName);

            status = ProcessShelf (ResFileName);
            if (Debug) fprintf (stdout, "ProcessShelf() %%X%08.08X\n", status);
         }

         if (status == RMS$_NMF) status = SS$_NORMAL;
         if (VMSnok (status))
         {
            if (ShelfSpec[0])
               cptr = ShelfSpec;
            else
               cptr = SearchFab.fab$l_dna;
            CgiLibResponseError (FI_LI, status, cptr);
            return (SS$_NORMAL);
         }

         /* continue to go through this rigmoral only if no shelf specified! */
         if (ShelfSpec[0]) break;
      }
   }
   else
   {
      /*******/
      /* BNU */
      /*******/

      ShelfFilePtr = OdlShelfDefaults;
      while (*ShelfFilePtr)
      {
         cptr = ShelfFilePtr;
         while (*cptr == ' ' || *cptr == ',') cptr++;
         if (!*cptr) break;
         if (Debug) fprintf (stdout, "cptr |%s|\n", cptr);
         ShelfFilePtr = cptr;
         while (*cptr && *cptr != ' ' && *cptr != ',') cptr++;
         c = *cptr;

         SearchFab = cc$rms_fab;
         SearchFab.fab$l_dna = ShelfFilePtr;
         SearchFab.fab$b_dns = cptr - ShelfFilePtr;
         SearchFab.fab$b_shr = FAB$M_SHRGET;

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

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

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

         if (VMSnok (status = sys$parse (&SearchFab, 0, 0)))
         {
            if (ShelfSpec[0])
               CgiLibResponseError (FI_LI, status, ShelfSpec);
            else
               CgiLibResponseError (FI_LI, status, ShelfFilePtr);
            *cptr = c;
            return (status);
         }

         *(ShelfFilePtr = cptr) = c;

#ifdef ODS_EXTENDED
         if (OdsExtended)
            SearchNaml.naml$l_long_ver[SearchNaml.naml$l_long_ver_size] = '\0';
         else
#endif /* ODS_EXTENDED */
            SearchNam.nam$l_ver[SearchNam.nam$b_ver] = '\0';
         if (Debug)
            fprintf (stdout, "ExpFileName |%s|\n", ExpFileName);

         status = sys$search (&SearchFab, 0, 0);
         if (Debug) fprintf (stdout, "sys$search() %%X%08.08X\n", status);
         if (VMSok (status))
         {
#ifdef ODS_EXTENDED
            if (OdsExtended)
               *SearchNaml.naml$l_long_ver = '\0';
            else
#endif /* ODS_EXTENDED */
               *SearchNam.nam$l_ver = '\0';
            if (Debug) fprintf (stdout, "ResFileName |%s|\n", ResFileName);

            status = ProcessShelf (ResFileName);
            if (Debug) fprintf (stdout, "ProcessShelf() %%X%08.08X\n", status);
            break;
         }

         /* try with the next combination of defaults (if any) */
         if (status == RMS$_FNF) continue;
         if (status == RMS$_DNF) continue;
         break;
      }

      if (!*ShelfFilePtr)
      {
         status = RMS$_FNF;
         CgiLibResponseError (FI_LI, status, OdlShelfDefaults);
         return (status);
      }
      else
      {
         if (VMSnok (status))
         {
            if (ShelfSpec[0])
               cptr = ShelfSpec;
            else
               cptr = ExpFileName;
            CgiLibResponseError (FI_LI, status, cptr);
            return (status);
         }
      }
   }

   /* release any parse and search internal data structures (for CGIplus) */
   SearchFab.fab$l_fna = "a:[b]c.d;";
   SearchFab.fab$b_fns = 9;
   SearchFab.fab$b_dns = 0;
#ifdef ODS_EXTENDED
   if (OdsExtended)
      SearchNaml.naml$b_nop = NAM$M_SYNCHK;
   else
#endif /* ODS_EXTENDED */
      SearchNam.nam$b_nop = NAM$M_SYNCHK;
   sys$parse (&SearchFab, 0, 0);

   if (BookshelfCount) fprintf (stdout, "</TABLE>\n");
   if (VMSok (status) && BookshelfCount)
   {
      ButtonBar (2);
      fprintf (stdout, "</BODY>\n</HTML>\n");
   }
}

/*****************************************************************************/
/*
Open the supplied shelf file name.  Read each line in the  file parsing the
entry type (shelf or book), the file specification, and entry  description
(title of shelf or book).  Close the file.
*/ 
 
int ProcessShelf (char* ShelfFileName)

{
   boolean  IsDecwShelf,
            IsOdlShelf,
            IsSameAsPrevTitle;
   int  status,
        idx,
        LineCount,
        LineOutputCount;
   char  ExpFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         FileName [ODS_MAX_FILE_NAME_LENGTH+1],
         HtmlTitle [256],
         Line [256],
         LinkPath [ODS_MAX_FILE_NAME_LENGTH+1],
         Scratch [ODS_MAX_FILE_NAME_LENGTH*2],
         ShelfDirectory [ODS_MAX_FILE_NAME_LENGTH+1],
         Title [256],
         Type [256],
         VmsLinkPath [ODS_MAX_FILE_NAME_LENGTH+1];
   char  *cptr, *sptr, *zptr,
         *ExpDevicePtr,
         *ExpNodePtr,
         *ExpNamePtr,
         *ExpTypePtr,
         *PossibleOdlShelfTitlePtr;
   struct FAB  ShelfFab;
#ifdef ODS_EXTENDED
   struct NAML  ShelfNaml;
#endif /* ODS_EXTENDED */
   struct NAM  ShelfNam;
   struct RAB  ShelfRab;
   struct XABDAT  ShelfXabDat;

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

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

   ShelfFab = cc$rms_fab;
   ShelfFab.fab$b_fac = FAB$M_GET;
   ShelfFab.fab$b_shr = FAB$M_SHRGET;
   ShelfFab.fab$l_xab = &ShelfXabDat;
   ShelfXabDat = cc$rms_xabdat;

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

      ENAMEL_RMS_NAML(ShelfNaml)
      ShelfNaml.naml$l_long_filename = ShelfFileName;
      ShelfNaml.naml$l_long_filename_size = strlen(ShelfFileName);
      ShelfNaml.naml$l_long_expand = ExpFileName;
      ShelfNaml.naml$l_long_expand_alloc = sizeof(ExpFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      ShelfFab.fab$l_fna = ShelfFileName;  
      ShelfFab.fab$b_fns = strlen(ShelfFileName);
      ShelfFab.fab$l_nam = &ShelfNam;

      ShelfNam = cc$rms_nam;
      ShelfNam.nam$l_esa = ExpFileName;
      ShelfNam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
   }

   status = sys$open (&ShelfFab, 0, 0);

   if (VMSnok (status))
   {
      if (Debug) fprintf (stdout, "sys$open() %%X%08.08X\n", status);
      if (ShelfSpec[0])
         cptr = ShelfSpec;
      else
         cptr = ShelfFileName;
      CgiLibResponseError (FI_LI, status, cptr);
      return (status);
   }

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      ExpNodePtr = ShelfNaml.naml$l_long_node;
      ExpDevicePtr = ShelfNaml.naml$l_long_dev;
      ExpNamePtr = ShelfNaml.naml$l_long_name;
      ExpTypePtr = ShelfNaml.naml$l_long_type;
      *ShelfNaml.naml$l_long_ver = '\0';
   }
   else
#endif /* ODS_EXTENDED */
   {
      ExpNodePtr = ShelfNam.nam$l_node;
      ExpDevicePtr = ShelfNam.nam$l_dev;
      ExpNamePtr = ShelfNam.nam$l_name;
      ExpTypePtr = ShelfNam.nam$l_type;
      *ShelfNam.nam$l_ver = '\0';
   }
   if (Debug) fprintf (stdout, "|%s|\n", ExpFileName);

   /* don't last-modified check if using DECW$BOOK to search for libraries */
   if ((ShelfSpec[0] ||
        DoBnu) &&
       !DecwBookshelfDefined)
   {
      if (CgiHttpIfModifiedSincePtr[0])
      {
         if (VMSnok (ModifiedSince (&IfModifiedSinceBinaryTime,
                                    &ShelfXabDat.xab$q_rdt)))
         {
            /* book has not been modified since the date/time, don't send */
            sys$close (&ShelfFab, 0, 0);
            return (STS$K_ERROR);
         }
      }
   }

   HttpGmTimeString (LastModifiedGmDateTime, &ShelfXabDat.xab$q_rdt);

   ShelfRab = cc$rms_rab;
   ShelfRab.rab$l_fab = &ShelfFab;
   /* 2 buffers and read ahead performance option */
   ShelfRab.rab$b_mbf = 2;
   ShelfRab.rab$l_rop = RAB$M_RAH;
   ShelfRab.rab$l_ubf = Line;
   ShelfRab.rab$w_usz = sizeof(Line)-1;

   if (VMSnok (status = sys$connect (&ShelfRab, 0, 0)))
   {
      if (Debug) fprintf (stdout, "sys$connect() %%X%08.08X\n", status);
      sys$close (&ShelfFab, 0, 0);
      if (ShelfSpec[0])
         cptr = ShelfSpec;
      else
         cptr = ShelfFileName;
      CgiLibResponseError (FI_LI, status, cptr);
      return (status);
   }

   /* convert the VMS file path into a slash-separated, sort-of equivalent */
   VmsToPath (ShelfPath, ExpFileName);

   if (Debug) fprintf (stdout, "|%s|\n", ExpTypePtr);
   IsDecwShelf = IsOdlShelf = false;
   if (!strcmp (ExpTypePtr, ".ODL"))
      IsOdlShelf = true;
   else
      IsDecwShelf = true;

   if (++BookshelfCount == 1)
   {
      /********************/
      /* first/only shelf */
      /********************/

      sptr = HyperReaderReferer;
      sptr += CgiLibUrlEncode (CgiScriptNamePtr, -1, sptr, -1);
      if (CgiPathInfoPtr[0] != '/') *sptr++ = '/';
      sptr += CgiLibUrlEncode (CgiPathInfoPtr, -1, sptr, -1);
      sptr += CgiLibUrlEncode ("?title=", -1, sptr, -1);
      /*
         This escapes the escape percentages leaving the URI still escaped
         when the script (HyperReader) receives it at the other end :^)
      */
      CgiLibUrlEncode (CgiFormTitlePtr, -1, Scratch, -1);
      sptr += CgiLibUrlEncode (Scratch, -1, sptr, -1);
      sptr += CgiLibUrlEncode ("&referer=", -1, sptr, -1);
      sptr += CgiLibUrlEncode (UriReferer, -1, sptr, -1);
      *sptr = '\0';
      if (Debug)
         fprintf (stdout, "HyperReaderReferer |%s|\n", HyperReaderReferer);

      /***************/
      /* HTTP header */
      /***************/

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

   /****************/
   /* file records */
   /****************/

   LineCount = LineOutputCount = 0;
   HtmlTitle[0] = PrevTitle[0] = UriTitle[0] = '\0';

   while (VMSok (status = sys$get (&ShelfRab, 0, 0)))
   {
      LineCount++;
      Line[ShelfRab.rab$w_rsz] = '\0';
      if (Debug) fprintf (stdout, "Line |%s|\n", Line);
      if (!Line[0]) continue;

      /********************/
      /* parse components */
      /********************/

      cptr = Line;
      if ((cptr[0] == '#' && cptr[1] == '#' && isalpha(cptr[2])) ||
          (cptr[0] == '!' && cptr[1] == '!' && isalpha(cptr[2])))
         cptr += 2;

      /* ignore unknown line types */
      if (!isalpha(*cptr)) continue;

      Type[0] = FileName[0] = Title[0] = '\0';

      sptr = Type;
      while (*cptr && isspace(*cptr)) cptr++;
      if (IsOdlShelf)
      {
         while (*cptr && !isspace(*cptr)) *sptr++ = toupper(*cptr++);
         *sptr = '\0';
         while (*cptr && isspace(*cptr)) cptr++;
      }
      else
      {
         while (*cptr && *cptr != '\\' && !isspace(*cptr))
            *sptr++ = toupper(*cptr++);
         *sptr = '\0';
         while (*cptr && isspace(*cptr)) cptr++;
         if (*cptr == '\\') cptr++;
         while (*cptr && isspace(*cptr)) cptr++;
      }

      PossibleOdlShelfTitlePtr = cptr;
      if (IsOdlShelf)
      {
         sptr = FileName;
         while (*cptr && !isspace(*cptr))
         {
            *sptr++ = *cptr++;
            if (*cptr == '%')
            {
               *sptr++ = '2';
               *sptr++ = '5';
            }
         }
         *sptr = '\0';
         while (*cptr && isspace(*cptr)) cptr++;
      }
      else
      {
         /* check if this specification has device and directory components */
         for (zptr = cptr;
              *zptr && *zptr != ':' && *zptr != '[' && *zptr != '/' &&
                 *zptr != '\\' && !isspace(*zptr);
              zptr++);
         if (*zptr == ':' || *zptr == '[' || *zptr == '/')
         {
            /* includes directory components, straight-up copy */
            sptr = FileName;
            while (*cptr && *cptr != '\\' && !isspace(*cptr))
               *sptr++ = tolower(*cptr++);
            *sptr = '\0';
         }
         else
         {
            /* use the device and directory of the current shelf */
            sptr = FileName;
            for (zptr = ExpNodePtr;
                 zptr < ExpNamePtr;
                 *sptr++ = tolower(*zptr++));
            while (*cptr && *cptr != '\\' && !isspace(*cptr))
               *sptr++ = tolower(*cptr++);
            *sptr = '\0';
         }
         while (*cptr && isspace(*cptr)) cptr++;
         if (*cptr == '\\') cptr++;
         while (*cptr && isspace(*cptr)) cptr++;
      }

      sptr = Title;
      while (*cptr) *sptr++ = *cptr++;
      *sptr = '\0';

      /***************/
      /* shelf title */
      /***************/

      if (Type[0] == 'T' && strsame (Type, "TITLE", -1))
      {
         /* title of this shelf */
         if (IsOdlShelf)
         {
            sptr = Title;
            for (cptr = PossibleOdlShelfTitlePtr; *cptr; *sptr++ = *cptr++);
            *sptr = '\0';
            cptr = "ODL";
         }
         else
            cptr = "DECW$BOOKSHELF";
         if (!Title[0])
            sprintf (Title, "[Unable to parse %s shelf title (line %d)]",
                     cptr, LineCount);
         
         if (Debug) fprintf (stdout, "TITLE |%s|\n", Title);

         if (!HtmlTitle[0])
         {
            CgiLibHtmlEscape (Title, -1, HtmlTitle, -1);
            CgiLibUrlEncode (Title, -1, UriTitle, -1);
         }
      }

      if (BookshelfCount == 1)
      {
         /***********************************/
         /* first/only shelf ... page title */
         /***********************************/

         if (!PageBegun)
         {
            BeginPage (HtmlTitle);
            PageBegun = true;
         }
      }

      if (BookshelfCount == 1 && BookshelfCount != PrevBookshelfCount)
      {
         /* note that we are now working on a new bookshelf file */
         PrevBookshelfCount = BookshelfCount;
         fprintf (stdout, "<!-- %s -->\n", ExpFileName);
      }

      if (BookshelfCount > 1)
      {
         /* Bookreader type search list through libraries */
         if (Type[0] == 'T' && strsame (Type, "TITLE", -1)) break;
         continue;
      }

      if (Type[0] == 'T' && strsame (Type, "TITLE", -1)) continue;


      /*********************/
      /* shelf or document */
      /*********************/

      CgiLibHtmlEscape (Title, -1, HtmlTitle, -1);
      CgiLibUrlEncode (Title, -1, UriTitle, -1);

      if (strsame (Title, PrevTitle, -1) || strsame (Title, "ditto", 5))
         IsSameAsPrevTitle = true;
      else
         IsSameAsPrevTitle = false;

      LinkPath[0] = VmsLinkPath[0] = '\0';
      if (FileName[0])
      {
         if (IsOdlShelf)
         {
            /*************/
            /* ODL shelf */
            /*************/

            if (FileName[0] == '%')
            {
               /* full path was specified */
               sptr = LinkPath;
               for (cptr = "/disk$"; *cptr; *sptr++ = *cptr++);
               for (cptr = FileName+1; *cptr; *sptr++ = tolower(*cptr++));
               *sptr = '\0';
            }
            else
            if (FileName[0] == '/')
            {
               /* full path was specified */
               sptr = LinkPath;
               for (cptr = FileName; *cptr; *sptr++ = tolower(*cptr++));
               *sptr = '\0';
            }
            else
            {
               sptr = LinkPath;
               for (cptr = CgiPathInfoPtr; *cptr; *sptr++ = *cptr++);
               *sptr = '\0';
               while (*sptr != '/' && sptr > LinkPath) sptr--;
               if (*sptr == '/') sptr++;
               for (cptr = FileName; *cptr; *sptr++ = tolower(*cptr++));
               *sptr = '\0';
            }
            if (Debug) fprintf (stdout, "LinkPath |%s|\n", LinkPath);
         }
         else
         {
            /************************/
            /* DECW$BOOKSHELF shelf */
            /************************/

            strcpy (VmsLinkPath, FileName);
         }
      }

      if (Debug)
         fprintf (stdout, "|%s|%s|%s|\n", FileName, LinkPath, VmsLinkPath);

      /*******************/
      /* output the HTML */
      /*******************/

      LineOutputCount++;

      fprintf (stdout, "<TR><TD><NOBR>\n");

      if (!Type[0] || !FileName[0])
         fprintf (stdout, "ERROR-IN-SHELF-ENTRY: Line %d", LineCount);
      else
      if (Type[0] == 'S' && strsame (Type, "SHELF", -1))
      {
         /*********/
         /* shelf */
         /*********/

         if (IsOdlShelf)
            ItemLink (UriTitle, CgiScriptNamePtr, LinkPath, ".odl",
                      "shelf.gif", " _|\\_");
         else
            ItemLink (UriTitle, CgiScriptNamePtr, VmsLinkPath,
                      ".decw$bookshelf", "shelf.gif", "_|\\_");
      }
      else
      if (Type[0] == 'B' && strsame (Type, "BOOK", -1))
      {
         /********/
         /* book */
         /********/

         if (!Title[0] || IsSameAsPrevTitle)
            strcpy (HtmlTitle, "<FONT SIZE=-1><I>(Bookreader version)</I></FONT>");
                 
         if (IsOdlShelf)
            ItemLink (UriTitle, HyperReaderScriptNamePtr, LinkPath,
                      ".bkb", "book.gif", "[}{]");
         else
            ItemLink (UriTitle, HyperReaderScriptNamePtr, VmsLinkPath,
                      ".decw$book", "book.gif", "[}{]");
      }
      else
      if (Type[0] == 'H' && strsame (Type, "HTML", -1))
      {
         /****************/
         /* HTML version */
         /****************/

         if (!Title[0] || IsSameAsPrevTitle)
            strcpy (HtmlTitle, "<FONT SIZE=-1><I>(HTML version)</I></FONT>");

         /* convert the VMS file path into a sort-of URL equivalent */
         if (VmsLinkPath[0]) VmsToPath (LinkPath, VmsLinkPath);

         if (IsOdlShelf) cptr = ".htm"; else cptr = ".html";
         ItemLink (NULL, "", LinkPath, cptr, "html.gif", "[htm]");
      }
      else
      if (Type[0] == 'P'  && strsame (Type, "PDF", -1))
      {
         /*********************************************/
         /* Adobe PDF version (HyperReader extension) */
         /*********************************************/

         if (!Title[0] || IsSameAsPrevTitle)
            strcpy (HtmlTitle, "<FONT SIZE=-1><I>(PDF version)</I></FONT>");

         /* convert the VMS file path into a sort-of URL equivalent */
         if (VmsLinkPath[0]) VmsToPath (LinkPath, VmsLinkPath);

         ItemLink (NULL, "", LinkPath, ".pdf", "pdf.gif", "[pdf]");
      }
      else
      if (Type[0] == 'P'  && strsame (Type, "PLAIN", -1))
      {
         /**********************************************/
         /* plain-text version (HyperReader extension) */
         /**********************************************/

         if (!Title[0] || IsSameAsPrevTitle)
            strcpy (HtmlTitle,
                    "<FONT SIZE=-1><I>(plain-text version)</I></FONT>");

         /* convert the VMS file path into a sort-of URL equivalent */
         if (VmsLinkPath[0]) VmsToPath (LinkPath, VmsLinkPath);

         ItemLink (NULL, "", LinkPath, ".txt", "plain.gif", "[txt]");

         if (PrintScriptNamePtr[0])
            ItemLink (NULL, PrintScriptNamePtr, LinkPath, ".txt",
                      "print.gif", "[prn]");
      }
      else
      if ((Type[0] == 'P' && strsame (Type, "POST", 4)) ||
          (Type[0] == 'P' && strsame (Type, "PS", -1)))
      {
         /**********************************************/
         /* PostScript version (HyperReader extension) */
         /**********************************************/

         if (!Title[0] || IsSameAsPrevTitle)
            strcpy (HtmlTitle, "<FONT SIZE=-1><I>(PostScript version)</I></FONT>");

         /* convert the VMS file path into a sort-of URL equivalent */
         if (VmsLinkPath[0]) VmsToPath (LinkPath, VmsLinkPath);

         ItemLink (NULL, "", LinkPath, ".ps", "ps.gif", "[.ps]");

         if (PrintScriptNamePtr[0])
            ItemLink (NULL, PrintScriptNamePtr, LinkPath, ".ps",
                      "print.gif", "[prn]");
      }
      else
      if (Type[0] == 'U'  && strsame (Type, "URL", -1))
      {
         /*******************************************/
         /* URL (HTML link - HyperReader extension) */
         /*******************************************/

         if (!Title[0]) strcpy (HtmlTitle, FileName);

         ItemLink (NULL, "", FileName, NULL, "url.gif", "[url]");
      }
      else
      {
         /*****************/
         /* unknown agent */
         /*****************/

         if (!Title[0] || IsSameAsPrevTitle)
            strcpy (HtmlTitle, "<FONT SIZE=-1><I>(ditto)</I></FONT>");

         ItemLink (UriTitle, "", LinkPath, "", "unknown.gif", "[???]");
      }

      fprintf (stdout, "</NOBR></TD><TD>&nbsp;%s</TD></TR>\n", HtmlTitle);

      strcpy (PrevTitle, Title);
   }

   /****************/
   /* end of shelf */
   /****************/

   sys$close (&ShelfFab, 0, 0);

   if (status == RMS$_EOF) status = SS$_NORMAL;
   if (VMSnok (status))
   {
      CgiLibHtmlEscape (CgiFormTitlePtr, -1, HtmlTitle, -1);
      if (HtmlTitle[0])
         cptr = HtmlTitle;
      else
         cptr = CgiPathInfoPtr;
      CgiLibResponseError (FI_LI, status, cptr);
      return (status);
   }

   if (BookshelfCount > 1)
   {
      /************************************/
      /* bookreader search list libraries */
      /************************************/

      if (BookshelfCount != PrevBookshelfCount)
      {
         /* note that we are now working on a new bookshelf file */
         PrevBookshelfCount = BookshelfCount;
         fprintf (stdout, "<!-- %s -->\n", ExpFileName);
      }

      if (LineCount)
      {
         fprintf (stdout, "<TR><TD><NOBR>\n");
         if (IsOdlShelf)
            ItemLink (UriTitle, CgiScriptNamePtr,
                      VmsToPath(NULL, ShelfFileName), ".odl",
                      "shelf.gif", " _|\\_");
         else
         {
            for (cptr = ShelfFileName; *cptr; cptr++) *cptr = tolower(*cptr);
            ItemLink (UriTitle, CgiScriptNamePtr, ShelfFileName,
                      ".decw$bookshelf", "shelf.gif", "_|\\_");
         }
         if (HtmlTitle[0])
            fprintf (stdout,
"</NOBR></TD><TD>&nbsp;%s\
 &nbsp;&nbsp;<FONT SIZE=-1>[library]</FONT></TD></TR>\n",
                     HtmlTitle);
         else
            fprintf (stdout,
"</NOBR></TD><TD>&nbsp;Library %d\
 &nbsp;&nbsp;<FONT SIZE=-1>[library]</FONT></TD></TR>\n",
                     BookshelfCount);
      }
      else
         fprintf (stdout,
            "<TR><TD></TD><TD>&nbsp;Empty bookshelf: <TT>%s</TT></TD></TR>\n",
            VmsToPath (NULL, ShelfFileName));
   }
   else
   if (!LineOutputCount)
   {
      if (!PageBegun)
      {
         BeginPage (HtmlTitle);
         PageBegun = true;
      }

      if (BookshelfCount != PrevBookshelfCount)
      {
         /* note that we are now working on a new bookshelf file */
         PrevBookshelfCount = BookshelfCount;
         fprintf (stdout, "<!-- %s -->\n", ExpFileName);
      }

      fprintf (stdout,
         "<TR><TD></TD><TD>&nbsp;Empty bookshelf: <TT>%s</TT></TD></TR>\n",
         VmsToPath (NULL, ShelfFileName));
   }

   return (SS$_NORMAL);
}

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

BeginPage (char *HtmlTitle)

{
   char  *cptr, *ShelfModePtr;
   char  UnixDateTime [32];
   unsigned long  UnixTime;
   struct tm  *UnixTmPtr;

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

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

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

   if (!HtmlTitle[0]) CgiLibHtmlEscape (CgiFormTitlePtr, -1, HtmlTitle, -1);

   if (DoBnu) ShelfModePtr = "BNU"; else ShelfModePtr = "Bookreader";

   fprintf (stdout,
"<HTML>\n\
<HEAD>\n\
<META NAME=\"generator\" CONTENT=\"%s\">\n\
<META NAME=\"date\" CONTENT=\"%s\">\n\
<META NAME=\"environment\" CONTENT=\"%s\">\n\
<META NAME=\"mode\" CONTENT=\"%s\">\n\
<TITLE>HyperShelf ... %s</TITLE>\n\
</HEAD>\n\
<BODY%s>\n",
      SoftwareID, UnixDateTime, CgiEnvironmentPtr, ShelfModePtr,
      HtmlTitle, 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>HyperReader</U>\n\
</FONT>\n\
<P>\n",
         PageScheme[PS_HEADLOCAL],
         HtmlTitle);
   }
   else
   {
      fprintf (stdout,
"<TABLE BORDER=%s CELLPADDING=%s CELLSPACING=0 WIDTH=100%%%s>\n",
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADPADDING],
         PageScheme[PS_HEADBGCOLOR]);

      fprintf (stdout,
"<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>HyperReader</U>\n\
</FONT>\n\
</TD>%s</TR>\n\
</TABLE>\n",
      PageScheme[PS_HEADTEXT],
      HtmlTitle,
      PageScheme[PS_HEADTEXT],
      PageScheme[PS_HEADLOCAL]);
   }
   fflush (stdout);

   ButtonBar (1);

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

/*****************************************************************************/
/*
Create a link for a single shelf item.
*/ 

ItemLink
(
char *UriTitle,
char *ScriptNamePtr,
char *LinkPath,
char *DefaultType,
char *IconName,
char *AltText
)
{
   char  *cptr;
   char EncodedLinkPath [ODS_MAX_FILE_NAME_LENGTH+64+1];

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

   if (Debug)
      fprintf (stdout, "ItemLink() |%s|%s|%s|%s|%s|%s|\n",
               UriTitle, ScriptNamePtr, LinkPath, DefaultType,
               IconName, AltText);

   if (!DefaultType)
   {
      /* default file type is specified as NULL, must be a raw URL */
      fprintf (stdout,
"<A HREF=\"%s\"><IMG SRC=\"%s%s\" ALIGN=top ALT=\"%s\"></A>\n",
         LinkPath, IconLocationPtr, IconName, AltText);
      return;
   }

   /* look for file type in specification */
   for (cptr = LinkPath; *cptr; cptr++);
   while (cptr > LinkPath && *cptr != '.'
          && *cptr != '/' && *cptr != ']' && *cptr != ':')
      cptr--;
   if (*cptr != '.')
   {
      /* no file type supplied, default to one! */
      while (*cptr) cptr++;
      strcat (LinkPath, DefaultType);
   }
   /* now scan backwards to check for likely VMS specification */
   while (cptr > LinkPath && *cptr != '/' && *cptr != ']' && *cptr != ':')
      cptr--;

   if (*cptr == ']' || *cptr == ':')
   {
      /* link has VMS file specification */
      CgiLibUrlEncodeFileName (LinkPath,
                               EncodedLinkPath, sizeof(EncodedLinkPath),
                               true, true);

      if (!UriTitle)
      {
         fprintf (stdout,
"<A HREF=\"%s?file=%s\"><IMG SRC=\"%s%s\" ALIGN=top ALT=\"%s\"></A>\n",
            ScriptNamePtr, EncodedLinkPath,
            IconLocationPtr, IconName, AltText);
      }
      else
      {
         fprintf (stdout,
"<A HREF=\"%s?file=%s&title=%s&referer=%s\">\
<IMG SRC=\"%s%s\" ALIGN=top ALT=\"%s\"></A>\n",
            ScriptNamePtr, EncodedLinkPath, UriTitle, UriReferer,
            IconLocationPtr, IconName, AltText);
      }
   }
   else
   {
      /* link has URL-style path */
      CgiLibUrlEncode (LinkPath, -1,
                       EncodedLinkPath, sizeof(EncodedLinkPath));

      if (!UriTitle)
      {
         fprintf (stdout,
"<A HREF=\"%s%s\"><IMG SRC=\"%s%s\" ALIGN=top ALT=\"%s\"></A>\n",
            ScriptNamePtr, EncodedLinkPath,
            IconLocationPtr, IconName, AltText);
      }
      else
      {
         fprintf (stdout,
"<A HREF=\"%s%s?title=%s&referer=%s\">\
<IMG SRC=\"%s%s\" ALIGN=top ALT=\"%s\"></A>\n",
            ScriptNamePtr, EncodedLinkPath, UriTitle, UriReferer,
            IconLocationPtr, IconName, AltText);
      }
   }
}

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

int SysTrnLnm
(
char *LogicalName,
int IndexNumber,
char *LogicalValue
)
{
   static unsigned short  Length;
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
   static $DESCRIPTOR (LogicalNameDsc, "");
   static struct {
      short int  buf_len;
      short int  item;
      void   *buf_addr;
      unsigned short  *ret_len;
   } LnmItems [] =
   {
      { sizeof(int), LNM$_INDEX, 0, 0, },
      { 255, LNM$_STRING, 0, &Length },
      { 0,0,0,0 }
   };

   int  status;
   char  *SignPtr;

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

   if (Debug)
      fprintf (stdout, "SysTrnLnm() |%s| %d\n", LogicalName, IndexNumber);

   LogicalValue[0] = '\0';
   LogicalNameDsc.dsc$a_pointer = LogicalName;
   LogicalNameDsc.dsc$w_length = strlen(LogicalName);
   LnmItems[0].buf_addr = &IndexNumber;
   LnmItems[1].buf_addr = LogicalValue;
   status = sys$trnlnm (0, &LnmFileDevDsc, &LogicalNameDsc, 0, &LnmItems);
   if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status);
   if (VMSnok (status)) return (status);
   LogicalValue[Length] = '\0';
   if (Debug) fprintf (stdout, "LogicalValue |%s|\n", LogicalValue);
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Convert a VMS file specification into a URL-style specification.  For example:
"DEVICE:[DIR1.DIR2]FILE.TXT" into "/device/dir1/dir2/file.txt".  OSU and Apache
environments require a MFD (i.e. 000000) to remain in a Unix-stlye
specification, whereas with WASD it's optional (and usually not present).
*/ 
 
char* VmsToPath
(
char *PathPtr,
char *VmsPtr
)
{
   static char  Path [ODS_MAX_FILE_NAME_LENGTH+1];

   char  *pptr, *vptr;

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

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

   vptr = VmsPtr;
   if (!(pptr = PathPtr)) pptr = PathPtr = Path;
   *pptr++ = '/';
   /* copy the device and directory components */
   while (*vptr)
   {
      if (*vptr == ':' && *(vptr+1) == '[')
      {
         vptr++;
         vptr++;
         /* remove any reference to a Master File Directory */
         if (WasdEnvironment && strncmp (vptr, "000000", 6) == 0)
            vptr += 6;
         else
            *pptr++ = '/';
      }
      if (*vptr == '.')
      {
         if (vptr[1] == '.' && vptr[2] == '.')
         {
            *pptr++ = '/';
            *pptr++ = *vptr++;
            *pptr++ = *vptr++;
            *pptr++ = *vptr++;
         }
         else
         {
            vptr++;
            *pptr++ = '/';
         }
      }
      else
      if (*vptr == ']')
      {
         vptr++;
         *pptr++ = '/';
         break;
      }
      else
         *pptr++ = tolower(*vptr++);
   }
   /* copy the file component */
   while (*vptr) *pptr++ = tolower(*vptr++);
   *pptr++ = '\0';
   *pptr = '\0';

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

   return (PathPtr);
}

/*****************************************************************************/
/*
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, "sys$add_times()");
      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, "sys$sub_times()");
      return (status);
   }

   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;
   unsigned short  Length;
   unsigned short  NumTime [7] = { 0,0,0,0,0,0,0 };
   unsigned long  DayOfWeek;
   char  *tptr;

   /*********/
   /* 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';
      fputs (String, stdout);
   }

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

   return (status);
}

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

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

