/*****************************************************************************/
/*
                                 Query.c

A CGI-compliant script to search plain-text and HTML files on ODS-2 and ODS-5
volumes.  Extended file specifications may be expressed using either
RMS-escaped ("^_") or URL-escaped ("%nn") forbidden characters.  If a version
delimiter (';', with or without version number) is present in the path
specification then this script displays and anchors RMS-escaped and VMS syntax
file names.  If none is present it supplies URL-encoded file names.

Query works in concert with EXTRACT.C.

Accepts query-based searches (e.g. "/web/*.*?find+this+phrase") or form-based
fields briefly discussed below. By default searches the URL-supplied path, or
will use a path supplied with the "path=" query form field (via request
redirection to get the server to map the supplied path). A VMS directory
elipsis (i.e. "...") may be supplied in the path to result in a directory tree
search.

Hits in both plain-text files and HTML files provide a link to the extract
script. With plain text files the extract script extracts a section of the
file and presents it with some buttons to allow retrieving other sections or
all the document. When extracting HTML files it returns the entire document
but with each occurance of the hit enclosed by a '<A NAME="__hit1__"></A>'
anchor that allows the specific hit to be jumped to with relative document
syntax. The following tags to not have any content included: <APPLET></APPLET>,
<HEAD></HEAD>, <SCRIPT></SCRIPT>, <SERVER></SERVER>, <TITLE></TITLE>.

Using a script-name prefixed path such as "/query/web/.../*.*" returns a simple
form for generating a search.

"Text" file extensions are predefined in the DEFAULT_TEXT_TYPES and
DEFAULT_HTML_TYPES macros.  To specify a custom list use /TEXT= and /HTML= or
to add other extensions to be considered text or HTML use /ADDTEXT= and
/ADDHTML= (not this is a comma-separated list with no extension period).  File
extensions may contain the asterisk wildcard character, representing zero or
more matching characters (e.g. "REPORT_*").


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 "QUERY$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="About=/query/-/aboutquery.html"

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="About=/query/-/aboutquery.html;Other Information=/info/"

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:

  $ QUERY$PARAM = "/BUTTON=""About=/query/-/aboutquery.html"""
  $ QUERY$PARAM = QUERY$PARAM + "/PBGCOLOR/PLINK/PVLINK"
  $ QUERY$PARAM = QUERY$PARAM + "/PHLOCAL=""<TD VALIGN=top>...</TD>"""
  $ RUN HT_EXE:QUERY


NOTE ON EXTENDED FILE SPECS
---------------------------
This script is ODS-5 volume compliant and will process extended file naming
(excluding those using Unicode characters).

It's design allows building on VAX VMS (which excludes ODS-5 support) and Alpha
VMS all versions.  When building on VAX all extended ODS code is "#ifdef"ed out
for efficiency reasons.  If built under an Alpha version that does not support
extended naming the ENAMEL.H header file provides an equivalent
(a build allowed by the bogus NAML created by ENAMEL.H) then it
can only process ODS-2 file names, however if built with VMS 7.2ff it will
process both, and the object module can still be linked and used on an earlier
version (although without ODS-5 capabilities of course).

This Alpha backward/forward compatibility is provided at runtime by checking
the version of VMS it is currently executing under.  If extended file
specification compliant then NAML structures are always used, otherwise NAMs. 
Hence, a VMS version that doesn't know about extended file specifications (and
doesn't have any ODS-5 volumes of course) never gets passed a NAML!


LOGICAL NAMES
-------------
QUERY$DBUG          turns on all "if (Debug)" statements
QUERY$PARAM         equivalent to (overrides) the command line
                    parameters/qualifiers (define as a system-wide logical)


HTML FORM ELEMENTS
------------------
case=                  case sensitive search (Y or N)
exact=                 exact number of records (for extract utility, Y or N)
extract=               number of line to pass to extract utility
hits=                  show all hits or document only (D or A)
html=                  comma-separated list of HTML file extensions
                       (overrides the /HTML and /ADDHTML qualifiers)
plain=                 if "yes" then treat HTML files as if plain-text
                       (i.e. search markup tags, everything!)
path=                  form supplied path (otherwise URL path)
search=                text to search for
text=                  comma-separated list of text file extensions
                       (overrides the /TEXT and /ADDTEXT qualifiers)
what=                  title on search results page

These could be used as in the following example (note that as this is in a
C-language comment "@" has been substituted for "*", they're just wildcards!):

<FORM ACTION="/web/doc/.../@.@"> 
<INPUT TYPE=hidden NAME=what VALUE="Example Search of /Web/ Documents">
<INPUT TYPE=submit VALUE="Search for:">
<INPUT TYPE=text NAME=search SIZE=20>
<INPUT TYPE=reset VALUE="Reset">
<BR>Case sensitive?
<INPUT TYPE=radio NAME=case VALUE=N CHECKED>N
<INPUT TYPE=radio NAME=case VALUE=Y>Y
<BR>Extract this number of lines around a &quot;hit&quot;:
<INPUT TYPE=text NAME=extract VALUE="20" SIZE=3 MAXLENGTH=3>
</FORM>


QUALIFIERS
----------
/ABOUT=         synonym for /HELP
/ADDHTML=       additional list of comma separated HTML file types
/ADDTEXT=       additional list of comma separated TEXT file types
/BUTTONS=       string containing button labels/paths
/CHARSET=       "Content-Type: text/html; charset=...", empty suppress charset
/DBUG           turns on all "if (Debug)" statements
/EXTRACT=       path to extract script
/HELP=          URL for help on searching
/HTML=          complete list of comma separated HTML file types
/[NO]ODS5       control extended file specification (basically for testing)
/TEXT=          complete list of comma separated TEXT file types
/TIMEFMT=       strftime() format string for last-modified time
/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


BUILD DETAILS
-------------
See BUILD_QUERY.COM procedure.


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


VERSION HISTORY (update SOFTWAREVN as well!)
---------------
23-DEC-2003  MGD  v3.2.8, minor conditional mods to support IA64
15-AUG-2003  MGD  v3.2.7, bugfix; move fragment *after* query string
23-JUN-2003  MGD  v3.2.6, record size increased to maximum (32767 bytes),
                          ignore RMS$_WLK from sys$open() in line with RMS$_PRV
12-APR-2003  MGD  v3.2.5, link colour changed to 0000cc
15-AUG-2002  MGD  v3.2.4, GetParameters() mod for direct CSWS 1.2 support
01-JUL-2001  MGD  v3.2.3, add 'SkipParameters' for direct OSU support
19-MAY-2001  MGD  v3.2.2, remove limitation in SameFileType() preventing
                          searching of multiple file versions
25-JAN-2001  MGD  v3.2.1, use <BODY> to terminate <HEAD> processing
28-OCT-2000  MGD  v3.2.0, use CGILIB object module
02-MAR-2000  MGD  v3.1.2, bugfix;ed again:^( rework SameFileType()
28-FEB-2000  MGD  v3.1.1, bugfix; SameFileType() wildcard processing
15-FEB-2000  MGD  v3.1.0, allow wildcarded file types
18-JAN-2000  MGD  v3.0.0, support extended file specifications (ODS-5)
07-AUG-1999  MGD  v2.9.0, use more of the CGILIB functionality,
                          plain-text files described using file name
24-APR-1999  MGD  v2.8.0, use CGILIB.C,
                          standard CGI environment (e.g. Netscape FastTrack)
18-FEB-1999  MGD  v2.7.2, Search[Text/Html]File() handling of SS$_PRV
                          (files with protection violations now just ignored)
20-NOV-1998  MGD  v2.7.1, exclude certain content (e.g. <SCRIPT>...</SCRIPT)
07-NOV-1998  MGD  v2.7.0, anchor hits within an HTML document,
                          additional information against file name
03-OCT-1998  MGD  v2.6.0, remove dependency on MapUrl_Map(),
                          provide content-type "; charset=...",
                          accomodations for OSU environment,
                          more general maintenance
24-JUL-1998  MGD  v2.5.1, suppress table background colours if empty
20-MAY-1998  MGD  v2.5.0, general maintenance,
                          cosmetic changes
02-APR-1998  MGD  v2.4.0, report file locked against access a non-fatal error,
                          added form-based path to override path-info,
                          refined (some might say corrected ;^) HTML tag parsing
19-FEB-1998  MGD  v2.3.2, bugfix; "&hits=document" nesting,
                          modified "?about=search" redirection
19-AUG-1997  MGD  v2.3.1, MapUrl() to MapUrl_Map() for conditional mapping
23-MAY-1997  MGD  v2.3.0, wildcard search,
                          un-escape entities (e.g. "&lt;") before match
19-SEP-1995  MGD  v2.2.1, replace <CR><LF> carriage-control with single <LF>,
                          still acceptable for HTTP, slightly more efficient
24-MAY-1995  MGD  v2.2.0, minor changes for AXP compatibility
21-APR-1995  MGD  v2.1.1, added 'FormWhat'
27-MAR-1995  MGD  v2.1.0, modifications to CGI interface
05-DEC-1994  MGD  v2.0.0, major revision, URL mapping, CGI-like interface
10-JUN-1994  MGD  v1.0.0, initial development
*/
/*****************************************************************************/

#define SOFTWAREVN "3.2.8"
#define SOFTWARENM "QUERY"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN
#endif

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

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

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

#ifndef __VAX
#   pragma nomember_alignment
#endif

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

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

#define FI_LI __FILE__, __LINE__

/* convenience macro, returns a pointer to calloc()ed HTML-escaped string */
#define HTMLESC(str) ((char*)CgiLibHtmlEscape(str,-1,NULL,-1))

#define DEFAULT_CGI_EXTRACT "/cgi-bin/extract"
#define DEFAULT_OSU_EXTRACT "/htbin/extract"
#define DEFAULT_WASD_EXTRACT "/extract"

#define DEFAULT_EXTRACT_RECORDS 25
#define DEFAULT_STRFTIME_FORMAT "%a, %d %b %Y %R"
#define MAX_RECORD_SIZE 32767

/* the first comma delimits an empty file type (e.g. README.;) */
#define DEFAULT_TEXT_TYPES \
",ADA,ASCII,BAS,C,CNF,COB,CONF,COM,CPP,DIS,FOR,JAVA,H,LIS,LOG,MAR,\
PAS,PRO,RELEASE_NOTES,SDML,TEX,TEXT,TXT"

#define DEFAULT_HTML_TYPES "HTM,HTML,SHTML,HTMLX"

#define DEFAULT_ABOUT_PATH "/query/-/aboutquery.html"

#define DEFAULT_BUTTONS "About=/query/-/aboutquery.html"

/* this macro just plugs in some script-specific code into ButtonBar() */
#define SCRIPT_SPECIFIC_BUTTON_CODE

#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  DefaultButtons [] = DEFAULT_BUTTONS,
      DefaultHtmlTypes [] = DEFAULT_HTML_TYPES,
      DefaultTextTypes [] = DEFAULT_TEXT_TYPES,
      Utility [] = "QUERY";

boolean  CaseSensitive,
         Debug,
         DocumentOnly,
         ExactNumberOfRecords,
         ThereHasBeenOutput,
         ListStarted,
         TreatHtmlAsPlain,
         FormatLikeVms;

int  ExtractNumberOfRecords,
     KeyCount,
     FileCount,
     FileHitCount,
     ResFileNameLength,
     NotSearchedFileCount,
     OdsExtended,
     TotalHitCount,
     RecordCount,
     SearchStringLength;

char  DocumentName [2048],
      ExpFileName [ODS_MAX_FILE_NAME_LENGTH+1],
      FileNamePath [ODS_MAX_FILE_NAME_LENGTH+1],
      FormHtml [256],
      FormPath [256],
      FormText [256],
      KeyName [16],
      ResFileName [ODS_MAX_FILE_NAME_LENGTH+1],
      SearchString [256],
      SoftwareID [48],
      UrlEncodedFileNamePath [ODS_MAX_FILE_NAME_LENGTH+1],
      UrlEncodedSearchString [256];

char  *ButtonPtr = DefaultButtons,
      *CgiEnvironmentPtr,
      *CgiFormAboutPtr,
      *CgiFormCasePtr,
      *CgiFormExactPtr,
      *CgiFormExtractPtr,
      *CgiFormHitsPtr,
      *CgiFormHtmlPtr,
      *CgiFormPathPtr,
      *CgiFormPlainPtr,
      *CgiFormSearchPtr,
      *CgiFormTextPtr,
      *CgiFormWhatPtr,
      *CgiKeyCountPtr,
      *CgiKeyValuePtr,
      *CgiPathInfoPtr,
      *CgiPathTranslatedPtr,
      *CgiQueryStringPtr,
      *CgiRequestMethodPtr,
      *CgiScriptNamePtr,
      *CgiServerSoftwarePtr,
      *CharsetPtr,
      *CliCharsetPtr,
      *ExtractScriptNamePtr,
      *FormPlainPtr,
      *HelpPathPtr = DEFAULT_ABOUT_PATH,
      *HtmlFileTypesPtr = DefaultHtmlTypes,
      *LastModifiedTimeFormatPtr = DEFAULT_STRFTIME_FORMAT,
      *TextFileTypesPtr = DefaultTextTypes;
      
   struct FAB  SearchFab;
   struct NAM  SearchNam;
#ifdef ODS_EXTENDED
   struct NAML  SearchNaml;
#endif /* ODS_EXTENDED */

/* required prototypes */
char* SearchTextString (char*, char*, boolean, boolean, int*);

/*****************************************************************************/
/*
'argc/argv' are only required to support OSU, in particular CgiLibOsuInit().
*/

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

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

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

   if (getenv ("QUERY$DBUG") != NULL) Debug = true;
   CgiLibEnvironmentSetDebug (Debug);

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

   GetParameters ();
   SetPageScheme ();

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

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

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

   CgiScriptNamePtr = CgiLibVar ("WWW_SCRIPT_NAME");
   CgiPathInfoPtr = CgiLibVar ("WWW_PATH_INFO");
   CgiQueryStringPtr = CgiLibVar ("WWW_QUERY_STRING");
   if (!CgiQueryStringPtr[0])
   {
      QueryForm ();
      return;
   }

   CgiPathTranslatedPtr = CgiLibVar ("WWW_PATH_TRANSLATED");

   CgiFormAboutPtr = CgiLibVar ("WWW_FORM_ABOUT");
   CgiFormCasePtr = CgiLibVar ("WWW_FORM_CASE");
   CgiFormExactPtr = CgiLibVar ("WWW_FORM_EXACT");
   CgiFormExtractPtr = CgiLibVar ("WWW_FORM_EXTRACT");
   CgiFormHitsPtr = CgiLibVar ("WWW_FORM_HITS");
   CgiFormHtmlPtr = CgiLibVar ("WWW_FORM_HTML");
   CgiFormPathPtr = CgiLibVar ("WWW_FORM_PATH");
   CgiFormPlainPtr = CgiLibVar ("WWW_FORM_PLAIN");
   CgiFormSearchPtr = CgiLibVar ("WWW_FORM_SEARCH");
   CgiFormTextPtr = CgiLibVar ("WWW_FORM_TEXT");
   CgiFormWhatPtr = CgiLibVar ("WWW_FORM_WHAT");
   CgiKeyCountPtr = CgiLibVar ("WWW_KEY_COUNT");

   SearchString[0] = '\0';
   KeyCount = atoi(CgiKeyCountPtr);
   for (Count = 1; Count <= KeyCount; Count++)
   {
      sprintf (KeyName, "WWW_KEY_%d", Count);
      CgiKeyValuePtr = CgiLibVar (KeyName);
      if (SearchString[0]) strcat (SearchString, " ");
      strcat (SearchString, CgiKeyValuePtr);
   }
   if (SearchString[0])
      CgiFormSearchPtr = SearchString;
   else
      strcpy (SearchString, CgiFormSearchPtr);

   if (strsame (CgiFormAboutPtr, "SEARCH", 6))
   {
      CgiLibResponseRedirect (HelpPathPtr);
      exit (SS$_NORMAL);
   }

   if (CgiFormPathPtr[0])
   {
      /* path supplied indirectly via form field, redirect to translate */
      CgiScriptNamePtr = CgiLibVar ("WWW_SCRIPT_NAME");
      CgiQueryStringPtr = CgiLibVar ("WWW_QUERY_STRING");

      CgiLibResponseRedirect ("%s%s%s%s",
         CgiScriptNamePtr, CgiFormPathPtr,
         CgiQueryStringPtr[0] ? "?" : "", CgiQueryStringPtr);

      exit (SS$_NORMAL);
   }

   if (!CgiPathTranslatedPtr[0])
   {
      CgiLibResponseError (FI_LI, 0, "Search path not supplied.");
      exit (SS$_NORMAL);
   }
   if (!CgiFormSearchPtr[0])
   {
      CgiLibResponseError (FI_LI, 0, "Search string not supplied.");
      exit (SS$_NORMAL);
   }
   if (CgiFormExtractPtr[0] && !isdigit(CgiFormExtractPtr[0]))
   {
      CgiLibResponseError (FI_LI, 0, "Invalid number of lines to extract.");
      exit (SS$_NORMAL);
   }

   ExtractNumberOfRecords = atoi(CgiFormExtractPtr);
   if (!ExtractNumberOfRecords)
      ExtractNumberOfRecords = DEFAULT_EXTRACT_RECORDS;

   if (toupper(CgiFormExactPtr[0]) == 'Y')
      ExactNumberOfRecords = true;
   else
      ExactNumberOfRecords = false;

   if (toupper(CgiFormCasePtr[0]) == 'Y')
      CaseSensitive = true;
   else
      CaseSensitive = false;

   if (toupper(CgiFormHitsPtr[0]) == 'D')
      DocumentOnly = true;
   else
      DocumentOnly = false;

   if (toupper(CgiFormPlainPtr[0]) == 'Y')
   {
      TreatHtmlAsPlain = true;
      FormPlainPtr = "&plain=yes";
   }
   else
   {
      TreatHtmlAsPlain = false;
      FormPlainPtr = "";
   }

   if (CgiFormHtmlPtr[0])
   {
      HtmlFileTypesPtr = CgiFormHtmlPtr;
      sprintf (FormHtml, "&html=%s", CgiFormHtmlPtr);
   }
   if (CgiFormTextPtr[0])
   {
      TextFileTypesPtr = CgiFormTextPtr;
      sprintf (FormText, "&text=%s", CgiFormTextPtr);
   }

   if (ExtractScriptNamePtr == NULL)
   {
      if (CgiLibEnvironmentIsWasd() ||
          CgiLibEnvironmentIsCgiPlus())
         ExtractScriptNamePtr = DEFAULT_WASD_EXTRACT;
      else
      if (CgiLibEnvironmentIsOsu())
         ExtractScriptNamePtr = DEFAULT_OSU_EXTRACT;
      else
         ExtractScriptNamePtr = DEFAULT_CGI_EXTRACT;
   }

   SearchFiles ();

   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 ("QUERY$PARAM")) == NULL)
   {
      /* 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") != NULL)
      SkipParameters = 3;
   else
      SkipParameters = 0;

   aptr = NULL;
   ch = *clptr;
   for (;;)
   {
      if (aptr != NULL) *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, "/ABOUT=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         HelpPathPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/ADDHTML=", 7))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         sptr = malloc (strlen(HtmlFileTypesPtr)+strlen(cptr)+2);
         strcpy (sptr, HtmlFileTypesPtr);
         strcat (sptr, ",");
         strcat (sptr, cptr);
         HtmlFileTypesPtr = sptr;
         continue;
      }
      if (strsame (aptr, "/ADDTEXT=", 7))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         sptr = malloc (strlen(TextFileTypesPtr)+strlen(cptr)+2);
         strcpy (sptr, TextFileTypesPtr);
         strcat (sptr, ",");
         strcat (sptr, cptr);
         TextFileTypesPtr = sptr;
         continue;
      }
      if (strsame (aptr, "/BUTTONS=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         ButtonPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/CHARSET=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliCharsetPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/DBUG", -1))
      {
         Debug = true;
         continue;
      }
      if (strsame (aptr, "/EXTRACT=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         ExtractScriptNamePtr = cptr;
         continue;
      } 
      if (strsame (aptr, "/HTML=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         HtmlFileTypesPtr = cptr;
         continue;
      } 
      if (strsame (aptr, "/ODS5", 5))
      {
         OdsExtended = true;
         continue;
      }
      if (strsame (aptr, "/NOODS5", 7))
      {
         OdsExtended = false;
         continue;
      }
      if (strsame (aptr, "/TEXT=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         TextFileTypesPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/TIMEFMT=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         LastModifiedTimeFormatPtr = 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] == NULL)
      PageScheme[PS_LAYOUT] = "1";

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

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

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      if (PageScheme[PS_BGCOLOR] == NULL) PageScheme[PS_BGCOLOR] = "";
      if (PageScheme[PS_TEXT] == NULL) PageScheme[PS_TEXT] = "";
      if (PageScheme[PS_LINK] == NULL) PageScheme[PS_LINK] = "";
      if (PageScheme[PS_VLINK] == NULL) PageScheme[PS_VLINK] = "";
      if (PageScheme[PS_HEADBGCOLOR] == NULL) PageScheme[PS_HEADBGCOLOR] = "";
      if (PageScheme[PS_HEADBORDER] == NULL) PageScheme[PS_HEADBORDER] = "";
      if (PageScheme[PS_HEADTEXT] == NULL) PageScheme[PS_HEADTEXT] = "";
      if (PageScheme[PS_BUTTONBGCOLOR] == NULL) PageScheme[PS_BUTTONBGCOLOR] = "";
      if (PageScheme[PS_BUTTONBORDER] == NULL) PageScheme[PS_BUTTONBORDER] = "";
   }
   else
   {
      if (PageScheme[PS_BGCOLOR] == NULL)
         PageScheme[PS_BGCOLOR] = DEFAULT_PS_BGCOLOR;
      if (PageScheme[PS_TEXT] == NULL)
         PageScheme[PS_TEXT] = DEFAULT_PS_TEXT;
      if (PageScheme[PS_LINK] == NULL)
         PageScheme[PS_LINK] = DEFAULT_PS_LINK;
      if (PageScheme[PS_VLINK] == NULL)
         PageScheme[PS_VLINK] = DEFAULT_PS_VLINK;
      if (PageScheme[PS_HEADBGCOLOR] == NULL)
         PageScheme[PS_HEADBGCOLOR] = DEFAULT_PS_HEADBGCOLOR;
      if (PageScheme[PS_HEADBORDER] == NULL)
         PageScheme[PS_HEADBORDER] = DEFAULT_PS_HEADBORDER;
      if (PageScheme[PS_HEADTEXT] == NULL)
         PageScheme[PS_HEADTEXT] = DEFAULT_PS_HEADTEXT;
      if (PageScheme[PS_BUTTONBGCOLOR] == NULL)
         PageScheme[PS_BUTTONBGCOLOR] = DEFAULT_PS_BUTTONBGCOLOR;
      if (PageScheme[PS_BUTTONBORDER] == NULL)
         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)) == NULL) 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)) == NULL)
         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)) == NULL)
         exit (vaxc$errno);
      sprintf (sptr, " BGCOLOR=\"%s\"", PageScheme[PS_BUTTONBGCOLOR]);
      PageScheme[PS_BUTTONBGCOLOR] = sptr;
   }
}

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

ButtonBar (int Top1Bottom2)

{
#define MAX_BUTTON_COUNT 8

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

   int  idx;
   char  *PathPtr;

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

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

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

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

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

      SCRIPT_SPECIFIC_BUTTON_CODE
   }

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

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

      fprintf (stdout, "<FONT SIZE=-1><NOBR>\n");
      for (idx = 0; idx < ButtonCount; idx++)
      {
         if (ButtonInternal[idx] == NULL)
            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] == NULL)
               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");
   }
}

/*****************************************************************************/
/*
Search files in specification for specified string.  Unless a file type 
(extension) matches an HTML type then consider the file to be a plain-text 
file.  The two need to searched differently because of the HTML markup tags 
compsrising an HTML file.
*/ 

SearchFiles ()

{
   static int  TimerElapsed = 1,
               TimerCpu = 2,
               TimerBio = 3,
               TimerDio = 4;
   static $DESCRIPTOR (ElapsedTimeFaoDsc, "!%T");
   static $DESCRIPTOR (StatisticsFaoDsc,
"<B>Elapsed:</B> !AZ &nbsp;<B>CPU:</B> !2ZL:!2ZL.!2ZL \
&nbsp;<B>I/O:</B> !UL &nbsp;<B>Disk:</B> !UL \
&nbsp;<B>Records (lines):</B> !UL");

   int  status,
        BioCount,
        CpuTime,
        DioCount,
        ExpandedNameOffset,
        ResultVersionLength;
   unsigned long  ResultFnb;
   unsigned long  ElapsedTime [2];
   unsigned short  Length;
   char  *cptr, *sptr,
         *FilePtr,
         *FileHitPtr,
         *FileNamePathPtr,
         *ResultNamePtr,
         *ResultTypePtr,
         *ResultVersionPtr,
         *TotalHitPtr;
   char  ElapsedString [32],
         EscapedPath [ODS_MAX_FILE_NAME_LENGTH+1],
         EscapedSearchString [256],
         NotSearchedString [32],
         Statistics [256],
         String [1024];
   $DESCRIPTOR (ElapsedStringDsc, ElapsedString);
   $DESCRIPTOR (StatisticsDsc, Statistics);

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

   if (Debug)
      fprintf (stdout, "SearchFiles() |%s|%s|\n",
               CgiPathTranslatedPtr, SearchString);

   lib$init_timer (0);

   ListStarted = false;

   FormatLikeVms = false;
   for (cptr = CgiPathInfoPtr; *cptr; cptr++);
   while (cptr > CgiPathInfoPtr && *cptr != '/')
   {
      if (*cptr == ';')
      {
         FormatLikeVms = true;
         break;
      }
      cptr--;
   }

   if (FormatLikeVms)
      CgiLibHtmlEscape (CgiPathTranslatedPtr, -1,
                        EscapedPath, sizeof(EscapedPath));
   else
   {
      if (!CgiPathInfoPtr[0]) CgiPathInfoPtr = "/";
      CgiLibHtmlEscape (CgiPathInfoPtr, -1, EscapedPath, sizeof(EscapedPath));
   }

   if (SearchString[0])
   {
      SearchStringLength = strlen(SearchString);
      CgiLibUrlEncode (SearchString, -1, UrlEncodedSearchString, -1);
      CgiLibHtmlEscape (SearchString, -1,
                        EscapedSearchString, sizeof(EscapedSearchString));
   }
   else
   {
      SearchStringLength = 0;
      EscapedSearchString[0] = UrlEncodedSearchString[0] = '\0';
   }

   sptr = String;
   if (CgiFormWhatPtr[0])
      sptr += sprintf (sptr, "Search &nbsp;%s&nbsp; for &nbsp;&quot;%s&quot;",
                       CgiFormWhatPtr, EscapedSearchString);
   else
      sptr += sprintf (sptr, "Search &nbsp;%s&nbsp; for &nbsp;&quot;%s&quot;",
                        EscapedPath, EscapedSearchString);

   SearchFab = cc$rms_fab;
   SearchFab.fab$l_dna = "*.*;0";
   SearchFab.fab$b_dns = 5;

#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 = CgiPathTranslatedPtr;
      SearchNaml.naml$l_long_filename_size = strlen(CgiPathTranslatedPtr);
      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 = CgiPathTranslatedPtr;
      SearchFab.fab$b_fns = strlen(CgiPathTranslatedPtr);
      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)))
   {
      CgiLibResponseError (FI_LI, status, HTMLESC(CgiPathInfoPtr));
      return (status);
   }

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

   /* establish the base for generated file name used in the file path */
#ifdef ODS_EXTENDED
   if (OdsExtended)
      sptr = SearchNaml.naml$l_long_name - 1;
   else
#endif /* ODS_EXTENDED */
      sptr = SearchNam.nam$l_name - 1;
   cptr = ExpFileName;
   while (cptr < sptr)
   {
      if (*cptr == '*' || *cptr == '%' ||
          (cptr[0] == '.' && cptr[1] == '.' && cptr[2] == '.'))
      {
         if (cptr[0] != '.' || cptr[1] != '.' || cptr[2] != '.') cptr--;
         break;
      }
      cptr++;
   }
   while (cptr > ExpFileName && *cptr != '[' && *cptr != '.' && *cptr != ']')
      cptr--;
   ExpandedNameOffset = cptr - ExpFileName + 1;

   /* establish the base for generated file path */
   sptr = FileNamePath;
   cptr = CgiPathInfoPtr;
   while (*cptr)
   {
      if (*cptr == '*' || *cptr == '%')
      {
         cptr--;
         sptr--;
         break;
      }
      if (cptr[0] == '.' && cptr[1] == '.' && cptr[2] == '.') break;
      *sptr++ = *cptr++;
   }
   if ((cptr[0] == '.' && cptr[1] == '.' && cptr[2] == '.') &&
       sptr[-1] != '/' && sptr[-1] != '*' && sptr[-1] != '%')
      *sptr++ = '/';
   else
   {
      while (sptr > FileNamePath && *sptr != '/') sptr--;
      *sptr++;
   }
   *sptr = '\0';
   FileNamePathPtr = sptr;
   if (Debug) fprintf (stdout, "FileNamePath |%s|\n", FileNamePath);

   /*************************/
   /* begin the page output */
   /*************************/

   CgiLibResponseHeader (200, "text/html");

   fprintf (stdout,
"<HTML>\n\
<HEAD>\n\
<META NAME=\"generator\" CONTENT=\"%s\">\n\
<META NAME=\"environment\" CONTENT=\"%s\">\n\
<META NAME=\"text-types\" CONTENT=\"%s\">\n\
<META NAME=\"HTML-types\" CONTENT=\"%s\">\n\
<TITLE>%s</TITLE>\n\
</HEAD>\n\
<BODY%s>\n",
      SoftwareID,
      CgiEnvironmentPtr,
      TextFileTypesPtr,
      HtmlFileTypesPtr,
      String,
      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>WASDquery</U>\n\
</FONT>\n\
<P>\n",
         PageScheme[PS_HEADLOCAL],
         String);
   }
   else
   {
      fprintf (stdout,
"<TABLE BORDER=%s CELLPADDING=%s CELLSPACING=0 WIDTH=100%%%s>\n\
<TR><TD>\n\
<FONT COLOR=\"%s\" SIZE=+2><B>\n\
%s\n\
</B></FONT>\n\
<BR>\
<FONT COLOR=\"%s\" SIZE=-1>\n\
&nbsp;<SUP>*</SUP><U>WASDquery</U>\n\
</FONT>\n\
</TD>%s</TR>\n\
</TABLE>\n",
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADPADDING],
         PageScheme[PS_HEADBGCOLOR],
         PageScheme[PS_HEADTEXT],
         String,
         PageScheme[PS_HEADTEXT],
         PageScheme[PS_HEADLOCAL]);
   }

   ButtonBar (1);

   fflush (stdout);

   ThereHasBeenOutput = true;

   /***************/
   /* file search */
   /***************/

   for (;;)
   {
      status = sys$search (&SearchFab, 0, 0);
      if (Debug) fprintf (stdout, "sys$search() %%X%08.08X\n", status);
      if (status == RMS$_PRV) continue;
      if (VMSnok (status)) break;

#ifdef ODS_EXTENDED
      if (OdsExtended)
      {
         ResultFnb = SearchNaml.naml$l_fnb;
         ResultNamePtr = SearchNaml.naml$l_long_name;
         ResultTypePtr = SearchNaml.naml$l_long_type;
         ResultVersionPtr = SearchNaml.naml$l_long_ver;
         ResultVersionLength = SearchNaml.naml$l_long_ver_size;
         ResFileNameLength = SearchNaml.naml$l_long_ver -
                             SearchNaml.naml$l_long_result;
      }
      else
#endif /* ODS_EXTENDED */
      {
         ResultFnb = SearchNam.nam$l_fnb;
         ResultNamePtr = SearchNam.nam$l_name;
         ResultTypePtr = SearchNam.nam$l_type;
         ResultVersionPtr = SearchNam.nam$l_ver;
         ResultVersionLength = SearchNam.nam$b_ver;
         ResFileNameLength = SearchNam.nam$l_ver -
                             SearchNam.nam$l_rsa;
      }

      if (FormatLikeVms)
      {
         ResultVersionPtr[ResultVersionLength] = '\0';
         ResFileNameLength += ResultVersionLength;
      }
      else
         ResultVersionPtr[0] = '\0';
      if (Debug)
         fprintf (stdout, "ResFileName %d |%s|\n",
                  ResFileNameLength, ResFileName);

      /* generate a file path from the base file path and base file name */
      sptr = FileNamePathPtr;
      cptr = ResFileName + ExpandedNameOffset;
      while (*cptr && *cptr != ']' && cptr < ResultNamePtr)
      {
         if (CgiLibEnvironmentIsWasd() &&
             !memcmp (cptr, "000000.", 7) ||
             !memcmp (cptr, "000000]", 7)) cptr += 7;
         if (*cptr == '.')
         {
            *sptr++ = '/';
            cptr++;
         }
         else
            *sptr++ = tolower(*cptr++);
      }
      if (*cptr == ']')
      {
         cptr++;
         *sptr++ = '/';
      }
      while (*cptr) *sptr++ = tolower(*cptr++);
      *sptr = '\0';
      if (Debug) fprintf (stdout, "FileNamePath |%s|\n", FileNamePath);

      if (SameFileType (HtmlFileTypesPtr, ResultTypePtr+1))
      {
         /* if HTML document's <title> can't be resolved then use file name */
#ifdef ODS_EXTENDED
         if (OdsExtended)
            cptr = (char*)CgiLibUrlEncodeFileName (ResultNamePtr, NULL, 0,
                                                   FormatLikeVms,
                                                   !FormatLikeVms);
         else
#endif /* ODS_EXTENDED */
            cptr = ResultNamePtr;

         sprintf (DocumentName,
"%s &nbsp;&nbsp;<FONT SIZE=-2>[file name]</FONT>",
                  cptr);

         if (TreatHtmlAsPlain)
         {
            if (VMSok (status = SearchTextFile ()))
               FileCount++;
            else
            if (status != RMS$_PRV &&
                status != RMS$_WLK)
            {
               CgiLibResponseError (FI_LI, status, FileNamePath);
               return (SS$_NORMAL);
            }
         }
         else
         {
            if (VMSok (status = SearchHtmlFile ()))
               FileCount++;
            else
            if (status != RMS$_PRV &&
                status != RMS$_WLK)
            {
               CgiLibResponseError (FI_LI, status, FileNamePath);
               return (SS$_NORMAL);
            }
         }
      }
      else
      if (SameFileType (TextFileTypesPtr, ResultTypePtr+1))
      {
#ifdef ODS_EXTENDED
         if (OdsExtended)
            cptr = (char*)CgiLibUrlEncodeFileName (ResultNamePtr, NULL, 0,
                                                   FormatLikeVms,
                                                   !FormatLikeVms);
         else
#endif /* ODS_EXTENDED */
            cptr = ResultNamePtr;

         strcpy (DocumentName, cptr);

         if (VMSok (status = SearchTextFile ()))
            FileCount++;
         else
         if (status != RMS$_PRV &&
             status != RMS$_WLK)
         {
            CgiLibResponseError (FI_LI, status, FileNamePath);
            return (SS$_NORMAL);
         }
      }
      else
         NotSearchedFileCount++;

      if (!FormatLikeVms)
      {
         /* restore the version delimiter */
         ResultVersionPtr[0] = ';';
      }
   }

   if (Debug) fprintf (stdout, "sys$search() %%X%08.08X\n", status);

   /*******************/
   /* end file search */
   /*******************/

   /* if its a search list treat directory not found as if file not found */
   if ((ResultFnb & NAM$M_SEARCH_LIST) && status == RMS$_DNF)
      status = RMS$_FNF;
   if (status == RMS$_FNF || status == RMS$_NMF) status = SS$_NORMAL;
   if (VMSnok (status))
   {
       CgiLibResponseError (FI_LI, status, HTMLESC(CgiPathInfoPtr)); 
       return (SS$_NORMAL);
   }

   /*********************/
   /* results of search */
   /*********************/

   lib$stat_timer (&TimerElapsed, &ElapsedTime, 0);
   lib$stat_timer (&TimerCpu, &CpuTime, 0);
   lib$stat_timer (&TimerBio, &BioCount, 0);
   lib$stat_timer (&TimerDio, &DioCount, 0);

   sys$fao (&ElapsedTimeFaoDsc, &Length, &ElapsedStringDsc, &ElapsedTime);
   ElapsedString[Length] = '\0';
   sys$fao (&StatisticsFaoDsc, &Length, &StatisticsDsc,
            ElapsedString+3, CpuTime/6000, CpuTime/100, CpuTime%100,
            BioCount, DioCount, RecordCount);
   Statistics[Length] = '\0';

   if (!NotSearchedFileCount)
      NotSearchedString[0] = '\0';
   else
   if (NotSearchedFileCount == 1)
      strcpy (NotSearchedString, " (1 not)");
   else
      sprintf (NotSearchedString, " (%d not)", NotSearchedFileCount);

   if (ListStarted) fputs ("</OL>\n", stdout);

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      fprintf (stdout,
"<P>\n\
<BLOCKQUOTE>\n\
<FONT SIZE=-1><NOBR>\n&nbsp;<U>");
   }
   else
   {
      fprintf (stdout,
"<P>\n\
<BLOCKQUOTE>\n\
<TABLE BORDER=%s CELLPADDING=2 CELLSPACING=0>\n\
<TR><TD%s>\n\
<FONT SIZE=-1 COLOR=\"%s\"><NOBR>\n&nbsp;",
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADBGCOLOR],
         PageScheme[PS_HEADTEXT]);
   }

   if (TotalHitCount)
   {
      if (FileCount == 1) FilePtr = "file"; else FilePtr = "files";
      if (FileHitCount == 1) FileHitPtr = "file"; else FileHitPtr = "files";
      if (TotalHitCount == 1) TotalHitPtr = "hit"; else TotalHitPtr = "hits";
      if (DocumentOnly)
      {
         fprintf (stdout,
"<B>%d %s searched%s with %d %s hit.</B>",
         FileCount, FilePtr, NotSearchedString, FileHitCount, FileHitPtr);
      }
      else
      {
         fprintf (stdout,
"<B>%d %s searched%s with %d %s hit, for a total of %d %s.</B>",
         FileCount, FilePtr, NotSearchedString, FileHitCount, FileHitPtr,
         TotalHitCount, TotalHitPtr);
      }
   }

   /*
       This cannot be an 'else' statement.
       Something wierd after change of carriage-control from '\n' to '\n',
       and, unfortunately, after update to DEC C v5.0 (so I don't know which
       caused it!)
       If it is an else the module access violates!
   */
   if (!TotalHitCount)
   {
      if (FileCount)
      {
         if (FileCount == 1) FilePtr = "file"; else FilePtr = "files";
         fprintf (stdout, "<B>%d %s searched%s, string not found.</B>",
                  FileCount, FilePtr, NotSearchedString);
      }
      else
         fprintf (stdout, "<B>No files found!</B>");
   }

   if (PageScheme[PS_LAYOUT][0] == '2') fprintf (stdout, "</U>");

   fprintf (stdout, "\n<BR>&nbsp;%s\n", Statistics);

   if (VMSnok (status))
   {
       CgiLibResponseError (FI_LI, status, HTMLESC(CgiPathInfoPtr));
       return (SS$_NORMAL);
   }

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      fprintf (stdout,
"</NOBR></FONT>\n\
</BLOCKQUOTE>\n");
   }
   else
   {
      fprintf (stdout,
"</NOBR></FONT>\n\
</TD></TR>\n\
</TABLE>\n\
</BLOCKQUOTE>\n");
   }

   ButtonBar (2);

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

   return (status);
}

/*****************************************************************************/
/*
This function accepts a comma-separated list of (possibly wildcarded) file
types (extensions, e.g. "TXT,TEXT,COM,C,PAS,FOR,RPT*") and a VMS file type
(e.g. ".TXT;", ".TXT", "TXT").  Returns true if the file type is in the list,
false if not.
*/

boolean SameFileType
(
char *TypeList,
char *TypePtr
)
{
   char  ch;
   char  *cptr, *sptr, *zptr;
   char  FileType [256];

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

   if (Debug) fprintf (stdout, "SameFileType() |%s|%s|\n", FileType, TypeList);

   zptr = (sptr = FileType) + sizeof(FileType)-1;
   for (cptr = TypePtr;
        *cptr && *cptr != ';' && sptr < zptr;
        *sptr++ = *cptr++);
   *sptr = '\0';
   cptr = TypeList;
   while (*cptr)
   {
      for (sptr = cptr; *sptr && *sptr != ','; sptr++);
      ch = *sptr;
      *sptr = '\0';
      if (Debug) fprintf (stdout, "|%s|%s|\n", FileType, cptr);
      if ((SearchTextString (FileType, cptr, false, false, NULL)) != NULL)
      {
         *sptr = ch;
         return (true);
      }
      if (*sptr = ch) sptr++;
      cptr = sptr;
   }
   return (false);
}

/*****************************************************************************/
/*
String search allowing wildcard "*" (matching any multiple characters) and "%" 
(matching any single character).  Returns NULL if not found or a pointer to
start of matched string.  Setting 'ImpliedWildcards' means the 'SearchFor'
string is processed as if enclosed by '*' wildcard characters.
*/ 

char* SearchTextString
( 
char *SearchIn,
char *SearchFor,
boolean CaseSensitive,
boolean ImpliedWildcards,
int *MatchedLengthPtr
)
{
   char  *cptr, *sptr, *inptr,
         *RestartCptr,
         *RestartInptr,
         *MatchPtr;

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

   if (Debug)
      fprintf (stdout, "SearchTextString() |%s|%s|\n", SearchIn, SearchFor);

   if (MatchedLengthPtr != NULL) *MatchedLengthPtr = 0;
   if (!*(cptr = SearchFor)) return (NULL);
   inptr = MatchPtr = SearchIn;

   if (ImpliedWildcards)
   {
      /* skip leading text up to first matching character (if any!) */
      if (*cptr != '*' && *cptr != '%')
      {
         if (CaseSensitive)
            while (*inptr && *inptr != *cptr) inptr++;
         else
            while (*inptr && toupper(*inptr) != toupper(*cptr)) inptr++;
         if (Debug && !*inptr) fprintf (stdout, "1. NOT matched!\n");
         if (!*inptr) return (NULL);
         cptr++;
         MatchPtr = inptr++;
      }
   }

   for (;;)
   {
      if (CaseSensitive)
      {
         while (*cptr && *inptr && *cptr == *inptr)
         {
            cptr++;
            inptr++;
         }
      }
      else
      {
         while (*cptr && *inptr && toupper(*cptr) == toupper(*inptr))
         {
            cptr++;
            inptr++;
         }
      }

      if (ImpliedWildcards)
      {
         if (!*cptr)
         {
            if (Debug) fprintf (stdout, "1. matched!\n");
            if (MatchedLengthPtr != NULL) *MatchedLengthPtr = inptr - MatchPtr;
            return (MatchPtr);
         }
      }
      else
      {
         if (!*cptr && !*inptr)
         {
            if (Debug) fprintf (stdout, "2. matched!\n");
            if (MatchedLengthPtr != NULL) *MatchedLengthPtr = inptr - MatchPtr;
            return (MatchPtr);
         }
         if (*cptr != '*' && *cptr != '%')
         {
            if (Debug && !*inptr) fprintf (stdout, "3. NOT matched!\n");
            return (NULL);
         }
      }

      if (*cptr != '*' && *cptr != '%')
      {
         if (!*inptr)
         {
            if (Debug) fprintf (stdout, "4. NOT matched!\n");
            return (NULL);
         }
         cptr = SearchFor;
         MatchPtr = ++inptr;
         continue;
      }

      if (*cptr == '%')
      {
         /* single char wildcard processing */
         if (!*inptr) break;
         cptr++;
         inptr++;
         continue;
      }

      /* asterisk wildcard matching */
      while (*cptr == '*') cptr++;

      /* an asterisk wildcard at end matches all following */
      if (!*cptr)
      {
         if (Debug) fprintf (stdout, "5. matched!\n");
         while (*inptr) inptr++;
         if (MatchedLengthPtr != NULL) *MatchedLengthPtr = inptr - MatchPtr;
         return (MatchPtr);
      }

      /* note the current position in the string (first after the wildcard) */
      RestartCptr = cptr;
      for (;;)
      {
         /* find first char in SearchIn matching char after wildcard */
         if (CaseSensitive)
            while (*inptr && *cptr != *inptr) inptr++;
         else
            while (*inptr && toupper(*cptr) != toupper(*inptr)) inptr++;
         /* if did not find matching char in SearchIn being searched */
         if (Debug && !*inptr) fprintf (stdout, "6. NOT matched!\n");
         if (!*inptr) return (NULL);
         /* note the current position in SearchIn being searched */
         RestartInptr = inptr;
         /* try to match the remainder of the string and SearchIn */
         if (CaseSensitive)
         {
            while (*cptr && *inptr && *cptr == *inptr)
            {
               cptr++;
               inptr++;
            }
         }
         else
         {
            while (*cptr && *inptr && toupper(*cptr) == toupper(*inptr))
            {
               cptr++;
               inptr++;
            }
         }
         /* if reached the end of both string and SearchIn - match! */
         if (ImpliedWildcards)
         {
            if (!*cptr)
            {
               if (Debug) fprintf (stdout, "7. matched!\n");
               if (MatchedLengthPtr != NULL)
                  *MatchedLengthPtr = inptr - MatchPtr;
               return (MatchPtr);
            }
         }
         else
         {
            if (!*cptr && !*inptr)
            {
               if (Debug) fprintf (stdout, "8. matched!\n");
               if (MatchedLengthPtr != NULL)
                  *MatchedLengthPtr = inptr - MatchPtr;
               return (MatchPtr);
            }
         }
         /* break to the external loop if we encounter another wildcard */
         if (*cptr == '*' || *cptr == '%') break;
         /* lets have another go */
         cptr = RestartCptr;
         /* starting the character following the previous attempt */
         inptr = MatchPtr = RestartInptr + 1;
      }
   }
}

/*****************************************************************************/
/*
Search an HTML marked up file.  Simply count the number of '<' and '>' 
characters, which should be balanced, and when not inside an HTML markup tag 
search the text.  As HTML files cannot easily have text extracted from within 
them without the results being unpredictable simply return the document as 
having the search string hit.  The HTML <title> tag (if present) is used as 
the document name.
*/ 

SearchHtmlFile ()

{
   boolean  InsideApplet = false,
            InsideComment = false,
            InsideHead = false,
            InsideScript = false,
            InsideServer = false,
            InsideStyle = false,
            InsideTitle = false;
   int  status,
        ByteCount,
        HitCount = 0,
        MatchedLength,
        RecordNumber = 0,
        TagCharCount = 0;
   unsigned long  UnixTime;
   char  ch;
   char  *cptr, *dptr, *rptr, *sptr, *tptr,
         *CaseSensitivePtr = "",
         *FileNamePathPtr;
   char  FileSize [32],
         LastModifiedTime [64],
         Record [MAX_RECORD_SIZE+1],
         String [MAX_RECORD_SIZE*2],
         Text [MAX_RECORD_SIZE*2];
   struct tm  *UnixTmPtr;
   struct FAB  FileFab;
#ifdef ODS_EXTENDED
   struct NAML  FileNaml;
#endif /* ODS_EXTENDED */
   struct RAB  FileRab;
   struct XABDAT  FileXabDat;
   struct XABFHC  FileXabFhc;

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

   if (Debug)
      fprintf (stdout, "SearchHtmlFile() |%s|%s|\n", ResFileName, SearchString);

   FileFab = cc$rms_fab;
   FileFab.fab$b_fac = FAB$M_GET;
   FileFab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT;
   FileFab.fab$l_xab = &FileXabDat;
   FileFab.fab$l_fop = FAB$M_NAM;
   FileXabDat = cc$rms_xabdat;
   FileXabDat.xab$l_nxt = &FileXabFhc;
   FileXabFhc = cc$rms_xabfhc;

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

      ENAMEL_RMS_NAML(FileNaml)
      FileNaml.naml$l_long_filename = ResFileName;
      FileNaml.naml$l_long_filename_size = ResFileNameLength;
   }
   else
#endif /* ODS_EXTENDED */
   {
      FileFab.fab$l_fna = ResFileName;  
      FileFab.fab$b_fns = ResFileNameLength;
   }

   status = sys$open (&FileFab, 0, 0);
   if (Debug) fprintf (stdout, "sys$open() %%X%08.08X\n", status);

   if (VMSnok (status))
   {
      if (status == RMS$_PRV) return (status);
      if (!ListStarted)
      {
         ListStarted = true;
         fputs ("<P>\n<OL>\n", stdout);
      }
      fprintf (stdout,
"<P><NOBR><B>ERROR</B> opening file (%%X%08.08X) \
<TT>%s</TT> <!-- %s --></NOBR><P>\n",
         status,
         FileNamePath,
         ResFileName);
      return (SS$_NORMAL);
   }

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

   if (VMSnok (status = sys$connect (&FileRab, 0, 0)))
   {
      if (Debug) fprintf (stdout, "sys$connect() %%X%08.08X\n", status);
      sys$close (&FileFab, 0, 0);
      return (status);
   }

   if (CaseSensitive) CaseSensitivePtr = "&case=yes";

   /**********************/
   /* search all records */
   /**********************/

   while (VMSok (status = sys$get (&FileRab, 0, 0)))
   {
      RecordNumber++;
      if (!FileRab.rab$w_rsz) continue;
      Record[FileRab.rab$w_rsz] = '\0';
      if (Debug)
         fprintf (stdout, "Record |%s|\n%d %d %d %d %d %d %d %d\n",
                  Record, TagCharCount, InsideApplet, InsideComment,
                  InsideHead, InsideScript, InsideServer, InsideStyle,
                  InsideTitle);

      /* terminate on any carriage control that may be in the record */
      for (rptr = Record; *rptr && *rptr != '\r' && *rptr != '\n'; rptr++);
      *rptr = '\0';
      /* continue if empty line */
      if (!Record[0]) continue;

      /**************************************/
      /* retrieve text not inside HTML tags */
      /**************************************/

      tptr = Text;
      rptr = Record;
      while (*rptr)
      {
         if (InsideComment)
         {
            if (rptr[0] == '-' && rptr[1] == '-' && rptr[2] == '>')
            {
               InsideComment = false;
               rptr += 3;
            }
            else
               rptr++;
            continue;
         }
         if (*rptr == '<' && *((unsigned long*)rptr) == '<!--')
         {
            InsideComment = true;
            rptr += 4;
            continue;
         }

         /* less-thans are forbidden inside tags! */
         if ((TagCharCount & 1) && rptr[0] == '<') break;

         /* consider adjacent white-space when determining what is a tag */
         if ((rptr[0] == '<' && rptr[1] && !isspace(rptr[1])) ||
             ((TagCharCount & 1) && rptr[0] == '>'))
         {
            TagCharCount++;
            rptr++;
            if (TagCharCount & 1)
            {
               /* checks to detect start and end of some tags */
               if (toupper(*rptr) == 'A' &&
                   (strsame (rptr, "APPLET>", 7) ||
                    strsame (rptr, "APPLET ", 7)))
               {
                  rptr += 6;
                  InsideApplet = true;
               }
               else
               if (strsame (rptr, "BODY>", 5) ||
                   strsame (rptr, "BODY ", 4))
               {
                  rptr += 4;
                  InsideHead = false;
               }
               else
               if (toupper(*rptr) == 'H' && strsame (rptr, "HEAD>", 5))
               {
                  rptr += 4;
                  InsideHead = true;
               }
               else
               if (toupper(*rptr) == 'S')
               {
                  if (strsame (rptr, "SCRIPT>", 7) ||
                      strsame (rptr, "SCRIPT ", 7))
                  {
                     rptr += 6;
                     InsideScript = true;
                  }
                  else
                  if (strsame (rptr, "SERVER>", 7) ||
                      strsame (rptr, "SERVER ", 7))
                  {
                     rptr += 6;
                     InsideServer = true;
                  }
                  if (strsame (rptr, "STYLE>", 7) ||
                      strsame (rptr, "STYLE ", 7))
                  {
                     rptr += 6;
                     InsideStyle = true;
                  }
               }
               else
               if (toupper(*rptr) == 'T' && strsame (rptr, "TITLE>", 6))
               { 
                  rptr += 5;
                  dptr = DocumentName;
                  InsideTitle = true;
               }
               else
               if (*rptr == '/')
               {
                  if (strsame (rptr, "/APPLET>", 8))
                  {
                     rptr += 7;
                     InsideApplet = false;
                  }
                  else
                  if (strsame (rptr, "/HEAD>", 6))
                  {
                     rptr += 5;
                     InsideHead = false;
                  }
                  else
                  if (strsame (rptr, "/SCRIPT>", 8))
                  {
                     rptr += 7;
                     InsideScript = false;
                  }
                  else
                  if (strsame (rptr, "/SERVER>", 8))
                  {
                     rptr += 7;
                     InsideServer = false;
                  }
                  else
                  if (strsame (rptr, "/STYLE>", 7))
                  {
                     rptr += 6;
                     InsideStyle = false;
                  }
                  else
                  if (strsame (rptr, "/TITLE>", 7))
                  {
                     rptr += 6;
                     *dptr = '\0';
                     InsideTitle = false;
                  }
               }
            }
         }
         else
         {
            if (InsideTitle)
            {
               if (dptr < DocumentName+255)
                  *dptr++ = *rptr++;
               else
                  rptr++;
            }
            else
            if (InsideApplet ||
                InsideHead ||
                InsideScript ||
                InsideServer ||
                InsideStyle)
            {
               rptr++;
            }
            else
            if (TagCharCount & 1)
               rptr++;
            else
            {
               if (*rptr == '&')
               {
                  if (strsame (rptr, "&lt;", 4))
                  {
                     *tptr++ = '<';
                     rptr += 4;
                  }
                  else
                  if (strsame (rptr, "&gt;", 4))
                  {
                     *tptr++ = '>';
                     rptr += 4;
                  }
                  else
                  if (strsame (rptr, "&amp;", 5))
                  {
                     *tptr++ = '&';
                     rptr += 5;
                  }
                  else
                  if (strsame (rptr, "&quot;", 6))
                  {
                     *tptr++ = '\"';
                     rptr += 6;
                  }
                  else
                  if (strsame (rptr, "&nbsp;", 6))
                  {
                     *tptr++ = ' ';
                     rptr += 6;
                  }
                  else
                  if (*(rptr+1) == '#')
                  {
                     for (cptr = rptr+2; *cptr && isdigit(*cptr); cptr++);
                     if (*cptr == ';')
                     {
                        ch = atoi(rptr+2) & 0xff;
                        *tptr++ = ch;
                        rptr = cptr + 1;
                     }
                     else
                        *tptr++ = *rptr++;
                  }
                  else
                     *tptr++ = *rptr++;
               }
               else
                  *tptr++ = *rptr++;
            }
         }

      }  /* while (*rptr) */
 
      *tptr = '\0';
      if (!Text[0]) continue;

      tptr = SearchTextString (Text, SearchString,
                               CaseSensitive, true,
                               &MatchedLength);
      if (tptr != NULL)
      {
         /********/
         /* hit! */
         /********/

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

         TotalHitCount++;

#ifdef ODS_EXTENDED
         if (OdsExtended)
            FileNamePathPtr = 
               (char*)CgiLibUrlEncodeFileName (FileNamePath, NULL, 0,
                                               FormatLikeVms, !FormatLikeVms);
         else
#endif /* ODS_EXTENDED */
            FileNamePathPtr = FileNamePath;

         if (!HitCount++)
         {
            /*************************************/
            /* first hit, entire document anchor */
            /*************************************/

            UnixTime = decc$fix_time (&FileXabDat.xab$q_rdt);
            UnixTmPtr = localtime (&UnixTime);
            if (!strftime (LastModifiedTime, sizeof(LastModifiedTime),
                           LastModifiedTimeFormatPtr, UnixTmPtr))
               strcpy (LastModifiedTime, "**ERROR**");

            /* true for non-VAR record formats, almost true for those :^) */
            if (FileXabFhc.xab$l_ebk)
               ByteCount = ((FileXabFhc.xab$l_ebk - 1) * 512) +
                           FileXabFhc.xab$w_ffb;
            else
               ByteCount = FileXabFhc.xab$w_ffb;
            if (ByteCount < 1000)
               sprintf (FileSize, "%d bytes", ByteCount);
            else
            if (ByteCount % 1000 < 500)
               sprintf (FileSize, "%d kB", ByteCount/1000);
            else
               sprintf (FileSize, "%d kB", (ByteCount/1000)+1);

            if (!ListStarted)
            {
               ListStarted = true;
               fputs ("<P>\n<OL>\n", stdout);
            }

            fprintf (stdout,
"<NOBR><B><LI><A HREF=\"%s\">%s</A> \
&nbsp;&nbsp;[<FONT SIZE=-1>HTML &nbsp;%s &nbsp;%s</FONT>]</B></NOBR>\n",
               FileNamePathPtr, DocumentName, LastModifiedTime, FileSize);

            if (DocumentOnly) break;

            fprintf (stdout, "<OL>\n");
         }

         /***********************/
         /* file extract anchor */
         /***********************/

         sptr = String;
         strcpy (sptr, "<LI>");
         sptr += 4;
         /* absorb leading white-space */
         for (cptr = Text; *cptr && (*cptr == ' ' || *cptr == '\t'); cptr++);
         /* copy the record up to the first character of the search string */
         sptr += CgiLibHtmlEscape (cptr, tptr-cptr, sptr, -1);

         /* add the HTML anchor */
         sptr += sprintf (sptr,
                 "<A HREF=\"%s%s?highlight=%s&anchor=yes%s%s%s#__hit%d__\">",
                 ExtractScriptNamePtr, FileNamePathPtr,
                 UrlEncodedSearchString, FormText, FormHtml, FormPlainPtr,
                 HitCount);

         /* matched string, highlighted within the anchor */
         sptr += CgiLibHtmlEscape (tptr, MatchedLength, sptr, -1);
         strcpy (sptr, "</A>");
         sptr += 4;

         /* rest of record after the matched search string */
         sptr += CgiLibHtmlEscape (tptr+MatchedLength, -1, sptr, -1);
         *sptr++ = '\n'; *sptr = '\0';

         if (Debug) fprintf (stdout, "String |%s|\n", String);
         fputs (String, stdout);
      }
   }

   /***************/
   /* end of file */
   /***************/

   if (status == RMS$_EOF) status = SS$_NORMAL;
   sys$close (&FileFab, 0, 0);

   if (VMSnok (status))
   {
      if (!ListStarted)
      {
         ListStarted = true;
         fputs ("<P>\n<OL>\n", stdout);
      }
      fprintf (stdout,
"<P><NOBR><B>ERROR</B> reading file (%%X%08.08X) \
<TT>%s</TT> <!-- %s --></NOBR><P>\n",
         status, FileNamePath, ResFileName);
   }

   if (HitCount)
   {
      if (!DocumentOnly) fputs ("</OL>\n", stdout);
      fflush (stdout);
      FileHitCount++;
   }
   if (TagCharCount & 1)
   {
      /* must have encountered an opening '<' without a closing '>' */
      if (!ListStarted)
      {
         ListStarted = true;
         fputs ("<P>\n<OL>\n", stdout);
      }
      fprintf (stdout,
"<P><NOBR><B>HTML problem</B>, unbalanced <B>&lt;&gt;</B> \
in <TT><A HREF=\"%s\">%s</A></TT> <!-- %s --></NOBR><P>\n",
         FileNamePath, FileNamePath, ResFileName);
   }

   RecordCount += RecordNumber;

   return (status);
}

/*****************************************************************************/
/*
Search each record of what is presumed to be a plain-text file for the 
supplied string.  When the first hit occurs output an HTML anchor for 
retrieving the entire file.  For each hit output an HTML anchor to extract a 
specified range of record (lines) from the file.  This search only checks for 
at least one match in a line before considering it a hit.
*/

SearchTextFile ()

{
   int  idx,
        status,
        LastSectionRecordNumber = 1,
        RecordNumber = 0,
        ByteCount,
        HitCount = 0,
        MatchedLength;
   unsigned long  UnixTime;
   char  *cptr, *rptr, *sptr, *tptr,
         *CaseSensitivePtr = "",
         *FileNamePathPtr;
   char  FileSize [32],
         LastModifiedTime [64],
         Record [MAX_RECORD_SIZE+1],
         String [MAX_RECORD_SIZE*2];
   struct tm  *UnixTmPtr;
   struct FAB  FileFab;
#ifdef ODS_EXTENDED
   struct NAML  FileNaml;
#endif /* ODS_EXTENDED */
   struct RAB  FileRab;
   struct XABDAT  FileXabDat;
   struct XABFHC  FileXabFhc;

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

   if (Debug)
      fprintf (stdout, "SearchTextFile() |%s|%s|\n", ResFileName, SearchString);

   FileFab = cc$rms_fab;
   FileFab.fab$b_fac = FAB$M_GET;
   FileFab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT;
   FileFab.fab$l_xab = &FileXabDat;
   FileXabDat = cc$rms_xabdat;
   FileXabDat.xab$l_nxt = &FileXabFhc;
   FileXabFhc = cc$rms_xabfhc;

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

      ENAMEL_RMS_NAML(FileNaml)
      FileNaml.naml$l_long_filename = ResFileName;
      FileNaml.naml$l_long_filename_size = ResFileNameLength;
   }
   else
#endif /* ODS_EXTENDED */
   {
      FileFab.fab$l_fna = ResFileName;  
      FileFab.fab$b_fns = ResFileNameLength;
   }

   status = sys$open (&FileFab, 0, 0);
   if (Debug) fprintf (stdout, "sys$open() %%X%08.08X\n", status);

   if (VMSnok (status))
   {
      if (status == RMS$_PRV) return (status);
      if (!ListStarted)
      {
         ListStarted = true;
         fputs ("<P>\n<OL>\n", stdout);
      }
      fprintf (stdout,
"<P><NOBR><B>ERROR</B> opening file (%%X%08.08X) \
<TT>%s</TT> <!-- %s --></NOBR><P>\n",
         status, FileNamePath, ResFileName);
      return (SS$_NORMAL);
   }

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

   if (VMSnok (status = sys$connect (&FileRab, 0, 0)))
   {
      if (Debug) fprintf (stdout, "sys$connect() %%X%08.08X\n", status);
      sys$close (&FileFab, 0, 0);
      return (status);
   }

   if (CaseSensitive) CaseSensitivePtr = "&case=yes";

   /**********************/
   /* search all records */
   /**********************/

   while (VMSok (status = sys$get (&FileRab, 0, 0)))
   {
      RecordNumber++;
      Record[FileRab.rab$w_rsz] = '\0';

      /* terminate on any carriage control that may be in the record */
      for (rptr = Record; *rptr && *rptr != '\r' && *rptr != '\n'; rptr++);
      *rptr = '\0';

      /* if necessary generate document name from first non-blank line */
      if (!DocumentName[0]) GenerateDocumentName (Record, DocumentName);

      if (*(rptr = Record))
         while (*rptr && isspace(*rptr)) rptr++;
      if (!*rptr)
      {
         /* the line contained none, or only white-space characters */
         LastSectionRecordNumber = RecordNumber + 1;
         continue;
      }

      tptr = SearchTextString (Record, SearchString,
                               CaseSensitive, true,
                               &MatchedLength);
      if (tptr != NULL)
      {
         /********/
         /* hit! */
         /********/

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

         TotalHitCount++;

#ifdef ODS_EXTENDED
         if (OdsExtended)
            FileNamePathPtr =
               (char*)CgiLibUrlEncodeFileName (FileNamePath, NULL, 0,
                                               FormatLikeVms, !FormatLikeVms);
         else
#endif /* ODS_EXTENDED */
            FileNamePathPtr = FileNamePath;

         if (!HitCount++)
         {
            /*************************************/
            /* first hit, entire document anchor */
            /*************************************/

            UnixTime = decc$fix_time (&FileXabDat.xab$q_rdt);
            UnixTmPtr = localtime (&UnixTime);
            if (!strftime (LastModifiedTime, sizeof(LastModifiedTime),
                           LastModifiedTimeFormatPtr, UnixTmPtr))
               strcpy (LastModifiedTime, "**ERROR**");

            /* true for non-VAR record formats, almost true for those :^) */
            if (FileXabFhc.xab$l_ebk)
               ByteCount = ((FileXabFhc.xab$l_ebk - 1) * 512) +
                           FileXabFhc.xab$w_ffb;
            else
               ByteCount = FileXabFhc.xab$w_ffb;
            if (ByteCount < 1000)
               sprintf (FileSize, "%d bytes", ByteCount);
            else
            if (ByteCount % 1000 < 500)
               sprintf (FileSize, "%d kB", ByteCount/1000);
            else
               sprintf (FileSize, "%d kB", (ByteCount/1000)+1);

            if (!ListStarted)
            {
               ListStarted = true;
               fputs ("<P>\n<OL>\n", stdout);
            }

            fprintf (stdout,
"<NOBR><B><LI><A HREF=\"%s%s?highlight=%s%s%s%s\">%s</A> \
&nbsp;&nbsp;[<FONT SIZE=-1>TEXT &nbsp;%s &nbsp;%s</FONT>]</B></NOBR>\n",
               ExtractScriptNamePtr, FileNamePathPtr, UrlEncodedSearchString,
               CaseSensitivePtr, FormText, FormHtml, DocumentName,
               LastModifiedTime, FileSize);

            if (DocumentOnly) break;

            fprintf (stdout, "<OL>\n");
         }

         /***********************/
         /* file extract anchor */
         /***********************/

         sptr = String;
         strcpy (sptr, "<LI>");
         sptr += 4;
         /* absorb leading white-space */
         for (cptr = Record; *cptr && (*cptr == ' ' || *cptr == '\t'); cptr++);
         /* copy the record up to the first character of the search string */
         sptr += CgiLibHtmlEscape (cptr, tptr-cptr, sptr, -1);

         /* add the HTML anchor */
         sptr += sprintf (sptr, "<A HREF=\"%s%s?highlight=%s",
                    ExtractScriptNamePtr, FileNamePathPtr,
                    UrlEncodedSearchString);

         if (ExactNumberOfRecords)
            sptr += sprintf (sptr, "&start=%d&end=%d&exact=true%s%s\">",
                    RecordNumber,
                    RecordNumber+ExtractNumberOfRecords-1, FormText, FormHtml);
         else
            sptr += sprintf (sptr, "&start=%d&end=%d%s%s\">",
                    LastSectionRecordNumber,
                    RecordNumber+ExtractNumberOfRecords-1, FormText, FormHtml);

         /* matched string, highlighted within the anchor */
         sptr += CgiLibHtmlEscape (tptr, MatchedLength, sptr, -1);
         strcpy (sptr, "</A>");
         sptr += 4;

         /* rest of record after the matched search string */
         sptr += CgiLibHtmlEscape (tptr+MatchedLength, -1, sptr, -1);
         *sptr++ = '\n'; *sptr = '\0';

         if (Debug) fprintf (stdout, "String |%s|\n", String);
         fputs (String, stdout);
      }
   }

   /***************/
   /* end of file */
   /***************/

   if (status == RMS$_EOF) status = SS$_NORMAL;
   sys$close (&FileFab, 0, 0);

   if (VMSnok (status))
   {
      if (!ListStarted)
      {
         ListStarted = true;
         fputs ("<P>\n<OL>\n", stdout);
      }

      fprintf (stdout,
"<P><NOBR><B>ERROR</B> reading file (%%X%08.08X) \
<TT>%s</TT> <!-- %s --></NOBR><P>\n",
         status, FileNamePath, ResFileName);
   }

   if (HitCount)
   {
      if (!DocumentOnly) fputs ("</OL>\n", stdout);
      fflush (stdout);
      FileHitCount++;
   }

   RecordCount += RecordNumber;

   return (status);
}

/*****************************************************************************/
/*
This function serves to generate a document name string (for use in the HTML
<TITLE> tag) for a plain-text file from the first line containing alpha-
numeric characters.  Copy the string pointed to by 'sptr' into the string 
pointed to by 'NamePtr', compressing white-space.
*/ 

GenerateDocumentName
(
char *sptr,
char *nptr
)
{
   /*********/
   /* begin */
   /*********/

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

   /* skip leading non-alphanumerics */
   while (*sptr && !isalnum(*sptr)) sptr++;
   while (*sptr)
   {
      /* copy alphanumeric element */
      while (*sptr && !isspace(*sptr) && isalnum(*sptr)) *nptr++ = *sptr++;
      if (!*sptr) break;
      /* skip intervening/trailing non-alphanumerics */
      while (*sptr && !isalnum(*sptr)) sptr++;
      if (!*sptr) break;
      /* add a single space between alphanumeric elements */
      *nptr++ = ' ';
   }
   *nptr = '\0';
}

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

QueryForm ()

{
   char  *cptr;
   char  EscapedPath [ODS_MAX_FILE_NAME_LENGTH+1];

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

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

   FormatLikeVms = false;
   for (cptr = CgiPathInfoPtr; *cptr; cptr++);
   while (cptr > CgiPathInfoPtr && *cptr != '/')
   {
      if (*cptr == ';') FormatLikeVms = true;
      cptr--;
   }

   if (FormatLikeVms)
      CgiLibHtmlEscape (CgiPathTranslatedPtr, -1,
                        EscapedPath, sizeof(EscapedPath));
   else
      CgiLibHtmlEscape (CgiPathInfoPtr, -1,
                        EscapedPath, sizeof(EscapedPath));

   CgiLibResponseHeader (200, "text/html");

   fprintf (stdout,
"<HTML>\n\
<HEAD>\n\
<META NAME=\"generator\" CONTENT=\"%s\">\n\
<META NAME=\"CGI\" CONTENT=\"%s\">\n\
<META NAME=\"text-types\" CONTENT=\"%s\">\n\
<META NAME=\"HTML-types\" CONTENT=\"%s\">\n\
<TITLE>Search %s</TITLE>\n\
</HEAD>\n\
<BODY%s>\n",
      SoftwareID,
      CgiEnvironmentPtr,
      TextFileTypesPtr,
      HtmlFileTypesPtr,
      EscapedPath,
      PageScheme[PS_BODYTAG]);

   ThereHasBeenOutput = true;

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

   fprintf (stdout,
"<P>\n\
<FORM ACTION=\"%s%s\">\n\
<INPUT TYPE=submit VALUE=\"Search for:\">&nbsp;\n\
<INPUT TYPE=text NAME=search SIZE=20>&nbsp;\n\
<INPUT TYPE=reset VALUE=\"Reset\">\n\
<P>Case sensitive?&nbsp;\n\
<INPUT TYPE=radio NAME=case VALUE=N CHECKED> N&nbsp;\n\
<INPUT TYPE=radio NAME=case VALUE=Y> Y&nbsp;\n\
<BR>Treat HTML as if plain-text?&nbsp;\n\
<INPUT TYPE=radio NAME=plain VALUE=N CHECKED> N&nbsp;\n\
<INPUT TYPE=radio NAME=plain VALUE=Y> Y&nbsp;\n\
<BR>Extract this number of lines around a plain-text &quot;hit&quot;:&nbsp;\n\
<INPUT TYPE=text NAME=extract VALUE=\"25\" SIZE=3 MAXLENGTH=3>\n\
</FORM>\n",
      CgiScriptNamePtr, CgiPathInfoPtr);

   ButtonBar (2);

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

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

#ifdef ODS_EXTENDED

int GetVmsVersion ()

{
   static char  SyiVersion [16];

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

   int  status,
        version;

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

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

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

#endif /* ODS_EXTENDED */

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

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

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

