/*****************************************************************************/
/*
                                 Extract.c


CGI-compliant script (working in concert with QUERY.C) to:

   1.  extract a specified range of lines from a plain text file
   2.  anchor hits of the keyword in an HTML file

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 VM syntax file names.  If none is
present it supplies URL-encoded file names.

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. If an
HTML file is "extracted" without either CGI variable 'form_anchor' or
'form_plain' being non-empty a 302 redirection is generated direct to the
document itself (on the assumption that it is a self-relative link within an
"extract"-anchored document, with the consequent loss of any partial-document
reference (i.e. #blah)).  The following tags to not have any content included:
<APPLET></APPLET>, <HEAD></HEAD>, <SCRIPT></SCRIPT>, <SERVER></SERVER>,
<TITLE></TITLE>.

"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=/extract/-/aboutextract.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=/extract/-/aboutextract.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:

  $ EXTRACT$PARAM = "/BUTTON=""About=/extract/-/aboutextract.html"""
  $ EXTRACT$PARAM = EXTRACT$PARAM + "/PBGCOLOR/PLINK/PVLINK"
  $ EXTRACT$PARAM = EXTRACT$PARAM + "/PHLOCAL=""<TD VALIGN=top>...</TD>"""
  $ RUN HT_EXE:EXTRACT


CGI FORM ELEMENTS
-----------------
form_anchor=           introduce "hit" anchors into an HTML file
form_case=             was a case sensitive search (Y or N)
form_end=              record (line) number to being extract
form_exact=            exact number of records (Y or N)
form_extract=          number of line to pass to extract utility
form_highlight=        the string to be highlighted
form_html=             comma-separated list of HTML file extensions
                       (overrides the /HTML and /ADDHTML qualifiers)
form_plain=            treat an HTML file as plain text
form_start=            record (line) number to being extract
form_text=             comma-separated list of text file extensions
                       (overrides the /TEXT and /ADDTEXT qualifiers)

Generally these form elements will be generated by QUERY.C but there is no
reasons why they shopuldn't come from somewhere else.


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


QUALIFIERS
----------
/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
/[NO]ODS5       control extended file specification (basically for testing)
/PBACKGROUND=   <body> background image path
/PBGCOLOR=      <body> background colour
/PBBGCOLOR=     button background color
/PBBORDER=      width of button border
/PHBGCOLOR=     heading background color
/PHBORDER=      width of heading and button-bar border
/PHLOCAL=       local information to be included in header
/PHTEXT=        heading text colour
/PLAYOUT=       1 is coloured header & buttons, 2 is text & horizontal rules
/PLINK=         <body> link colour
/PTEXT=         <body> text colour
/PVLINK=        <body> visited link colour
/TEXT=          complete list of comma separated TEXT file types


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.


"VANILLA" CGI ENVIRONMENT
-------------------------
Primarily for the likes of Netscape FastTrack.  This environment can accomodate
CGI variables that are not prefixed with "WWW_" and do not supply "KEY_xxxxx"
or "FORM_xxxxx" (which must be derived from "QUERY_STRING").  Full HTTP stream
(non-parsed header) is assumed as not supported so all output occurs with a
CGI-compliant header line (e.g. "Status: 200 Success") and record-oriented
output.


BUILD DETAILS
-------------
See BUILD_EXTRACT.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.3.1, minor conditional mods to support IA64
23-JUN-2003  MGD  v3.3.0, record size increased to maximum (32767 bytes)
12-APR-2003  MGD  v3.2.4, link colour changed to 0000cc
15-AUG-2002  MGD  v3.2.3, GetParameters() mod for direct CSWS 1.2 support
01-JUL-2001  MGD  v3.2.2, add 'SkipParameters' for direct OSU support
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.7.0, use more of the CGILIB functionality
24-APR-1999  MGD  v2.6.0, use CGILIB.C,
                          standard CGI environment (Netscape FastTrack)
20-NOV-1998  MGD  v2.5.1, exclude certain content (e.g. <SCRIPT>...</SCRIPT)
07-NOV-1998  MGD  v2.5.0, anchor hits within an HTML document
03-OCT-1998  MGD  v2.4.0, general maintenance in-line with query.,
                          provide content-type "; charset=...",
                          accomodations for OSU environment
24-JUL-1998  MGD  v2.3.1, suppress table background colours if empty
20-MAY-1998  MGD  v2.3.0, general maintenance,
                          cosmetic changes
19-SEP-1995  MGD  v2.2.1, replace <CR><LF> carriage-control with single <LF>,
                          still acceptable for HTTP, slightly more efficient;
                          added 'previous' and 'next' number of lines link
24-MAY-1995  MGD  v2.2.0, minor changes for AXP compatibility
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.3.1"
#define SOFTWARENM "EXTRACT"
#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>

/* 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__

/* macro provides NULL pointer if CGI variable does not exist */
#define GetCgiVarIfExists(CharPointer,CgiVariableName) \
   CharPointer = getenv(CgiVariableName)

/* macro provides pointer to empty string even if CGI variable does not exist */
#define GetCgiVar(CharPointer,CgiVariableName) \
   if ((CharPointer = getenv(CgiVariableName)) == NULL) \
       CharPointer = ""; \
   if (Debug) fprintf (stdout, "%s |%s|\n", CgiVariableName, CharPointer);

#define MAX_RECORD_SIZE 32767

/* use this character to mask out HTML tag characters when searching */
#define MASK_TAG_CHAR '\xff'

/*
   The first comma delimits an empty file type (e.g. README.;)
   HTML extensions are included here because they can be treated as plain
   text files if being considered as source rather than marked-up text.
*/
#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,HTML,HTM,SHTML,HTMLX"

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

#define DEFAULT_BUTTONS ""

#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 [] = "EXTRACT";

boolean  Debug,
         FormatLikeVms,
         ThereHasBeenOutput;

int  ExtractNumberOfRecords,
     OdsExtended;

char  ContentTypeCharset [64],
      SoftwareID [48];
      
char  *ButtonPtr = DefaultButtons,
      *CgiEnvironmentPtr,
      *CgiFormAnchorPtr,
      *CgiFormExactPtr,
      *CgiFormCasePtr,
      *CgiFormEndPtr,
      *CgiFormHighlightPtr,
      *CgiFormHtmlPtr,
      *CgiFormPlainPtr,
      *CgiFormStartPtr,
      *CgiFormTextPtr,
      *CgiPathInfoPtr,
      *CgiPathTranslatedPtr,
      *CgiRequestMethodPtr,
      *CgiRequestSchemePtr,
      *CgiScriptNamePtr,
      *CgiServerNamePtr,
      *CgiServerSoftwarePtr,
      *CgiServerPortPtr,
      *CharsetPtr,
      *CliCharsetPtr,
      *HtmlFileTypesPtr = DefaultHtmlTypes,
      *TextFileTypesPtr = DefaultTextTypes,
      *UrlEncodedCgiPathInfoPtr;

/* 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[]
)
{
   boolean  CaseSensitive = false,
            DocumentOnly = false;
   int  status,
        EndRecordNumber,
        StartRecordNumber;
   char  *cptr;
   char  ServerPort [32],
         String [512];

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

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

   if (getenv ("EXTRACT$DBUG") != NULL) Debug = true;
   CgiLibEnvironmentSetDebug (Debug);
   if (Debug) fprintf (stdout, "Content-Type: text/plain\n\n");

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

   GetParameters ();
   SetPageScheme ();

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

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

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

   if (Debug)
      fprintf (stdout, "TextFileTypesPtr |%s|\n", TextFileTypesPtr);
   if (!*TextFileTypesPtr)
   {
      CgiLibResponseError (FI_LI, 0, "Text file extensions not configured.");
      exit (SS$_NORMAL);
   }

   if (Debug)
      fprintf (stdout, "HtmlFileTypesPtr |%s|\n", HtmlFileTypesPtr);
   if (!*HtmlFileTypesPtr)
   {
      CgiLibResponseError (FI_LI, 0, "HTML file extensions not configured.");
      exit (SS$_NORMAL);
   }

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

   CgiPathInfoPtr = CgiVar ("WWW_PATH_INFO");
   CgiPathTranslatedPtr = CgiVar ("WWW_PATH_TRANSLATED");
   CgiScriptNamePtr = CgiVar ("WWW_SCRIPT_NAME");

   CgiFormAnchorPtr = CgiVar ("WWW_FORM_ANCHOR");
   CgiFormCasePtr = CgiVar ("WWW_FORM_CASE");
   CgiFormEndPtr = CgiVar ("WWW_FORM_END");
   CgiFormExactPtr = CgiVar ("WWW_FORM_EXACT");
   CgiFormHighlightPtr = CgiVar ("WWW_FORM_HIGHLIGHT");
   CgiFormHtmlPtr = CgiVar ("WWW_FORM_HTML");
   CgiFormPlainPtr = CgiVar ("WWW_FORM_PLAIN");
   CgiFormStartPtr = CgiVar ("WWW_FORM_START");
   CgiFormTextPtr = CgiVar ("WWW_FORM_TEXT");

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

   CgiFormExactPtr[0] = toupper(CgiFormExactPtr[0]);

   StartRecordNumber = atoi (CgiFormStartPtr);
   EndRecordNumber = atoi (CgiFormEndPtr);

   if (CgiFormHtmlPtr[0]) HtmlFileTypesPtr = CgiFormHtmlPtr;
   if (CgiFormTextPtr[0]) TextFileTypesPtr = CgiFormTextPtr;

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

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      /* re-encode the path in case it's an extended file specification */
      UrlEncodedCgiPathInfoPtr =
         (char*)CgiLibUrlEncode (CgiPathInfoPtr, -1, NULL, -1);
   }
   else
#endif /* ODS_EXTENDED */
      UrlEncodedCgiPathInfoPtr = CgiPathInfoPtr;

   for (cptr = CgiPathTranslatedPtr; *cptr; cptr++);
   while (cptr > CgiPathTranslatedPtr && *cptr != '.') cptr--;
   if (*cptr == '.') cptr++;

   if (SameFileType (HtmlFileTypesPtr, cptr))
   {
      /*************/
      /* HTML file */
      /*************/

      if (CgiFormAnchorPtr[0])
      {
         /*******************/
         /* provide anchors */
         /*******************/

         status = AnchorHtmlFile (CgiPathTranslatedPtr,
                                  CgiFormHighlightPtr,
                                  CaseSensitive);

         if (VMSnok (status))
            CgiLibResponseError (FI_LI, status, UrlEncodedCgiPathInfoPtr);
      }
      else
      if (CgiFormPlainPtr[0])
      {
         /**************************/
         /* treat as if plain text */
         /**************************/

         status = ExtractTextFile (CgiPathTranslatedPtr,
                                   CgiFormHighlightPtr,
                                   StartRecordNumber, EndRecordNumber,
                                   CaseSensitive);

         if (VMSnok (status))
            CgiLibResponseError (FI_LI, status, UrlEncodedCgiPathInfoPtr);
      }
      else
      {
         /************/
         /* redirect */
         /************/

         CgiLibResponseRedirect (UrlEncodedCgiPathInfoPtr);
      }
   }
   else
   if (SameFileType (TextFileTypesPtr, cptr))
   {
      /*************/
      /* text file */
      /*************/

      status = ExtractTextFile (CgiPathTranslatedPtr,
                                CgiFormHighlightPtr,
                                StartRecordNumber, EndRecordNumber,
                                CaseSensitive);

      if (VMSnok (status))
         CgiLibResponseError (FI_LI, status, UrlEncodedCgiPathInfoPtr);
   }
   else
   {
      /*********************/
      /* unknown file type */
      /*********************/

      /* just return the document/image/file via redirection */
      CgiLibResponseRedirect (UrlEncodedCgiPathInfoPtr);
   }

   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 ("EXTRACT$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, "/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, "/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 (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;
   }
}

/*****************************************************************************/
/*
Modified ButtonBar() for extract.  Relies on calling code to set up button
enviroment.   This function just creates the buttons.  See other script for
information on how this function operates.
*/

ExtractButtonBar ()

{
#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, "ExtractButtonBar()\n");

   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 (PageScheme[PS_LAYOUT][0] == '2')
   {
      /************/
      /* format 2 */
      /************/

      for (idx = 0; idx < ButtonCount; idx++)
      {
         if (ButtonInternal[idx] == NULL)
            PathPtr = ButtonPath[idx];
         else
            PathPtr = ButtonInternal[idx];
         if (PathPtr[0])
            fprintf (stdout, "[<A HREF=\"%s\">%s</A>]\n",
                     PathPtr, ButtonLabel[idx]);
         else
            fprintf (stdout, "[%s]\n", ButtonLabel[idx]);
      }

      return;
   }
   else
   {
      /************/
      /* format 1 */
      /************/

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

/*****************************************************************************/
/*
Extract the specified record (line) range from a plain-text file.  If the 
search string is supplied then highlight the first matched string in any line.
*/ 

int ExtractTextFile
(
char *FileName,
char *HighlightString,
int StartRecordNumber,
int EndRecordNumber,
boolean CaseSensitive
)
{
   int  status,
        NumberOfRecords,
        RecordNumber = 0,
        HighlightStringLength = 0;
   char  *rptr, *sptr;
   char  EscapedPath [ODS_MAX_FILE_NAME_LENGTH+1],
         ExpFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         Record [MAX_RECORD_SIZE+1],
         String [MAX_RECORD_SIZE*2],
         UrlEncodedHighlightString [256] = "";
   struct FAB  FileFab;
   struct RAB  FileRab;
#ifdef ODS_EXTENDED
   struct NAML  FileNaml;
#endif /* ODS_EXTENDED */
   struct NAM  FileNam;

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

   if (Debug)
      fprintf (stdout, "ExtractTextFile() |%s|%s|\n",
               FileName, HighlightString);

   if (EndRecordNumber > StartRecordNumber)
      NumberOfRecords = EndRecordNumber - StartRecordNumber;
   else
      NumberOfRecords = 0;

   if (HighlightString[0])
   {
      HighlightStringLength = strlen(HighlightString);
      CgiLibUrlEncode (HighlightString, -1, UrlEncodedHighlightString, -1);
   }

   FileFab = cc$rms_fab;
   FileFab.fab$b_fac = FAB$M_GET;
   FileFab.fab$b_shr = FAB$M_SHRGET;

#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 = FileName;
      FileNaml.naml$l_long_filename_size = strlen(FileName);
      FileNaml.naml$l_long_expand = ExpFileName;
      FileNaml.naml$l_long_expand_alloc = sizeof(ExpFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      FileFab.fab$l_fna = FileName;  
      FileFab.fab$b_fns = strlen(FileName);
      FileFab.fab$l_nam = &FileNam;

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

   if (VMSnok (status = sys$open (&FileFab, 0, 0)))
   {
      /* if its a search list treat directory not found as if file not found */
#ifdef ODS_EXTENDED
      if (OdsExtended)
      {
         if ((FileNaml.naml$l_fnb & NAM$M_SEARCH_LIST) && status == RMS$_DNF)
            status = RMS$_FNF;
      }
      else
#endif /* ODS_EXTENDED */
      {
         if ((FileNam.nam$l_fnb & NAM$M_SEARCH_LIST) && status == RMS$_DNF)
            status = RMS$_FNF;
      }

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

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      /* if explicit version number display in VMS extended file syntax */
      if (FormatLikeVms)
         UrlEncodedCgiPathInfoPtr =
            (char*)CgiLibHtmlEscape (CgiPathInfoPtr, -1, NULL, -1);
   }
#endif /* ODS_EXTENDED */

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

   /*******************/
   /* extract records */
   /*******************/

   while (VMSok (status = sys$get (&FileRab, 0, 0)))
   {
      RecordNumber++;
      Record[FileRab.rab$w_rsz] = '\0';
      if (Debug) fprintf (stdout, "Record |%d|%s|\n", RecordNumber, Record);

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

      if (StartRecordNumber && RecordNumber < StartRecordNumber) continue;

      if (!ThereHasBeenOutput && !Record[0]) continue;

      if (!ThereHasBeenOutput)
      {
         /**************/
         /* begin page */
         /**************/

         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=\"environment\" CONTENT=\"%s\">\n\
<META NAME=\"text-types\" CONTENT=\"%s\">\n\
<META NAME=\"html-types\" CONTENT=\"%s\">\n\
<TITLE>Extract ... %s</TITLE>\n\
</HEAD>\n\
<BODY%s>\n",
            SoftwareID,
            CgiEnvironmentPtr,
            TextFileTypesPtr,
            HtmlFileTypesPtr,
            EscapedPath,
            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>WASDextract</U>\n\
</FONT>\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\
%s\n\
</B></FONT>\n\
<BR>\
<FONT COLOR=\"%s\" SIZE=-1>\n\
&nbsp;<SUP>*</SUP><U>WASDextract</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]);
         }

         /******************/
         /* top button bar */
         /******************/

         if (PageScheme[PS_LAYOUT][0] == '2')
         {
            fprintf (stdout, "<P>\n<FONT SIZE=-1><NOBR>[");
         }
         else
         {
            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\
<TABLE BORDER=%s CELLPADDING=1 CELLSPACING=0>\n\
<TR>\n\
<TD ALIGN=center%s><FONT SIZE=-1><NOBR>&nbsp;&nbsp;",
               PageScheme[PS_HEADBORDER],
               PageScheme[PS_HEADBORDER],
               PageScheme[PS_HEADBGCOLOR],
               PageScheme[PS_BUTTONBORDER],
               PageScheme[PS_BUTTONBGCOLOR]);
         }

         if (StartRecordNumber)
         {
            if (EndRecordNumber && StartRecordNumber - NumberOfRecords > 0)
            {
               fprintf (stdout,
"begins at line %d, retrieve \
<A HREF=\"%s%s?highlight=%s&start=%d&end=%d\">previous %d lines</A> or \
<A HREF=\"%s%s?highlight=%s\">entire document</A> \
or <A HREF=\"%s\">file</A>",
                  StartRecordNumber, 
                  CgiScriptNamePtr,
                  UrlEncodedCgiPathInfoPtr,
                  UrlEncodedHighlightString,
                  StartRecordNumber-NumberOfRecords-1,
                  StartRecordNumber-1,
                  NumberOfRecords+1,
                  CgiScriptNamePtr,
                  UrlEncodedCgiPathInfoPtr,
                  UrlEncodedHighlightString,
                  UrlEncodedCgiPathInfoPtr);
            }
            else
            {
               fprintf (stdout,
"begins at line %d, retrieve \
<A HREF=\"%s%s?highlight=%s\">entire document</A> \
or <A HREF=\"%s\">file</A>",
                  RecordNumber,
                  CgiScriptNamePtr,
                  UrlEncodedCgiPathInfoPtr,
                  UrlEncodedHighlightString,
                  UrlEncodedCgiPathInfoPtr);
            }
         }
         else
         {
            if (EndRecordNumber)
            {
               fprintf (stdout,
"begins at line %d (start of document), retrieve \
<A HREF=\"%s%s?highlight=%s\">entire document</A> \
or <A HREF=\"%s\">file</A>",
                  RecordNumber,
                  CgiScriptNamePtr,
                  UrlEncodedCgiPathInfoPtr,
                  UrlEncodedHighlightString,
                  UrlEncodedCgiPathInfoPtr);
            }
            else
               fprintf (stdout,
"entire document, retrieve <A HREF=\"%s\">file</A>",
                  UrlEncodedCgiPathInfoPtr);
         }

         if (PageScheme[PS_LAYOUT][0] == '2')
            fprintf (stdout, "]\n");
         else
            fprintf (stdout, "&nbsp;&nbsp;</NOBR></FONT></TD>\n");

         ExtractButtonBar ();

         if (PageScheme[PS_LAYOUT][0] == '2')
         {
            fprintf (stdout,
"</NOBR></FONT>\n\
<HR ALIGN=left SIZE=2 WIDTH=95%%>\n\
<PRE>");
         }
         else
         {
            fprintf (stdout,
"</TR>\n\
</TABLE>\n\
</TD></TR>\n\
</TABLE>\n\
<PRE>");
         }

         fflush (stdout);
         ThereHasBeenOutput = true;
      }

      /******************/
      /* process record */
      /******************/

      if (EndRecordNumber && RecordNumber > EndRecordNumber)
      {
         if (CgiFormExactPtr[0] == 'T' || CgiFormExactPtr[0] == 'Y') break;
         /* if this is an empty (blank) line then end-of-section */
         if (!FileRab.rab$w_rsz) break;
         /* if there is only white-space in this line then end-of-section */
         for (rptr = Record; *rptr && isspace(*rptr); rptr++);
         if (!*rptr) break;
      }

      rptr = SearchTextString (Record, HighlightString,
                               CaseSensitive, true, NULL);

      if (rptr != NULL)
      {
         /***********************************/
         /* hit! - Highlight matched string */
         /***********************************/

         sptr = String;
         /* copy the record up to the first character of the search string */
         sptr += CgiLibHtmlEscape (Record, rptr-Record, sptr, -1);
         /* emphasize the matched search string */
         strcpy (sptr, "<B>"); sptr += 3;
         sptr += CgiLibHtmlEscape (rptr, HighlightStringLength, sptr, -1);
         strcpy (sptr, "</B>"); sptr += 4;
         /* rest of record after the matched search string */
         sptr += CgiLibHtmlEscape (rptr+HighlightStringLength, -1, sptr, -1);
         *sptr++ = '\n'; *sptr = '\0';
      }
      else
      {
         /**********/
         /* no hit */
         /**********/

         sptr = String;
         sptr += CgiLibHtmlEscape (Record, -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);

   /*********************/
   /* bottom button bar */
   /*********************/

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

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      fprintf (stdout,
"<HR ALIGN=left SIZE=2 WIDTH=95%%>\n\
<FONT SIZE=-1><NOBR>[");
   }
   else
   {
      fprintf (stdout,
"<TABLE BORDER=%s CELLPADDING=%s CELLSPACING=0 WIDTH=100%%>\n\
<TR><TD%s>\n\
<TABLE BORDER=%s CELLPADDING=1 CELLSPACING=0>\n\
<TR>\n\
<TD ALIGN=center%s><FONT SIZE=-1><NOBR>&nbsp;&nbsp;",
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADBGCOLOR],
         PageScheme[PS_BUTTONBORDER],
         PageScheme[PS_BUTTONBGCOLOR]);
   }

   if (StartRecordNumber)
   {
      if (EndRecordNumber && RecordNumber > EndRecordNumber)
      {
         fprintf (stdout,
"ends at line %d, \
retrieve <A HREF=\"%s%s?Highlight=%s&start=%d&end=%d\">next %d lines</A> or \
<A HREF=\"%s%s?Highlight=%s&start=%d\">rest of document</A> \
or <A HREF=\"%s\">file</A>",
            RecordNumber-1, 
            CgiScriptNamePtr,
            UrlEncodedCgiPathInfoPtr,
            UrlEncodedHighlightString,
            RecordNumber,
            RecordNumber+NumberOfRecords,
            NumberOfRecords+1,
            CgiScriptNamePtr,
            UrlEncodedCgiPathInfoPtr,
            UrlEncodedHighlightString,
            RecordNumber,
            UrlEncodedCgiPathInfoPtr);
      }
      else
      {
         fprintf (stdout,
"ends at line %d (end of document), \
retrieve <A HREF=\"%s%s?Highlight=%s\">entire document</A> \
or <A HREF=\"%s\">file</A>",
            RecordNumber,
            CgiScriptNamePtr,
            UrlEncodedCgiPathInfoPtr,
            UrlEncodedHighlightString,
            UrlEncodedCgiPathInfoPtr);
      }
   }
   else
      fprintf (stdout, "entire document, retrieve <A HREF=\"%s\">file</A>",
               UrlEncodedCgiPathInfoPtr);

   if (PageScheme[PS_LAYOUT][0] == '2')
      fprintf (stdout, "]\n");
   else
      fprintf (stdout, "&nbsp;&nbsp;</NOBR></FONT></TD>\n");

   ExtractButtonBar ();

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

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

   return (status);
}

/*****************************************************************************/
/*
Add an HTML anchor around each highlight string corresonding to the hit number
displayed by query (<A NAME="__hit1__">blah</A>). Due to the way the search
string is located within the HTML tags (by masking them out) it is possible
that search strings containg HTML entities may not be correctly found by this
function. The incidence of this should be quite low so abosulute accuracy has
been sacrificed to ease (and speed) in programming it.
*/ 

int AnchorHtmlFile
(
char *FileName,
char *HighlightString,
boolean CaseSensitive
)
{
   boolean  InsideApplet = false,
            InsideComment = false,
            InsideHead = false,
            InsideScript = false,
            InsideServer = false,
            InsideStyle = false,
            InsideTitle = false;
   int  status,
        HighlightStringLength = 0,
        HitCount = 0,
        TagCharCount = 0;
   char  ch;
   char  *cptr, *rptr, *sptr, *tptr;
   char  ExpFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         Record [MAX_RECORD_SIZE+1],
         String [MAX_RECORD_SIZE*2],
         Text [MAX_RECORD_SIZE*2],
         UrlEncodedHighlightString [256] = "";
   struct FAB  FileFab;
   struct RAB  FileRab;
#ifdef ODS_EXTENDED
   struct NAML  FileNaml;
#endif /* ODS_EXTENDED */
   struct NAM  FileNam;

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

   if (Debug)
      fprintf (stdout, "AnchorHtmlFile() |%s|%s|\n",
               FileName, HighlightString);

   if (HighlightString[0])
   {
      HighlightStringLength = strlen(HighlightString);
      CgiLibUrlEncode (HighlightString, -1, UrlEncodedHighlightString, -1);
   }

   FileFab = cc$rms_fab;
   FileFab.fab$b_fac = FAB$M_GET;
   FileFab.fab$b_shr = FAB$M_SHRGET;

#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 = FileName;
      FileNaml.naml$l_long_filename_size = strlen(FileName);
      FileNaml.naml$l_long_expand = ExpFileName;
      FileNaml.naml$l_long_expand_alloc = sizeof(ExpFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      FileFab.fab$l_fna = FileName;  
      FileFab.fab$b_fns = strlen(FileName);
      FileFab.fab$l_nam = &FileNam;

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

   if (VMSnok (status = sys$open (&FileFab, 0, 0)))
   {
      /* if its a search list treat directory not found as if file not found */
#ifdef ODS_EXTENDED
      if (OdsExtended)
      {
         if ((FileNaml.naml$l_fnb & NAM$M_SEARCH_LIST) && status == RMS$_DNF)
            status = RMS$_FNF;
      }
      else
#endif /* ODS_EXTENDED */
      {
         if ((FileNam.nam$l_fnb & NAM$M_SEARCH_LIST) && status == RMS$_DNF)
            status = RMS$_FNF;
      }

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

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      /* if explicit version number display in VMS extended file syntax */
      if (FormatLikeVms)
         UrlEncodedCgiPathInfoPtr =
            (char*)CgiLibHtmlEscape (CgiPathInfoPtr, -1, NULL, -1);
   }
#endif /* ODS_EXTENDED */

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

   /*******************/
   /* extract records */
   /*******************/

   while (VMSok (status = sys$get (&FileRab, 0, 0)))
   {
      Record[FileRab.rab$w_rsz] = '\0';
      if (Debug) fprintf (stdout, "Record |%s|\n", Record);

      /* terminate at any carriage control that may be in the record */
      for (rptr = Record; *rptr && *rptr != '\r' && *rptr != '\n'; rptr++);
      /* add a single new-line carriage-control */
      *rptr++ = '\n';
      *rptr = '\0';

      if (!ThereHasBeenOutput)
      {
         /**************/
         /* begin page */
         /**************/

         CgiLibResponseHeader (200, "text/html");
         fprintf (stdout, "<!-- HITS ANCHORED BY: %s -->\n", SoftwareID);
         ThereHasBeenOutput = true;
      }

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

      tptr = Text;
      rptr = Record;
      while (*rptr)
      {
         if (InsideComment)
         {
            if (rptr[0] == '-' && rptr[1] == '-' && rptr[2] == '>')
            {
               InsideComment = false;
               memset (tptr, MASK_TAG_CHAR, 3);
               tptr += 3;
               rptr += 3;
            }
            else
            {
               *tptr++ = MASK_TAG_CHAR;
               rptr++;
            }
            continue;
         }

         if (*((unsigned short*)rptr) == '</')
         {
            if (InsideApplet)
            {
               if (strsame (rptr, "</APPLET>", 9))
               {
                  InsideApplet = false;
                  memset (tptr, MASK_TAG_CHAR, 9);
                  tptr += 9;
                  rptr += 9;
                  continue;
               }
            }
            if (InsideHead)
            {
               if (strsame (rptr, "</HEAD>", 7))
               {
                  InsideHead = false;
                  memset (tptr, MASK_TAG_CHAR, 7);
                  tptr += 7;
                  rptr += 7;
                  continue;
               }
            }
            if (InsideScript)
            {
               if (strsame (rptr, "</SCRIPT>", 9))
               {
                  InsideScript = false;
                  memset (tptr, MASK_TAG_CHAR, 9);
                  tptr += 9;
                  rptr += 9;
                  continue;
               }
            }
            if (InsideServer)
            {
               if (strsame (rptr, "</SERVER>", 9))
               {
                  InsideServer = false;
                  memset (tptr, MASK_TAG_CHAR, 9);
                  tptr += 9;
                  rptr += 9;
                  continue;
               }
            }
            if (InsideStyle)
            {
               if (strsame (rptr, "</STYLE>", 7))
               {
                  InsideStyle = false;
                  memset (tptr, MASK_TAG_CHAR, 7);
                  tptr += 7;
                  rptr += 7;
                  continue;
               }
            }
            if (InsideTitle)
            {
               if (strsame (rptr, "</TITLE>", 7))
               {
                  InsideTitle = false;
                  memset (tptr, MASK_TAG_CHAR, 7);
                  tptr += 7;
                  rptr += 7;
                  continue;
               }
            }
         }

         if (*rptr == '<')
         {
            if (*((unsigned long*)rptr) == '<!--')
            {
               InsideComment = true;
               memset (tptr, MASK_TAG_CHAR, 4);
               tptr += 4;
               rptr += 4;
               TagCharCount++;
               continue;
            }
            if (strsame (rptr, "<APPLET>", 8) ||
                strsame (rptr, "<APPLET ", 8))
            {
               InsideApplet = true;
               memset (tptr, MASK_TAG_CHAR, 8);
               tptr += 8;
               rptr += 7;
               TagCharCount++;
               continue;
            }
            if (strsame (rptr, "<BODY>", 6) ||
                strsame (rptr, "<BODY ", 6))
            {
               InsideHead = false;
               memset (tptr, MASK_TAG_CHAR, 5);
               tptr += 6;
               rptr += 5;
               TagCharCount++;
               continue;
            }
            if (strsame (rptr, "<HEAD>", 6))
            {
               InsideHead = true;
               memset (tptr, MASK_TAG_CHAR, 6);
               tptr += 6;
               rptr += 5;
               TagCharCount++;
               continue;
            }
            if (strsame (rptr, "<SCRIPT>", 8) ||
                strsame (rptr, "<SCRIPT ", 8))
            {
               InsideScript = true;
               memset (tptr, MASK_TAG_CHAR, 8);
               tptr += 8;
               rptr += 7;
               TagCharCount++;
               continue;
            }
            if (strsame (rptr, "<SERVER>", 8) ||
                strsame (rptr, "<SERVER ", 8))
            {
               InsideServer = true;
               memset (tptr, MASK_TAG_CHAR, 8);
               tptr += 8;
               rptr += 7;
               TagCharCount++;
               continue;
            }
            if (strsame (rptr, "<STYLE>", 7) ||
                strsame (rptr, "<STYLE ", 7))
            {
               InsideStyle = true;
               memset (tptr, MASK_TAG_CHAR, 7);
               tptr += 7;
               rptr += 6;
               TagCharCount++;
               continue;
            }
            if (strsame (rptr, "<TITLE>", 7))
            {
               InsideTitle = true;
               memset (tptr, MASK_TAG_CHAR, 7);
               tptr += 7;
               rptr += 6;
               TagCharCount++;
               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++;
            *tptr++ = MASK_TAG_CHAR;
         }
         else
         if (InsideApplet || InsideHead || InsideScript ||
             InsideServer || InsideTitle)
         {
            rptr++;
            *tptr++ = MASK_TAG_CHAR;
         }
         else
         {
            if (TagCharCount & 1)
            {
               rptr++;
               *tptr++ = MASK_TAG_CHAR;
            }
            else
            {
               if (*rptr == '&')
               {
                  if (strsame (rptr, "&lt;", 4))
                  {
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = '<';
                     rptr += 4;
                  }
                  else
                  if (strsame (rptr, "&gt;", 4))
                  {
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = '>';
                     rptr += 4;
                  }
                  else
                  if (strsame (rptr, "&amp;", 5))
                  {
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = '&';
                     rptr += 5;
                  }
                  else
                  if (strsame (rptr, "&quot;", 6))
                  {
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = '\"';
                     rptr += 6;
                  }
                  else
                  if (strsame (rptr, "&nbsp;", 6))
                  {
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = ' ';
                     rptr += 6;
                  }
                  else
                  if (*(rptr+1) == '#')
                  {
                     *tptr++ = MASK_TAG_CHAR;
                     *tptr++ = MASK_TAG_CHAR;
                     for (cptr = rptr+2; *cptr && isdigit(*cptr); cptr++)
                        *tptr++ = MASK_TAG_CHAR;
                     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, HighlightString,
                               CaseSensitive, true, NULL);

      if (tptr != NULL)
      {
         /***********************************/
         /* hit! - Highlight matched string */
         /***********************************/

         HitCount++;

         /* copy the record up to the first character of the search string */
         rptr = Record;
         sptr = String;
         cptr = Text;
         while (cptr < tptr)
         {
            *sptr++ = *rptr++;
            cptr++;
         }
         /* anchor the matched search string */
         sptr += sprintf (sptr, "<A NAME=\"__hit%d__\">", HitCount);
         tptr += HighlightStringLength;
         while (cptr < tptr)
         {
            *sptr++ = *rptr++;
            cptr++;
         }
         strcpy (sptr, "</A>");
         sptr += 4;
         /* rest of record after the matched search string */
         while (*rptr) *sptr++ = *rptr++;
         *sptr++ = '\n';
         *sptr = '\0';
         if (Debug) fprintf (stdout, "String |%s|\n", String);
         fputs (String, stdout);
      }
      else
      {
         /**********/
         /* no hit */
         /**********/

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

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

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

   fprintf (stdout, "<!-- HITS ANCHORED BY: %s -->\n", SoftwareID);

   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"). It returns true if the file type is in the list,
false if not.
*/

boolean SameFileType
(
char *TypeList,
char *FileType
)
{
   char  ch;
   char  *cptr, *sptr;

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

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

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

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

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