/*****************************************************************************
/*
                                 Str[i]ng.c

String support functions for HTTPd.

Traditional U**x regular expressions:

  http://en.wikipedia.org/wiki/Regular_expression


VERSION HISTORY
---------------
25-AUG-2022  MGD  strzcpy() and strzcat() rework in face of ubiquity
21-MAY-2022  MGD  strsame() now implemented using str[n]casecmp()
26-FEB-2021  MGD  strcasestr() case insensitive strstr()
                  StringReplace() case insensitive string replacement 
14-DEC-2018  MGD  bugfix; for bugfix StringSliceValue() kludge
                    allow for DECnet connection string specified username 
15-AUG-2018  MGD  bugfix; StringSliceValue() kludge for DECnet tasks
                    (25-MAY-2017 allow quote-delim inside space-delimited)
28-APR-2018  MGD  refactor Admin..() AST delivery
25-MAY-2017  MGD  StringSliceValue() allow quote-delim inside space-delimited
07-JAN-2016  MGD  StringSpanValue() now StringSlice[s]Value() and dices
25-AUG-2015  MGD  StringMatchAndRegex() ensure |rqptr| not needed
02-JAN-2011  MGD  StringMatchAndRegex() support configurable regex syntax
                  StringMatchReport() provide configurable regex syntax
14-OCT-2006  MGD  bugfix; StringMatchAndRegex() regular expression
                    'MatchType' detection prior to pre-match
                  added REG_NEWLINE to REGEX_C_FLAGS so that anchors match
                    newlines in strings to support 'Request' filter in WATCH
30-JUL-2004  MGD  bugfix; StringMatchAndRegex() SMATCH__GREEDY_REGEX
09-SEP-2003  MGD  bugfix; StringSpanValue()
06-JUL-2003  MGD  StringParseValue() modified to accept a parenthesis
                  delimited, comma-separated list of quoted strings
03-MAY-2003  MGD  StringMatchAndRegex() regular expression support
                  StringMatchSubstitute() substitute matched strings
                  StringMatchReport() server admin facility
14-FEB-2003  MGD  bugfix; StringParseQuery() loop on string overflow
31-JAN-2003  MGD  strzcpy() now returns the length of the source, not
                  necessarily, the copied string (allows overflow check)
08-NOV-2002  MGD  StringSpanValue(), StringParseValue(),
                  StringStripValue() now terminate on newline
16-OCT-2002  MGD  add StringScriptValue(),
                  refine StringSpanValue(), StringParseValue()
08-AUG-2002  MGD  add StringUrlEncodeURL()
12-JAN-2002  MGD  added '**' wildcard match to StringMatch(),
                  bugfix; StringMatch() wildcard matching
13-OCT-2001  MGD  uncoupled from SUPPORT.C,
                  StringMatch() replaces SearchTextString()
                  for more light-weight text matching,
                  StringSpanValue(), StringParseValue(),
                  StringParseNameValue()
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#undef __VMS_VER
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

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

/* VMS related header files */
#include <ssdef.h>
#include <stsdef.h>

/* application related header files */
#include "wasd.h"

#define WASD_MODULE "STRNG"

/********************/
/* external storage */
/********************/

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

extern char  *FaoUrlEncodeTable[];

extern char  ErrorSanityCheck[];

extern CONFIG_STRUCT  Config;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Case insensitive wildcard string match or regular expression match.
StringMatch() and StringMatchGreedy() are macros for this function using fixed
values for 'MatchType', and a 'pregptr' and 'pmatchptr' of NULL.  StringMatch()
is non-greedy and StringMatchGreedy() the greedy verion (see below).  If either
strings is NULL or 'PatternPtr' empty return false.

Start with a light-weight character match.  Even if regex is eventually
required this will eliminate many strings on simple character comparison before
more heavy-weight pattern matching needs to be called into play.

If regular expression configuration is enabled, and the 'PatternPtr' string
begins with a REGEX_CHAR ('^'), or a 'pregptr' is supplied, a regex match is
undertaken.  'PregPtr' must have already been compiled or it is ignored and a
scratch 'preg' is compiled and then freed.  The regular expression matching is
case-insensitive and takes EGREP style expressions.

If not a regular expression then a string match allowing wildcard "*" (matching
any zero or more characters) and "%" (matching any single character).  Matches
the string specified in 'StringPtr' against the pattern supplied in
'PatternPtr'.  A double asterisk (i.e. **) makes the match more of a find-in
string search than a pattern match.  Due to some pretty mixed past decisions on
how string searching should be conducted there have been two expectations
throughout the HTTPd.  The first that a wildcard is "greedy" (making the
longest possible match).  This matches up until any string following the
wildcard fails to match, or it encounters another wildcard.  The second
"non-greedy" (the first non-match of the character following the wildcard
aborts the match, or it encounters another wildcard).  This wildcard match
generates similar information to regex matching using the data in a
'regmatch_t' structure.  Each element contains the start and end offsets of
wildcard matched portion of the 'StringPtr' text.
*/ 

BOOL StringMatchAndRegex
( 
REQUEST_STRUCT *rqptr,
char *StringPtr,
char *PatternPtr,
int MatchType,
regex_t *pregptr,
regmatch_t *pmatchptr
)
{
   BOOL  MetaCharHit,
         TwoWild,
         WatchThisMatch;
   int  pidx, retval,
        RegexSyntax;
   char  ch;
   char  *pptr, *sptr, *tsptr, *tpptr;
   char  ErrorString [32];
   regex_t  preg;
   regmatch_t  pmatch [REGEX_PMATCH_MAX];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER,
                 "StringMatchAndRegex() !&Z !&Z !UL !&X !&X",
                 StringPtr, PatternPtr, MatchType, pregptr, pmatchptr);

   if (WATCHING (rqptr, WATCH_MATCH))
   {
      WatchThis (WATCHITM(rqptr), WATCH_MATCH, "!&Z !&?COMPILED:\r\r!&Z",
                 StringPtr, pregptr && pregptr->buffer, PatternPtr); 
      WatchThisMatch = true;
   }
   else
      WatchThisMatch = false;

   if (!StringPtr) return (false);
   if (!PatternPtr) return (false);

   /* initialize the match offset buffer */
   if (pmatchptr)
   {
      for (pidx = 0; pidx < REGEX_PMATCH_MAX; pidx++)
         pmatchptr[pidx].rm_so = pmatchptr[pidx].rm_eo = -1;
      pidx = 1;
   }

   /**********************/
   /* light-weight match */
   /**********************/

   sptr = StringPtr;
   pptr = PatternPtr;
   RegexSyntax = 0;

   if (Config.cfMisc.RegexSyntax && *pptr == REGEX_CHAR)
   {
      if (MatchType == SMATCH_STRING_REGEX ||
          MatchType == SMATCH_GREEDY_REGEX ||
          MatchType == SMATCH_REGEX)
      {
         /* must be a regex pattern */
         RegexSyntax = Config.cfMisc.RegexSyntax;
         pptr++;
         /* step over any start-of-line anchor seeing we're there already */
         if (*pptr == '^')
         {
            pptr++;
            /* if regex for empty string, then it's a match */
            if (SAME2(pptr,'$\0') && !*sptr)
            {
               if (WatchThisMatch)
                  WatchThis (WATCHITM(rqptr), WATCH_MATCH, "YES prematch");
               if (!pmatchptr) return (true);
               pmatchptr[0].rm_so = 0;
               pmatchptr[0].rm_eo = 1;
               return (true);
            }
         }
      }
   }

   MetaCharHit = false;
   while (*pptr && *sptr)
   {
      switch (*pptr)
      {
         /* "significant" characters when pattern matching */
         case '%' :
            if (RegexSyntax) break;
         case '*' :
            MetaCharHit = true;
            break;
         case '^' :
         case '$' :
         case '.' :
         case '+' :
         case '?' :
         case '|' :
         case '{' :
         case '[' :
         case '(' :
         case '\\' :
            if (!RegexSyntax) break;
            MetaCharHit = true;
            break;
      }
      if (MetaCharHit) break;
      if (to_lower(*pptr) != to_lower(*sptr) && *pptr != '%') break;
      pptr++;
      sptr++;
   }

   if (!*pptr && !*sptr)
   {
      /* matched exactly */
      if (WatchThisMatch) WatchThis (WATCHITM(rqptr), WATCH_MATCH, "YES prematch");
      if (!pmatchptr) return (true);
      pmatchptr[0].rm_so = 0;
      pmatchptr[0].rm_eo = sptr - StringPtr;
      return (true);
   }
   if (SAME2(pptr,'*\0'))
   {
      /* pattern ended in a trailing wildcard, therefore it matches */
      if (WatchThisMatch) WatchThis (WATCHITM(rqptr), WATCH_MATCH, "YES prematch");
      if (!pmatchptr) return (true);
      pmatchptr[0].rm_so = 0;
      pmatchptr[1].rm_so = sptr - StringPtr;
      while (*sptr) sptr++;
      pmatchptr[0].rm_eo = pmatchptr[1].rm_eo = sptr - StringPtr;
      return (true);
   }
   if (!RegexSyntax && !MetaCharHit)
   {
      /* didn't match */
      if (!WatchThisMatch) return (false);
      WatchThis (WATCHITM(rqptr), WATCH_MATCH, "NO prematch !&Z", pptr);
      return (false);
   }

   /**********************/
   /* heavy-weight match */
   /**********************/

   switch (MatchType)
   {
      case SMATCH_STRING :        /* non-greedy match - no regex */
      case SMATCH_GREEDY :        /* greedy match - no regex */
         RegexSyntax = 0;
         break;
      case SMATCH_STRING_REGEX :  /* non-greedy match - allow regex */
      case SMATCH_GREEDY_REGEX :  /* greedy match - allow regex */
         if (pregptr && pregptr->buffer)
            RegexSyntax = Config.cfMisc.RegexSyntax;
         else
         if (*PatternPtr == REGEX_CHAR)
            RegexSyntax = Config.cfMisc.RegexSyntax;
         else
            RegexSyntax = 0;
         break;
      case SMATCH_REGEX :         /* regular expression only */
         RegexSyntax = Config.cfMisc.RegexSyntax;
         break;
      default :
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   sptr = StringPtr;
   pptr = PatternPtr;

   if (RegexSyntax)
   {
      /**********************/
      /* regular expression */                       
      /**********************/

      if (!pmatchptr) pmatchptr = &pmatch;

      if (rqptr && rqptr->rqPathSet.RegexSyntax)
      {
         if (MatchType != SMATCH_REGEX) pptr++;
         if (WATCHING (rqptr, WATCH_MATCH))
            WatchThis (WATCHITM(rqptr), WATCH_MATCH, "REGEX flags !8XL",
                       rqptr->rqPathSet.RegexSyntax);
         retval = regcomp (pregptr = &preg, pptr, rqptr->rqPathSet.RegexSyntax);
      }
      else
      if (pregptr && pregptr->buffer)
         retval = 0;
      else
      {
         if (MatchType != SMATCH_REGEX) pptr++;
         retval = regcomp (pregptr = &preg, pptr, RegexSyntax);
      }

      if (!retval)
      {
         retval = regexec (pregptr, sptr, REGEX_PMATCH_MAX, pmatchptr,
                           REGEX_E_FLAGS);
         if (pregptr == &preg) regfree (pregptr);
         if (retval)
         {
            if (!WatchThisMatch) return (false);
            WatchThis (WATCHITM(rqptr), WATCH_MATCH, "NO regex");
            return (false);
         }
         else
         {
            if (!WatchThisMatch) return (true);
            WatchThis (WATCHITM(rqptr), WATCH_MATCH, "YES regex");
            StringWatchPmatch (StringPtr, pmatchptr);
            return (true);
         }
      }

      regerror (retval, pregptr, ErrorString, sizeof(ErrorString));
      /* always output a regex compile error when WATCHing! */
      if (WatchThisMatch)
         WatchThis (WATCHITM(rqptr), WATCH_MATCH,
                    "COMPILE regex !AZ", ErrorString);
      else
      if (WATCHPNT(rqptr))
         WatchThis (WATCHITM(rqptr), WATCH_MATCH,
                    "COMPILE regex !&Z !AZ", pptr, ErrorString);
      ErrorNoticed (rqptr, 0, "regex \"!AZ\" !AZ", FI_LI, pptr, ErrorString);
      return (false);
   }

   /****************/
   /* string match */
   /****************/

   for (;;)
   {
      while (*pptr && *sptr && *pptr != '*' && *pptr != '%' &&
             to_lower(*pptr) == to_lower(*sptr))
      {
         pptr++;
         sptr++;
      }
      if (!*pptr && !*sptr)
      {
         if (pmatchptr)
         {
            pmatchptr[0].rm_so = 0;
            pmatchptr[0].rm_eo = sptr - StringPtr;
         }
         if (!WatchThisMatch) return (true);
         WatchThis (WATCHITM(rqptr), WATCH_MATCH, "YES wildcard");
         if (pmatchptr) StringWatchPmatch (StringPtr, pmatchptr);
         return (true);
      }
      if (*pptr != '*' && *pptr != '%')
      {
         if (pmatchptr) pmatchptr[0].rm_so = pmatchptr[0].rm_eo = -1;
         if (!WatchThisMatch) return (false);
         WatchThis (WATCHITM(rqptr), WATCH_MATCH, "NO wildcard !&Z", pptr);
         return (false);
      }
      if (*pptr == '%')
      {
         /* single char wildcard processing */
         if (*sptr)
         {
            if (pmatchptr && pidx < REGEX_PMATCH_MAX)
            {
               pmatchptr[pidx].rm_so = sptr - StringPtr;
               pmatchptr[pidx].rm_eo = pmatchptr[pidx].rm_so + 1;
               pidx++;
            }
            pptr++;
            sptr++;
            continue;
         }
         if (pmatchptr) pmatchptr[0].rm_so = pmatchptr[0].rm_eo = -1;
         if (!WatchThisMatch) return (false);
         WatchThis (WATCHITM(rqptr), WATCH_MATCH, "NO wildcard !&Z", pptr);
         return (false);
      }

      TwoWild = SAME2(pptr,'**');
      while (*pptr == '*') pptr++;
      /* an asterisk wildcard at end matches all following */
      if (!*pptr)
      {
         if (pmatchptr)
         {
            if (pidx < REGEX_PMATCH_MAX)
            {
               pmatchptr[pidx].rm_so = sptr - StringPtr;
               while (*sptr) sptr++;
               pmatchptr[pidx].rm_eo = sptr - StringPtr;
               pmatchptr[0].rm_so = 0;
               pmatchptr[0].rm_eo = sptr - StringPtr;
            }
         }
         if (!WatchThisMatch) return (true);
         WatchThis (WATCHITM(rqptr), WATCH_MATCH, "YES wildcard");
         if (pmatchptr) StringWatchPmatch (StringPtr, pmatchptr);
         return (true);
      }

      if (TwoWild ||
          MatchType == SMATCH_GREEDY ||
          MatchType == SMATCH_GREEDY_REGEX)
      {
         /*****************************/
         /* two consecutive asterisks */
         /*****************************/

         /* note current position in the string (first after the wildcard) */
         tpptr = pptr;
         if (pmatchptr && pidx < REGEX_PMATCH_MAX)
            pmatchptr[pidx].rm_so = sptr - StringPtr;
         for (;;)
         {
            /* find first char in StringPtr matching char after wildcard */
            ch = to_lower(*pptr);
            while (*sptr && to_lower(*sptr) != ch) sptr++;
            if (!*sptr)
            {
               /* did not find one of those characters */
               if (pmatchptr)
               {
                  if (pidx < REGEX_PMATCH_MAX) pmatchptr[pidx].rm_so = -1;
                  pmatchptr[0].rm_so = pmatchptr[0].rm_eo = -1;
               }
               if (!WatchThisMatch) return (false);
               WatchThis (WATCHITM(rqptr), WATCH_MATCH, "NO wildcard !&Z", pptr);
               return (false);
            }
            /* note the current position in StringPtr being searched */
            if (pmatchptr && pidx < REGEX_PMATCH_MAX)
               pmatchptr[pidx].rm_eo = sptr - StringPtr;
            tsptr = sptr;
            /* try to match the string trailing the wildcard */
            while (*pptr && *sptr && to_lower(*pptr) == to_lower(*sptr))
            {
               pptr++;
               sptr++;
            }
            if (!*pptr && !*sptr)
            {
               /* reached the end of both strings - match! */
               if (pmatchptr)
               {
                  pmatchptr[0].rm_so = 0;
                  pmatchptr[0].rm_eo = sptr - StringPtr;
               }
               if (!WatchThisMatch) return (true);
               WatchThis (WATCHITM(rqptr), WATCH_MATCH, "YES wildcard");
               if (pmatchptr) StringWatchPmatch (StringPtr, pmatchptr);
               return (true);
            }
            /* break to the external loop if encountered another wildcard */
            if (*pptr == '*' || *pptr == '%')
            {
               if (pidx < REGEX_PMATCH_MAX) pidx++;
               break;
            }
            /* another try starting at character following previous attempt */
            if (pmatchptr && pidx < REGEX_PMATCH_MAX)
               pmatchptr[pidx].rm_eo = -1;
            pptr = tpptr;
            sptr = tsptr + 1;
         }
         if (pmatchptr && pidx < REGEX_PMATCH_MAX) pmatchptr[pidx].rm_so = -1;
      }
      else
      {
         /****************/
         /* one asterisk */
         /****************/

         if (pmatchptr && pidx < REGEX_PMATCH_MAX)
            pmatchptr[pidx].rm_so = sptr - StringPtr;
         for (;;)
         {
            /* find first char in StringPtr matching char after wildcard */
            ch = to_lower(*pptr);
            while (*sptr && to_lower(*sptr) != ch) sptr++;
            if (!*sptr)
            {
               /* did not find one of those characters */
               if (pmatchptr)
               {
                  if (pidx < REGEX_PMATCH_MAX) pmatchptr[pidx].rm_so = -1;
                  pmatchptr[0].rm_so = pmatchptr[0].rm_eo = -1;
               }
               if (!WatchThisMatch) return (false);
               WatchThis (WATCHITM(rqptr), WATCH_MATCH, "NO wildcard !&Z", pptr);
               return (false);
            }
            /* try to match the string trailing the wildcard */
            if (pmatchptr && pidx < REGEX_PMATCH_MAX)
               pmatchptr[pidx].rm_eo = sptr - StringPtr;
            while (*pptr && *sptr && to_lower(*pptr) == to_lower(*sptr))
            {
               pptr++;
               sptr++;
            }
            if (!*pptr && !*sptr)
            {
               /* reached the end of both strings - match! */
               if (pmatchptr)
               {
                  pmatchptr[0].rm_so = 0;
                  pmatchptr[0].rm_eo = sptr - StringPtr;
               }
               if (!WatchThisMatch) return (true);
               WatchThis (WATCHITM(rqptr), WATCH_MATCH, "YES wildcard");
               if (pmatchptr) StringWatchPmatch (StringPtr, pmatchptr);
               return (true);
            }
            /* break to the external loop if encountered another wildcard */
            if (*pptr == '*' || *pptr == '%')
            {
               if (pidx < REGEX_PMATCH_MAX) pidx++;
               break;
            }
            if (pmatchptr)
            {
               if (pidx < REGEX_PMATCH_MAX) pmatchptr[pidx].rm_so = -1;
               pmatchptr[0].rm_so = pmatchptr[0].rm_eo = -1;
            }
            if (!WatchThisMatch) return (false);
            WatchThis (WATCHITM(rqptr), WATCH_MATCH, "NO wildcard !&Z", pptr);
            return (false);
         }
      }
   }
}

/*****************************************************************************/
/*
Uses the 'regmatch_t' offset data to populate an output buffer with wildcard
substitutions from the 'regmatch_t' array (as might be generated by regexec()
or StringMatchAndRegex()) according to specification provided in 'Result'. 
This is completely backward-compatible with the way the MAPURL.C module has
performed wildcard substitutions from 'template' to 'result' strings.  That is,
the result substitutes it's wildcard specified strings out of the source string
in the same order as matched by the pattern (template) specification.  If there
was no corresponding wildarded string the substitution is just ignored.  With
this new substitution scheme (function) it is also possible to specify which
wildcarded string should be substituted where.  This is specified in the result
string using a "*'<n>" sequence, where <n> is a single digit from 0..9 (e.g.
"*'1").  This allows slightly greater flexibility if required.  If there is no
digit following the "*'", the substitution is just ignored.
*/ 

int StringMatchSubstitute
(
REQUEST_STRUCT *rqptr,
char *String,
char *Result,
regmatch_t *pmatchptr,
char *Buffer,
int SizeOfBuffer
)
{
   int  idx, pidx;
   char  *cptr, *sptr, *zptr,
         *pcptr, *pzptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER,
                 "StringMatchSubstitute() !&Z !&Z !&X !UL",
                 String, Result, Buffer, SizeOfBuffer);

   pidx = 1;
   zptr = (sptr = Buffer) + SizeOfBuffer;
   cptr = Result;
   while (*cptr && sptr < zptr)
   {
      if (*cptr != '*')
      {
         *sptr++ = *cptr++;
         continue;
      }
      while (*cptr && *cptr == '*') cptr++;
      if (*cptr != '\'')
      {
         if (pidx >= REGEX_PMATCH_MAX) continue;
         if (pmatchptr[pidx].rm_so != -1 && pmatchptr[pidx].rm_so != -1)
         {
            pcptr = String + pmatchptr[pidx].rm_so;
            pzptr = String + pmatchptr[pidx].rm_eo;
            if (WATCH_MODULE(WATCH_MOD__OTHER))
               WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER, "!UL {!UL}!-!#AZ",
                          pidx, pzptr - pcptr, pcptr);
            while (pcptr < pzptr && sptr < zptr) *sptr++ = *pcptr++;
         }
         pidx++;
         continue;
      }
      cptr++;
      if (!isdigit(*cptr)) continue;
      idx = *cptr++ - '0';  /* i.e. index from 0 to 9 */
      if (idx >= REGEX_PMATCH_MAX) continue;
      if (pmatchptr[idx].rm_so != -1 && pmatchptr[idx].rm_so != -1)
      {
         pcptr = String + pmatchptr[idx].rm_so;
         pzptr = String + pmatchptr[idx].rm_eo;
         if (WATCH_MODULE(WATCH_MOD__OTHER))
            WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER, "!UL {!UL}!-!#AZ",
                       idx, pzptr - pcptr, pcptr);
         while (pcptr < pzptr && sptr < zptr) *sptr++ = *pcptr++;
      }
   }
   if (sptr < zptr)
   {
      *sptr = '\0';
      if (WATCH_MODULE(WATCH_MOD__OTHER))
         WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER, "!&Z", Buffer);
      return (SS$_NORMAL);
   }
   *Buffer = '\0';
   return (SS$_RESULTOVF);
}

/*****************************************************************************/
/*
Compile the supplied regular expression into the supplied 'preg' structure. 
Return a NULL if the compile is successful, a pointer to static storage
containing an error message if not.
*/

char* StringRegexCompile
(
char *Pattern,
regex_t *pregptr
)
{
   static char  ErrorString [64];

   int  retval,
        RegexSyntax;

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

   if (!(RegexSyntax = Config.cfMisc.RegexSyntax))
      RegexSyntax = REGEX_C_FLAGS;
   retval = regcomp (pregptr, Pattern, Config.cfMisc.RegexSyntax);
   if (!retval) return (NULL);
   regerror (retval, pregptr, ErrorString, sizeof(ErrorString));
   return (ErrorString);
}

/*****************************************************************************/
/*
Provide a report page which allows the matching or a string with a pattern
(either wildcard or regular expression).  This allows these expressions to be
experimentally evaluated from the Servr Administration menu.
*/ 

StringMatchReport (REQUEST_STRUCT *rqptr)

{
   static char  BeginPage [] =
"<form method=\"POST\" action=\"!AZ\">\n\
<p><table cellpadding=\"3\" cellspacing=\"0\" border=\"1\">\n\
<tr><td>\n\
<table cellpadding=\"3\" cellspacing=\"0\" border=\"0\">\n";

   static char  MatchFao [] =
"<tr><th align=\"right\">String:</th><td><tt>!&;AZ</tt></td></tr>\n\
<tr><th align=\"right\">Pattern:</th><td><tt>!&;AZ</tt></td></tr>\n\
<tr><th align=\"right\" valign=\"top\">Match:</th><td>!&;AZ&nbsp;&nbsp;<i>(!AZ)</i>!AZ";

   static char  MatchEnd [] = "</td></tr>\n";

   static char  PmatchFao [] =
"<b>!UL.</b> !7<{!UL,!UL}!> <b>|</b>!&;AZ<b>|</b>\n";

   static char  PmatchEnd [] = "</pre></td></tr>\n";

   static char  ResultFao [] =
"<tr><th align=\"right\">Result:</th><td>!&@</td></tr>\n\
<tr><th></th><td>!&@</td></tr>\n\
<tr><td></td></tr>\n";

   static char  EndPageFao [] =
"<tr><th align=\"right\">String:</th>\
<td><input type=\"text\" size=\"80\" name=string value=\"!&;AZ\"></td></tr>\n\
<tr><th align=\"right\">Pattern:</th>\
<td><input type=\"text\" size=\"80\" name=pattern value=\"!&;AZ\"></td></tr>\n\
<tr><th></th><td><font size=\"-1\">\
<input type=\"radio\" name=match value=\"!UL\"!AZ>match&nbsp;\
<input type=\"radio\" name=match value=\"!UL\"!AZ>greedy&nbsp;\
<input type=\"radio\" name=match value=\"!UL\"!AZ>regex&nbsp;\
<input type=\"text\" size=\"1\" name=regex value=\"!&;AZ\">&nbsp;!8XL&nbsp;(!AZ)\
</font></td></tr>\n\
<tr><th align=\"right\">Result:</th><td>\
<input type=\"text\" size=\"80\" name=result value=\"!&;AZ\"></td></tr>\n\
<tr><th></th><td align=\"left\">\
<input type=\"submit\" value=\"Match\">&nbsp;&nbsp;\
<input type=reset value=\"Reset\">\
</td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
</form>\n\
<p>[<a href=\"!AZ\">Server Admin</a>]\n\
</body>\n\
</html>\n";

   int  idx, retval, status,
        MatchType,
        RegexSyntax;
   unsigned long  *vecptr;
   unsigned long  FaoVector [32];
   char  *cptr, *qptr, *sptr, *zptr;
   char  FieldName [128],
         FieldValue [256],
         Pattern [256],
         RegEx [256],
         Result [256],
         Scratch [256],
         String [256];
   regex_t  preg;
   regmatch_t  pmatch [REGEX_PMATCH_MAX];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER, "StringMatchReport()");

   if (rqptr->rqHeader.Method == HTTP_METHOD_POST)
   {
      if (!rqptr->rqBody.DataPtr)
      {
         /* read all the request body (special case) then AST back */
         BodyReadBegin (rqptr, &StringMatchReport, &BodyProcessReadAll);
         return;
      }
      qptr = rqptr->rqBody.DataPtr;
   }
   else
   if (rqptr->rqHeader.QueryStringLength)
      qptr = rqptr->rqHeader.QueryStringPtr;
   else
      qptr = "";

   MatchType = RegexSyntax = 0;
   Pattern[0] = RegEx[0] = Result[0] = String[0] = '\0';

   while (*qptr)
   {
      status = StringParseQuery (&qptr, FieldName, sizeof(FieldName),
                                        FieldValue, sizeof(FieldValue));
      if (VMSnok (status))
      {
         /* error occured */
         if (status == SS$_IVCHAR) rqptr->rqResponse.HttpStatus = 400;
         rqptr->rqResponse.ErrorTextPtr = "parsing query string";
         ErrorVmsStatus (rqptr, status, FI_LI);
         AdminEnd (rqptr);
         return;
      }

      if (strsame (FieldName, "match", -1))
         MatchType = atoi(FieldValue);
      else
      if (strsame (FieldName, "pattern", -1))
         strzcpy (Pattern, FieldValue, sizeof(Pattern));
      else
      if (strsame (FieldName, "regex", -1))
         strzcpy (RegEx, FieldValue, sizeof(RegEx));
      else
      if (strsame (FieldName, "result", -1))
         strzcpy (Result, FieldValue, sizeof(Result));
      else
      if (strsame (FieldName, "string", -1))
         strzcpy (String, FieldValue, sizeof(String));
      else
      {
         ErrorGeneral (rqptr, "Unknown query field.", FI_LI);
         AdminEnd (rqptr);
         return;
      }
   }

   if (!MatchType) MatchType = SMATCH_STRING;

   if (strsame (RegEx, "AWK", 3))
      RegexSyntax = RE_SYNTAX_AWK;
   else
   if (strsame (RegEx, "ED", 2))
      RegexSyntax = RE_SYNTAX_ED;
   else
   if (strsame (RegEx, "EGREP", 5))
      RegexSyntax = RE_SYNTAX_EGREP;
   else
   if (strsame (RegEx, "GREP", 4))
      RegexSyntax = RE_SYNTAX_GREP;
   else
   if (strsame (RegEx, "POSIX_AWK", 9))
      RegexSyntax = RE_SYNTAX_POSIX_AWK;
   else
   if (strsame (RegEx, "POSIX_BASIC", 11))
      RegexSyntax = RE_SYNTAX_POSIX_BASIC;
   else
   if (strsame (RegEx, "POSIX_EGREP", 11))
      RegexSyntax = RE_SYNTAX_POSIX_EGREP;
   else
   if (strsame (RegEx, "POSIX_EXTENDED", 14))
      RegexSyntax = RE_SYNTAX_POSIX_EXTENDED;
   else
   if (strsame (RegEx, "POSIX_MINIMAL_BASIC", 19))
      RegexSyntax = RE_SYNTAX_POSIX_MINIMAL_BASIC;
   else
   if (strsame (RegEx, "POSIX_MINIMAL_EXTENDED", 22))
      RegexSyntax = RE_SYNTAX_POSIX_MINIMAL_EXTENDED;
   else
   if (strsame (RegEx, "SED", 3))
      RegexSyntax = RE_SYNTAX_AWK;
   else
   if (strsame (RegEx, "WASD", 4))
      RegexSyntax = REGEX_C_FLAGS;
   else
   if (isdigit(RegEx[0]))
      RegexSyntax = atoi(RegEx);
   else
      RegexSyntax = REGEX_C_FLAGS;

   AdminPageTitle (rqptr, "String Match Report",
                   BeginPage, ADMIN_REPORT_MATCH);

   if (Pattern[0])
   {
      if (MatchType == SMATCH_REGEX ||
          Pattern[0] == REGEX_CHAR)
      {
         if (MatchType == SMATCH_REGEX)
            retval = regcomp (&preg, Pattern, RegexSyntax);
         else
            retval = regcomp (&preg, Pattern+1, RegexSyntax);
         if (retval)
         {
            regerror (retval, &preg, Scratch, sizeof(Scratch));
            vecptr = FaoVector;
            *vecptr++ = String;
            *vecptr++ = Pattern;
            *vecptr++ = Scratch;
            *vecptr++ = "regex";
            *vecptr++ = MatchEnd;
            status = FaolToNet (rqptr, MatchFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         }
         else
         {
            retval = regexec (&preg, String, REGEX_PMATCH_MAX, pmatch,
                              REGEX_E_FLAGS);
            regfree (&preg);
            vecptr = FaoVector;
            *vecptr++ = String;
            *vecptr++ = Pattern;
            if (!retval)
            {
               *vecptr++ = "YES";
               *vecptr++ = "regex";
               *vecptr++ = "<pre>";
            }
            else
            {
               *vecptr++ = "NO";
               *vecptr++ = "regex";
               *vecptr++ = MatchEnd;
            }
            status = FaolToNet (rqptr, MatchFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         }
      }
      else
      {
         retval = StringMatchAndRegex (rqptr, String, Pattern,
                                       MatchType, NULL, &pmatch);
         vecptr = FaoVector;
         *vecptr++ = String;
         *vecptr++ = Pattern;
         if (retval)
         {
            *vecptr++ = "YES";
            *vecptr++ = "wildcard";
            *vecptr++ = "<pre>";
         }
         else
         {
            *vecptr++ = "NO";
            *vecptr++ = "wildcard";
            *vecptr++ = MatchEnd;
         }
         status = FaolToNet (rqptr, MatchFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         retval = !retval;
      }

      if (!retval)
      {
         /* success, display the matching offsets (strings) */
         for (idx = 0; idx < REGEX_PMATCH_MAX; idx++)
         {
            if (pmatch[idx].rm_so != -1 || pmatch[idx].rm_eo != -1)
            {
               zptr = (sptr = Scratch) + sizeof(Scratch)-1;
               for (cptr = String + pmatch[idx].rm_so;
                    cptr < String + pmatch[idx].rm_eo && sptr < zptr;
                    *sptr++ = *cptr++);
               *sptr = '\0';
               vecptr = FaoVector;
               *vecptr++ = idx;
               *vecptr++ = pmatch[idx].rm_so;
               *vecptr++ = pmatch[idx].rm_eo ? pmatch[idx].rm_eo - 1 : 0;
               *vecptr++ = Scratch;
               status = FaolToNet (rqptr, PmatchFao, &FaoVector);
               if (VMSnok (status))
                  ErrorNoticed (rqptr, status, NULL, FI_LI);
            }
         }
         status = FaolToNet (rqptr, PmatchEnd, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      }

      vecptr = FaoVector;
      if (Result[0] && !retval)
      {
         status = StringMatchSubstitute (rqptr, String, Result, &pmatch,
                                         Scratch, sizeof(Scratch));
         if (VMSnok (status))
            FaoToBuffer (Scratch, sizeof(Scratch), NULL, "!&S", status);
         *vecptr++ = "<tt>!&;AZ</tt>";
         *vecptr++ = Result;
         *vecptr++ = "<tt><b>|</b>!&;AZ<b>|</b></tt>";
         *vecptr++ = Scratch;
      }
      else
      {
         *vecptr++ = "!AZ";
         *vecptr++ = "<i>(none)</i>";
         *vecptr++ = "!AZ";
         *vecptr++ = "<i>(none)</i>";
      }
      status = FaolToNet (rqptr, ResultFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

   vecptr = FaoVector;
   *vecptr++ = String;
   *vecptr++ = Pattern;
   *vecptr++ = SMATCH_STRING;
   if (MatchType == SMATCH_STRING)
      *vecptr++ = " CHECKED";
   else
      *vecptr++ = "";
   *vecptr++ = SMATCH_GREEDY;
   if (MatchType == SMATCH_GREEDY)
      *vecptr++ = " CHECKED";
   else
      *vecptr++ = "";
   *vecptr++ = SMATCH_REGEX;
   if (MatchType == SMATCH_REGEX)
      *vecptr++ = " CHECKED";
   else
      *vecptr++ = "";
   *vecptr++ = RegEx;

   if (RegexSyntax)
      *vecptr++ = RegexSyntax;
   else
      *vecptr++ = Config.cfMisc.RegexSyntax;

   switch (RegexSyntax)
   {
      case 0 : *vecptr++ = "disabled"; break;
      case RE_SYNTAX_AWK : *vecptr++ = "AWK"; break; 
      case RE_SYNTAX_EGREP : *vecptr++ = "EGREP"; break; 
      case RE_SYNTAX_GREP : *vecptr++ = "GREP"; break; 
      case RE_SYNTAX_POSIX_AWK : *vecptr++ = "POSIX_AWK"; break; 
      case RE_SYNTAX_POSIX_BASIC : *vecptr++ = "POSIX_BASIC/ED/SED"; break; 
      case RE_SYNTAX_POSIX_EGREP : *vecptr++ = "POSIX_EGREP"; break; 
      case RE_SYNTAX_POSIX_EXTENDED : *vecptr++ = "POSIX_EXTENDED"; break; 
      case RE_SYNTAX_POSIX_MINIMAL_BASIC :
                *vecptr++ = "POSIX_MINIMAL_BASIC"; break; 
      case RE_SYNTAX_POSIX_MINIMAL_EXTENDED :
                *vecptr++ = "POSIX_MINIMAL_EXTENDED"; break; 
      case REGEX_C_FLAGS : *vecptr++ = "WASD"; break; 
      default : *vecptr++ = "custom";
   }

   *vecptr++ = Result;
   *vecptr++ = HTTPD_ADMIN;
   status = FaolToNet (rqptr, EndPageFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc);

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
WATCH each of the valid 'pmatch' offsets.
*/

StringWatchPmatch
(
char *String,
regmatch_t *pmatchptr
)
{
   int  idx;
   char  *cptr, *sptr, *zptr;
   char  Scratch [256];

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

   for (idx = 0; idx < REGEX_PMATCH_MAX; idx++)
   {
      if (pmatchptr[idx].rm_so == -1 || pmatchptr[idx].rm_eo == -1) continue;
      zptr = (sptr = Scratch) + sizeof(Scratch)-1;
      for (cptr = String + pmatchptr[idx].rm_so;
           cptr < String + pmatchptr[idx].rm_eo && sptr < zptr;
           *sptr++ = *cptr++);
      *sptr = '\0';
      WatchDataFormatted ("!UL. {!UL,!UL} !AZ\n",
                          idx, pmatchptr[idx].rm_so,
                          pmatchptr[idx].rm_eo ? pmatchptr[idx].rm_eo - 1 : 0,
                          Scratch);
   }
}

/*****************************************************************************/
/*
A null terminated string is parsed for the next "fieldname=fieldvalue[&]"
pair.  Return an appropriate VMS status code.
*/

int StringParseQuery
(
char **QueryStringPtrPtr,
char *FieldName,
int SizeOfFieldName,
char *FieldValue,
int SizeOfFieldValue
)
{
   char  *cptr, *qptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "StringParseQuery() !&Z",
                 QueryStringPtrPtr ? *QueryStringPtrPtr : NULL);

   if (!QueryStringPtrPtr || !*QueryStringPtrPtr || !FieldName || !FieldValue)
      return (SS$_BADPARAM);

   qptr = *QueryStringPtrPtr;
   zptr = (sptr = FieldName) + SizeOfFieldName;
   while (*qptr && *qptr != '=' && sptr < zptr)
   {
      while (*qptr == '\r' || *qptr == '\n') qptr++;
      while (*qptr && *qptr != '=' && *qptr != '\r' && *qptr != '\n' &&
             sptr < zptr) *sptr++ = *qptr++;
   }
   if (sptr >= zptr) return (SS$_RESULTOVF);
   *sptr = '\0';

   if (*qptr++ != '=') return (SS$_IVCHAR);

   zptr = (sptr = FieldValue) + SizeOfFieldValue;
   while (*qptr && *qptr != '&' && sptr < zptr)
   {
      while (*qptr == '\r' || *qptr == '\n') qptr++;
      while (*qptr && *qptr != '&' && *qptr != '\r' && *qptr != '\n' &&
             sptr < zptr) *sptr++ = *qptr++;
   }
   if (sptr >= zptr) return (SS$_RESULTOVF);
   *sptr = '\0';

   if (*qptr && *qptr++ != '&') return (SS$_IVCHAR);

   if (StringUrlDecode (FieldValue) < 0) return (SS$_IVCHAR);

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "!&Z !&Z", FieldName, FieldValue);

   *QueryStringPtrPtr = qptr;
   return (SS$_NORMAL);
}

/****************************************************************************/
/*
URL-decodes a string in place (can do this because URL-decoded text is always
the same or less than the length of the original).  Returns the number of
characters in the decoded string, or -1 to indicate a URL-encoding error.
*/ 
 
int StringUrlDecode (char *String)

{
   char  *cptr, *sptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
       WatchThis (WATCHALL, WATCH_MOD__OTHER,
                  "StringUrlDecode() !&Z", String);

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

         case '%' :
            cptr++;
            if (*cptr >= '0' && *cptr <= '9')
               *sptr = ((unsigned char)(*cptr - '0')) << 4;
            else
            if (to_upper(*cptr) >= 'A' && to_upper(*cptr) <= 'F')
               *sptr = ((unsigned char)(to_upper(*cptr) - '7')) << 4;
            else
               return (-1);
            if (*cptr) cptr++;
            if (*cptr >= '0' && *cptr <= '9')
               *sptr |= *cptr - '0';
            else
            if (to_upper(*cptr) >= 'A' && to_upper(*cptr) <= 'F')
               *sptr |= to_upper(*cptr) - '7';
            else
               return (-1);
            sptr++;
            if (*cptr) cptr++;
            break;

         default :
            *sptr++ = *cptr++;
      }
   }
   *sptr = '\0';

   if (WATCH_MODULE(WATCH_MOD__OTHER))
       WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&Z", String);

   return (sptr - String);
}
 
/*****************************************************************************/
/*
URL-encode a string.  Returns the number of characters in the new string.
*/ 

int StringUrlEncode
( 
char *UrlString,
char *EncString,
int SizeOfEncString
)
{
   char  *cptr, *eptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "StringUrlEncode() !&Z", UrlString);

   zptr = (sptr = EncString) + SizeOfEncString-1;
   cptr = UrlString;
   while (*cptr && sptr < zptr)
   {
      eptr = FaoUrlEncodeTable[*(unsigned char*)cptr++];
      while (*eptr && sptr < zptr) *sptr++ = *eptr++;
   }
   *sptr = '\0';

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchDataFormatted ("!&Z\n", UrlString);
   return (sptr - EncString);
}  

/*****************************************************************************/
/*
Specially URL-encode a URL string.  This leaves the 'scheme://the.host/'
portion untouched, URL-encodes the path component, then any query string
component not encoding '+', &' or '=' characters.  Returns the number of
characters in the new string.
*/ 

int StringUrlEncodeURL
( 
char *UrlString,
char *EncString,
int SizeOfEncString
)
{
   static char  HexDigits [] = "0123456789abcdef";

   BOOL  HitQueryString;
   char  ch;
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "StringUrlEncodeURL() !&Z", UrlString);

   zptr = (sptr = EncString) + SizeOfEncString-1;
   cptr = UrlString;
   if (*cptr != '/')
   {
      /* must have a scheme/host component */
      while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++;
      if (SAME2(cptr,':/'))
      {
         if (sptr < zptr) *sptr++ = *cptr++;
         if (sptr < zptr) *sptr++ = *cptr++;
         if (*cptr == '/' && sptr < zptr) *sptr++ = *cptr++;
         /* now we've skipped over any 'http://' */
      }
      /* locate the start of the path */
      while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
   }
   /* copy URL-encoding path then query string appropriately */
   HitQueryString = false;
   while (*cptr && sptr < zptr)
   {
      ch = *cptr++;
      if (isalnum(ch) || ch == '/' || ch == '-' ||
            ch == '_' || ch == '$' || ch == '*' ||
            ch == ':' || ch == '[' || ch == ']' ||
            ch == ';' || ch == '~' || ch == '.')
      {
         *sptr++ = ch;
         continue;
      }
      if (!HitQueryString && ch == '?')
      {
         *sptr++ = ch;
         HitQueryString = true;
         continue;
      }
      if (HitQueryString && (ch == '+' || ch == '&' || ch == '='))
      {
         *sptr++ = ch;
         continue;
      }
      if (ch == '%' && ((cptr[0] >= '0' && cptr[0] <= '9') ||
                        (cptr[0] >= 'A' && cptr[0] <= 'F') ||
                        (cptr[0] >= 'z' && cptr[0] <= 'f')))
      {
         /* this looks like an already URL-encoded sequence */
         *sptr++ = ch;
         continue;
      }
      /* encode this character */
      *sptr++ = '%';
      if (sptr < zptr) *sptr++ = HexDigits[(ch & 0xf0) >> 4];
      if (sptr < zptr) *sptr++ = HexDigits[ch & 0x0f];
   }
   *sptr = '\0';

   return (sptr - EncString);
}  

/*****************************************************************************/
/*
Scan along a (possibly) single or double quoted text from the supplied string,
otherwise white-space delimits the string.  White-space is allowed in a quoted
string.  The character '\' escapes the following character allowing otherwise
forbidden characters (e.g. quotes) be included in the string.  Unquoted text
cannot include a trailing semicolon - it's considered a field separator.
The processed string is terminated with a null character and the length
returned. 
*/ 

int StringSliceValue (char **StringPtrPtr)

{
   char  *aptr, *bptr, *cptr, *sptr;

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

   if (!StringPtrPtr || !*StringPtrPtr) return (0);
   sptr = bptr = cptr = *StringPtrPtr;

   if (*cptr == '\"')
   {
      if (SAME3(cptr,'\"0='))
      {
         /* kludge for DECnet scripting in MAPURL.C */
         *sptr++ = *cptr++;
         while (*cptr && *cptr != '\"')
         {
            if (*cptr == '\\') cptr++;
            if (*cptr) *sptr++ = *cptr++;
         }
         if (*cptr) *sptr++ = *cptr++;
      }
      else
      {
         for (aptr = cptr + 1; *aptr && *aptr != '\"'; aptr++)
            if (*aptr == '\\' && *(aptr+1)) aptr++;
         if (SAME4(aptr,'\"::\"'))
         {
            /* kludge for specified username DECnet scripting in MAPURL.C */
            *sptr++ = *cptr++;
            while (*cptr && *cptr != '\"')
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            if (*cptr) *sptr++ = *cptr++;
         }
         else
         {
            /* just another quote */
            cptr++;
            while (*cptr && *cptr != '\"')
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
         }
      }
   }
   else
   /* a single leading quote */
   if (*cptr == '\'')
   {
      cptr++;
      while (*cptr && *cptr != '\'')
      {
         if (*cptr == '\\') cptr++;
         if (*cptr) *sptr++ = *cptr++;
      }
   }
   else
   {
      /* white-space delimitted */
      while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr))
      {
         if (*cptr == '\"')
         {
            /* double-quote delimited portion */
            if (SAME3(cptr,'\"0='))
            {
               /* kludge for DECnet scripting in MAPURL.C */
               *sptr++ = *cptr++;
               while (*cptr && *cptr != '\"')
               {
                  if (*cptr == '\\') cptr++;
                  if (*cptr) *sptr++ = *cptr++;
               }
               if (*cptr) *sptr++ = *cptr++;
            }
            else
            {
               for (aptr = cptr + 1; *aptr && *aptr != '\"'; aptr++)
                  if (*aptr == '\\' && *(aptr+1)) aptr++;
               if (SAME4(aptr,'\"::\"'))
               {
                  /* kludge specified username DECnet scripting in MAPURL.C */
                  *sptr++ = *cptr++;
                  while (*cptr && *cptr != '\"')
                  {
                     if (*cptr == '\\') cptr++;
                     if (*cptr) *sptr++ = *cptr++;
                  }
                  if (*cptr) *sptr++ = *cptr++;
               }
               else
               {
                  /* just another quote */
                  cptr++;
                  while (*cptr && *cptr != '\"')
                  {
                     if (*cptr == '\\') cptr++;
                     if (*cptr) *sptr++ = *cptr++;
                  }
               }
            }
            continue;
         }
         if (*cptr == '\'')
         {
            /* single-quote delimited portion */
            cptr++;
            while (*cptr && *cptr != '\'')
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            if (*cptr) cptr++;
            continue;
         }
         if (*cptr == ';' && (ISLWS(*(cptr+1)) || ISEOL(*(cptr+1)))) break;
         if (*cptr == '\\') cptr++;
         if (*cptr) *sptr++ = *cptr++;
      }
   }
   if (*cptr) cptr++;
   *sptr = '\0';

   /* skip trailing possible field terminator and white-space */
   while (ISLWS(*cptr)) cptr++;
   *StringPtrPtr = cptr;
   return (sptr - bptr);
}

/*****************************************************************************/
/*
Parse a (possibly) single or double quoted text from the supplied string.
otherwise white-space (or white-space and semicolon) delimits the string. 
Single or double quotes may be used around value string.  White-space is
allowed in a quoted string.  The character '\' escapes the following character
allowing quotes to be included in the value string.  Unquoted text cannot
include a trailing semicolon - it's considered a field separator.  Returns an
indicative VMS status code.
*/ 

int StringParseValue
( 
char **StringPtrPtr,
char *ValuePtr,
int ValueSize
)
{
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "StringParseValue() !&Z",
                 StringPtrPtr ? *StringPtrPtr : NULL);

   if (!StringPtrPtr || !*StringPtrPtr || !ValuePtr) return (SS$_BADPARAM);
   cptr = *StringPtrPtr;
   if (!*cptr) return (SS$_ENDOFFILE);
   zptr = (sptr = ValuePtr) + ValueSize;

   while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr))
   {
      if (SAME2(cptr,'(\"') || SAME2(cptr,'(\'')) cptr++;
      if (*cptr == '\"')
      {
         cptr++;
         while (*cptr && *cptr != '\"' && sptr < zptr)
         {
            if (*cptr == '\\') cptr++;
            if (*cptr) *sptr++ = *cptr++;
         }
         if (*cptr) cptr++;
         if (*cptr == ',' || *cptr == ')')
         {
            while (*cptr == ',' || *cptr == ')') cptr++;
            break;
         }
      }
      else
      if (*cptr == '\'')
      {
         cptr++;
         while (*cptr && *cptr != '\'' && sptr < zptr)
         {
            if (*cptr == '\\') cptr++;
            if (*cptr) *sptr++ = *cptr++;
         }
         if (*cptr) cptr++;
         if (*cptr == ',' || *cptr == ')')
         {
            while (*cptr == ',' || *cptr == ')') cptr++;
            break;
         }
      }
      else
      {
         if (*cptr == ';' && (ISLWS(*(cptr+1)) || ISEOL(*(cptr+1)))) break;
         if (*cptr == '\\') cptr++;
         if (*cptr) *sptr++ = *cptr++;
      }
   }
   if (sptr >= zptr) return (SS$_RESULTOVF); 
   *sptr = '\0';

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchDataFormatted ("!&Z\n", ValuePtr);

   /* skip trailing possible field terminator and white-space */
   while (*cptr == ';') cptr++;
   while (ISLWS(*cptr)) cptr++;
   *StringPtrPtr = cptr;
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Parse text from a string.  Single or double quotes may be used around value
string otherwise white-spece delimits it.  White-space is allowed in a quoted
string.  The character '\' escapes the following character allowing quotes to
be included in the value string.  Unquoted text is delimited by white-space. 
Returns the length of the string or -1 to indicate an error.
*/ 

int StringParseOnlyValue
( 
char **StringPtrPtr,
char *ValuePtr,
int ValueSize
)
{
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "StringParseOnlyValue() !&Z",
                 StringPtrPtr ? *StringPtrPtr : NULL);

   if (!StringPtrPtr || !*StringPtrPtr || !ValuePtr) return (-1);
   cptr = *StringPtrPtr;
   zptr = (sptr = ValuePtr) + ValueSize-1;
   *sptr = '\0';
   if (!*cptr) return (0);

   if (*cptr == '\"')
   {
      cptr++;
      while (*cptr && *cptr != '\"' && sptr < zptr)
      {
         if (*cptr == '\\') cptr++;
         if (*cptr) *sptr++ = *cptr++;
      }
      if (*cptr) cptr++;
   }
   else
   if (*cptr == '\'')
   {
      cptr++;
      while (*cptr && *cptr != '\'' && sptr < zptr)
      {
         if (*cptr == '\\') cptr++;
         if (*cptr) *sptr++ = *cptr++;
      }
      if (*cptr) cptr++;
   }
   else
   {
      while (*cptr && !(ISLWS(*cptr) || ISEOL(*cptr)) && sptr < zptr)
      {
         if (*cptr == '\\') cptr++;
         if (*cptr) *sptr++ = *cptr++;
      }
   }

   *StringPtrPtr = cptr;
   *sptr = '\0';
   if (sptr >= zptr) return (-1); 

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchDataFormatted ("!&Z\n", ValuePtr);

   return (sptr - ValuePtr);
}

/*****************************************************************************/
/*
Parses name=VALUE pairs from a string.  The name will be converted to upper
case and contain only alpha-numerics and underscores (i.e. suitable for DCL
symbol names).  White-space delimits the string.  Single or double quotes may
be used around value string.  White-space is allowed in a quoted string.  The
character '\' escapes the following character allowing quotes to be included
in the value string.  These must be in one of the following formats ...

   name=value
   name="string with white space"
   name='string with \"white\" space and quotes'
   (NAME1=value1,NAME2="value \"2\"",NAME3='VALUE 3')

Parameter 'StringPtrPtr' must be the address of a pointer to char, and must be
initialized to the startup of the source string prior to the first call. 
Parameter 'NameAlphaNum' controls whether the name should only consist of
alpha-numeric-underscore characters (suitable for DCL symbol name for example).
Returns an indicative VMS status code.
*/ 

int StringParseNameValue
( 
char **StringPtrPtr,
BOOL NameAlphaNum,
char *NamePtr,
int NameSize,
char *ValuePtr,
int ValueSize
)
{
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "StringParseNameValue() !&Z",
                 StringPtrPtr ? *StringPtrPtr : NULL);

   if (!StringPtrPtr || !*StringPtrPtr || !NamePtr || !ValuePtr)
      return (SS$_BADPARAM);
   cptr = *StringPtrPtr;
   if (!*cptr) return (SS$_ENDOFFILE);

   while (*cptr && *cptr == '(') cptr++;
   zptr = (sptr = NamePtr) + NameSize;
   while (*cptr && *cptr != '=' && sptr < zptr)
   {
      if (*cptr == '\\') cptr++;
      if (*cptr)
         if (!NameAlphaNum || isalnum(*cptr) || *cptr == '_')
            *sptr++ = *cptr++;
         else
            cptr++;
   }
   if (sptr >= zptr) return (SS$_RESULTOVF); 
   *sptr = '\0';
   if (!NamePtr[0]) return (SS$_BADPARAM);
   if (*cptr) cptr++;
   zptr = (sptr = ValuePtr) + ValueSize;
   if (*cptr == '\"')
   {
      cptr++;
      while (*cptr && *cptr != '\"' && sptr < zptr)
      {
         if (*cptr == '\\') cptr++;
         if (*cptr) *sptr++ = *cptr++;
      }
      if (*cptr) cptr++;
   }
   else
   if (*cptr == '\'')
   {
      cptr++;
      while (*cptr && *cptr != '\'' && sptr < zptr)
      {
         if (*cptr == '\\') cptr++;
         if (*cptr) *sptr++ = *cptr++;
      }
      if (*cptr) cptr++;
   }
   else
   {
      while (*cptr && *cptr != ',' && *cptr != ')' && !ISLWS(*cptr) &&
             sptr < zptr)
      {
         if (*cptr == '\\') cptr++;
         if (*cptr) *sptr++ = *cptr++;
      }
   }
   if (sptr >= zptr) return (SS$_RESULTOVF); 
   *sptr = '\0';
   while (*cptr && *cptr != ',' && *cptr != ')') cptr++;
   while (*cptr == ',' || *cptr == ')') cptr++;

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchDataFormatted ("!&Z !&Z\n", NamePtr, ValuePtr);

   /* skip trailing white-space */
   while (ISLWS(*cptr)) cptr++;
   *StringPtrPtr = cptr;
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Parses name=VALUE pairs from a series of back-to-back, null-terminated strings,
terminated by an empty string.  The name will be converted to upper case and
contain only alpha-numerics and underscores (i.e. suitable for DCL symbol
names).

Parameter 'StringPtrPtr' must be the address of a pointer to char, and must be
initialized to the startup of the source string prior to the first call. 
Parameter 'NameAlphaNum' controls whether the name should only consist of
alpha-numeric-underscore characters (suitable for DCL symbol name for example).
Returns an indicative VMS status code.
*/ 

int StringParseNullNameValue
( 
char **StringPtrPtr,
BOOL NameAlphaNum,
char *NamePtr,
int NameSize,
char *ValuePtr,
int ValueSize
)
{
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "StringParseNullNameValue() !&Z",
                 StringPtrPtr ? *StringPtrPtr : NULL);

   if (!StringPtrPtr || !NamePtr || !ValuePtr) return (SS$_BADPARAM);
   cptr = *StringPtrPtr;
   if (!*cptr) return (SS$_ENDOFFILE);

   zptr = (sptr = NamePtr) + NameSize;
   while (*cptr && *cptr != '=' && sptr < zptr)
   {
      if (!NameAlphaNum || isalnum(*cptr) || *cptr == '_') *sptr++ = *cptr;
      cptr++;
   }
   if (sptr >= zptr) return (SS$_RESULTOVF); 
   *sptr = '\0';
   if (!NamePtr[0]) return (SS$_BADPARAM);
   if (*cptr) cptr++;
   zptr = (sptr = ValuePtr) + ValueSize;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr) return (SS$_RESULTOVF); 
   *sptr = '\0';
   cptr++;

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchDataFormatted ("!&Z !&Z\n", NamePtr, ValuePtr);

   *StringPtrPtr = cptr;
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Maintains a string containing newline-separated text items.
*/

StringListAdd
(
char *cptr,
char **ListPtrPtr,
int *ListLengthPtr
)
{
   int  NewLength,
        ListLength;
   char  *sptr,
         *ListPtr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "StringListAdd() !&Z !UL !&Z",
                 cptr, *ListLengthPtr, *ListPtrPtr);

   ListPtr = *ListPtrPtr;
   ListLength = *ListLengthPtr;
   while (*cptr && ISLWS(*cptr)) cptr++;
   for (sptr = cptr; *sptr; sptr++);
   NewLength = ListLength + (sptr - cptr) + 2;
   ListPtr = VmRealloc (ListPtr, NewLength, FI_LI);
   sptr = ListPtr + ListLength;
   if (ListLength) *sptr++ = STRING_LIST_CHAR;
   while (*cptr) *sptr++ = *cptr++;
   *sptr = '\0';
   *ListPtrPtr = ListPtr;
   *ListLengthPtr = sptr - ListPtr;
   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchDataFormatted ("!UL !&Z", *ListLengthPtr, *ListPtrPtr);
}

/*****************************************************************************/
/*
With the VmGetHeap()ed string, replace all occurances of |this| with |that|.
Return zero if not change, or the length of the new string.
*/

#pragma message disable (CXXKEYWORD)

int StringReplace
(
REQUEST_STRUCT *rqptr,
char **string,
char *this,
char *that
)
{
   int  count, len1, len2, size;
   char  *aptr, *bptr, *cptr, *sptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "StringReplace() !&Z !&Z", this, that);

   /* if does not contain |this| then just return */
   if (!strcasestr (*string, this)) return (0);

   len1 = strlen(this);
   len2 = strlen(that);

   if (len2 > len1)
   {
      /* need a larger buffer */
      cptr = *string;
      count = 1;
      while (cptr)
      {
         if (!(cptr = strcasestr (cptr, this))) break;
         count++;
         sptr = (cptr += len1);
      }
      while (*sptr) sptr++;
      size = sptr - *string;
      size += (len2 - len1) * count;
      bptr = VmGetHeap (rqptr, size); 
   }
   else
      /* enough space in the current buffer */
      bptr = *string;

   sptr = bptr;
   aptr = *string;
   for (;;)
   {
      if (!(cptr = strcasestr (aptr, this)))
      {
         /* copy remainder after last |this| */
         while (*aptr) *sptr++ = *aptr++;
         break;
      }
      /* copy intermediate up until start of |this| */
      while (aptr < cptr) *sptr++ = *aptr++;
      /* step over existing |this| */
      aptr += len1;
      /* copy in replacement |that| */
      for (cptr = that; *cptr; *sptr++ = *cptr++);
   }
   *sptr = '\0';

   /* dispose of the old buffer if possible */
   if (*string != bptr)
   {
      VmFreeFromHeap (rqptr, *string, FI_LI);
      *string = bptr;
   }

   return (sptr - bptr);
} 

#pragma message enable (CXXKEYWORD)

/****************************************************************************/
/*
Catenates null-terminated string |sptr| to |dptr|.  Copies no more than
|size|-1 then terminates with a null character, effectively truncating the
string.  Returns length of *source* (not the copied) string.  Report buffer
under and over flows.                                        
*/

int strzcat
(
char *dptr,
char *sptr,
int size
)
{
   char  *cptr, *zptr;

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

   zptr = dptr + size;
   if (zptr <= dptr)
   {
      HttpdStackTrace ("strzcat()", FI_LI);
      return (-1);
   }
   while (*dptr && dptr < zptr) dptr++;
   for (cptr = sptr; *cptr && dptr < zptr; *dptr++ = *cptr++);
   if (dptr < zptr)
   {
      *dptr = '\0';
      return (cptr - sptr);
   }
   zptr[-1] = '\0';
   HttpdStackTrace ("strzcat()", FI_LI);
   while (*cptr) cptr++;
   return (cptr - sptr);
}

/****************************************************************************/
/*
Copies null-terminated string |sptr| to |dptr|.  Copies no more than |size|-1
then terminates with a null character, effectively truncating the string. 
Returns length of *source* (not the copied) string.  Report buffer under and
over flows.
*/

int strzcpy
(
char *dptr,
char *sptr,
int size
)
{
   char  *cptr, *zptr;

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

   zptr = dptr + size;
   if (zptr <= dptr)
   {
      HttpdStackTrace ("strzcpy()", FI_LI);
      return (-1);
   }
   for (cptr = sptr; *cptr && dptr < zptr; *dptr++ = *cptr++);
   if (dptr < zptr)
   {
      *dptr = '\0';
      return (cptr - sptr);
   }
   zptr[-1] = '\0';
   HttpdStackTrace ("strzcpy()", FI_LI);
   while (*cptr) cptr++;
   return (cptr - sptr);
}

/****************************************************************************/
/*
Same as strzcpy() without the HttpdStackTrace().
*/

int strz2cpy
(
char *dptr,
char *sptr,
int size
)
{
   char  *cptr, *zptr;

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

   zptr = dptr + size;
   if (zptr <= dptr) return (-1);
   for (cptr = sptr; *cptr && dptr < zptr; *dptr++ = *cptr++);
   if (dptr < zptr)
   {
      *dptr = '\0';
      return (cptr - sptr);
   }
   zptr[-1] = '\0';
   while (*cptr) cptr++;
   return (cptr - sptr);
}

/****************************************************************************/
/*
Alternate version.
*/

#if WATCH_MOD

int strz1cpy
(
char *dptr,
char *sptr,
int size
)
{
   /*********/
   /* begin */
   /*********/

   if (dptr + size <= 0)
   {
      ErrorNoticed (NULL, SS$_BUGCHECK, NULL, FI_LI);
      return (-1);
   }
   strncpy (dptr, sptr, size);
   dptr[size-1] = '\0';
   /* length of *source* string */
   for (dptr = sptr; *sptr; *sptr++);
   return (sptr - dptr);
}

/****************************************************************************/
/*
Test and time strzcpy() along with strzcat().
*/

void strzcpy_test (int len)

{
#undef MAX_CNT
#define MAX_CNT 10000000

   int  cnt, idx;
   char  str1 [1024],
         str2 [1024];

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

   if (len > sizeof(str2)) len = sizeof(str2);
   for (idx = 0; idx < sizeof(str1); idx++) str1[idx] = (uchar)rand();

   fprintf (stdout, "\n");

   fprintf (stdout, "-1 %d\n", strzcpy (str1, "123456789", -1));
   fprintf (stdout, "0 %d\n", strzcpy (str1, "123456789", 0));
   fprintf (stdout, "7 %d\n", strzcpy (str1, "123456789", 7));
   fprintf (stdout, "8 %d\n", strzcpy (str1, "123456789", 8));
   fprintf (stdout, "9 %d\n",strzcpy (str1, "123456789", 9));
   fprintf (stdout, "10 %d\n", strzcpy (str1, "123456789", 10));
   fprintf (stdout, "11 %d\n", strzcpy (str1, "123456789", 11));
   fprintf (stdout, "12 %d\n", strzcpy (str1, "123456789", 12));
   fprintf (stdout, "13 %d\n", strzcpy (str1, "123456789", 13));
   fprintf (stdout, "+a %d\n", strzcat (str1, "a", 13));
   strzcpy (str1, "123456789", 13);
   fprintf (stdout, "+ab %d\n", strzcat (str1, "ab", 13));
   strzcpy (str1, "123456789", 13);
   fprintf (stdout, "+abc %d\n", strzcat (str1, "abc", 13));
   strzcpy (str1, "123456789", 13);
   fprintf (stdout, "+abcd %d\n", strzcat (str1, "abcd", 13));
   strzcpy (str1, "123456789", 13);
   fprintf (stdout, "+abcde %d\n", strzcat (str1, "abcde", 13));
   strzcpy (str1, "123456789", 13);
   fprintf (stdout, "+abcdef %d\n", strzcat (str1, "abcdef", 13));

   lib$init_timer (0);
   for (cnt = 0; cnt < MAX_CNT; cnt++)
      strzcpy (str1, str2, len);
   fprintf (stdout, "strzcpy() %d %d\n", len, cnt);
   lib$show_timer (0, 0, 0, 0);

   lib$init_timer (0);
   for (cnt = 0; cnt < MAX_CNT; cnt++)
      strz1cpy (str1, str2, len);
   fprintf (stdout, "strz1cpy() %d %d\n", len, cnt);
   lib$show_timer (0, 0, 0, 0);

   /* for comparison purposes only - CAREFUL <= sizeof(str2) */
   lib$init_timer (0);
   for (cnt = 0; cnt < MAX_CNT; cnt++)
      strcpy (str1, str2);
   fprintf (stdout, "strcpy() %d %d\n", len, cnt);
   lib$show_timer (0, 0, 0, 0);

   fprintf (stdout, "\n");
}

#endif

/****************************************************************************/
/*
Is |sptr2| contained in |sptr1| - case insensitive.
*/ 
 
char* strcasestr
(
char *sptr1,
char *sptr2
)
{
   char  *cptr1, *cptr2;

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

   if (!sptr1 || !sptr2) return (NULL);
   while (*sptr1)
   {
      cptr1 = sptr1;
      cptr2 = sptr2;
      while (to_lower(*cptr1) == to_lower(*cptr2))
      {
         cptr1++;
         cptr2++;
      }
      if (!*cptr2) return (sptr1);
      sptr1++;
   }
   return (NULL);
}
 
/****************************************************************************/
/*
Does a case-insensitive, character-by-character string compare and returns 
true if two strings are the same, or false if not.
See comment in strsame_test() below.
*/ 
 
BOOL strsame
(
char *sptr1,
char *sptr2,
int  count
)
{
   /*********/
   /* begin */
   /*********/

   if (count > 0)
      return (strncasecmp (sptr1, sptr2, count) == 0);
   else
      return (strcasecmp (sptr1, sptr2) == 0);
}
 
/****************************************************************************/

#if WATCH_MOD

/****************************************************************************/
/*
*/ 
 
BOOL str1same
(
char *sptr1,
char *sptr2,
int  count
)
{
   /*********/
   /* begin */
   /*********/

   while (*sptr1 && *sptr2)
   {
      if (TOLO(*sptr1++) != TOLO(*sptr2++)) return (false);
      if (count)
         if (!--count) return (true);
   }
   if (*sptr1 || *sptr2)
      return (false);
   else
      return (true);
}
 
/****************************************************************************/
/*
*/ 
 
BOOL str2same
(
char *sptr1,
char *sptr2,
int  count
)
{
   /*********/
   /* begin */
   /*********/

   while (*sptr1 && *sptr2)
   {
      if (to_lower(*sptr1++) != to_lower(*sptr2++)) return (false);
      if (count)
         if (!--count) return (true);
   }
   if (*sptr1 || *sptr2)
      return (false);
   else
      return (true);
}
 
/****************************************************************************/
/*
*/ 
 
BOOL str3same
(
char *sptr1,
char *sptr2,
int  count
)
{
   /*********/
   /* begin */
   /*********/

   while (*sptr1 && *sptr2)
   {
      if (tolower(*sptr1++) != tolower(*sptr2++)) return (false);
      if (count)
         if (!--count) return (true);
   }
   if (*sptr1 || *sptr2)
      return (false);
   else
      return (true);
}
 
/****************************************************************************/
/*
Proves conclusively that the C-RTL implementation of case-insensitive string
comparison is twice as efficient as the home-grown version used for so many
years (IIRC; pre-V7.0 had no strcasecmp()).  Also, to_lower()/to_upper()
implementation is marginally more efficient than TOLO()/TOUP() macros, and much
more efficient than C-RTL tolower()/toupper().
*/ 
 
void strsame_test (char *str1, char *str2, int len)

{
#undef MAX_CNT
#define MAX_CNT 10000000

   int  cnt, total;

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

   fprintf (stdout, "\n");

   lib$init_timer (0);
   for (cnt = total = 0; cnt < MAX_CNT; cnt++)
      if (str1same (str1, str2, len)) total++;
   fprintf (stdout, "str1same() %d %d |%s|%s|\n", cnt, total, str1, str2);
   lib$show_timer (0, 0, 0, 0);

   lib$init_timer (0);
   for (cnt = total = 0; cnt < MAX_CNT; cnt++)
      if (str2same (str1, str2, len)) total++;
   fprintf (stdout, "str2same() %d %d |%s|%s|\n", cnt, total, str1, str2);
   lib$show_timer (0, 0, 0, 0);

   lib$init_timer (0);
   for (cnt = total = 0; cnt < MAX_CNT; cnt++)
      if (str3same (str1, str2, len)) total++;
   fprintf (stdout, "str3same() %d %d |%s|%s|\n", cnt, total, str1, str2);
   lib$show_timer (0, 0, 0, 0);

   lib$init_timer (0);
   for (cnt = total = 0; cnt < MAX_CNT; cnt++)
      if (strsame (str1, str2, len)) total++;
   fprintf (stdout, "strsame() %d %d |%s|%s|\n", cnt, total, str1, str2);
   lib$show_timer (0, 0, 0, 0);

   fprintf (stdout, "\n");
}
 
/****************************************************************************/

#endif WATCH_MOD

