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

String support functions for HTTPd.


VERSION HISTORY
---------------
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_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#undef __VMS_VER
#undef __CRTL_VER
#define __CRTL_VER 60000000
#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"

/******************/
/* global storage */
/******************/

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

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

extern char *FaoUrlEncodeTable[];

extern char ErrorSanityCheck[],
            Utility[];

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,
         RegexRequired,
         TwoWild,
         WatchThisMatch;
   int  pidx, retval;
   char  ch;
   char  *pptr, *sptr, *tsptr, *tpptr;
   char  ErrorString [32],
         ErrorBuffer [256];
   regex_t  preg;
   regmatch_t  pmatch [REGEX_PMATCH_MAX];

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

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

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_MATCH))
   {
      WatchThis (rqptr, FI_LI, 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;
   RegexRequired = false;
   if (Config.cfMisc.RegexEnabled && *pptr == REGEX_CHAR)
   {
      /* must be a regex pattern */
      RegexRequired = true;
      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 (*(unsigned short*)pptr == '$\0' && !*sptr)
         {
            if (WatchThisMatch)
               WatchThis (rqptr, FI_LI, 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 (RegexRequired) break;
         case '*' :
            MetaCharHit = true;
            break;
         case '^' :
         case '$' :
         case '.' :
         case '+' :
         case '?' :
         case '|' :
         case '{' :
         case '[' :
         case '(' :
         case '\\' :
            if (!RegexRequired) break;
            MetaCharHit = true;
            break;
      }
      if (MetaCharHit) break;
      if (tolower(*pptr) != tolower(*sptr) && *pptr != '%') break;
      pptr++;
      sptr++;
   }

   if (!*pptr && !*sptr)
   {
      /* matched exactly */
      if (WatchThisMatch) WatchThis (rqptr, FI_LI, WATCH_MATCH, "YES prematch");
      if (!pmatchptr) return (true);
      pmatchptr[0].rm_so = 0;
      pmatchptr[0].rm_eo = sptr - StringPtr;
      return (true);
   }
   if (*(unsigned short*)pptr == '*\0')
   {
      /* pattern ended in a trailing wildcard, therefore it matches */
      if (WatchThisMatch) WatchThis (rqptr, FI_LI, 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 (!MetaCharHit)
   {
      /* didn't match */
      if (!WatchThisMatch) return (false);
      WatchThis (rqptr, FI_LI, 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 */
         RegexRequired = false;
         break;
      case SMATCH_STRING_REGEX :  /* non-greedy match - allow regex */
      case SMATCH_GREEDY_REGEX :  /* greedy match - allow regex */
         if (pregptr && pregptr->buffer)
            RegexRequired = Config.cfMisc.RegexEnabled;
         else
         if (*pptr == REGEX_CHAR)
            RegexRequired = Config.cfMisc.RegexEnabled;
         else
            RegexRequired = false;
         break;
      case SMATCH_REGEX :         /* regular expression only */
         RegexRequired = true;
         break;
      default :
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   sptr = StringPtr;
   pptr = PatternPtr;

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

      if (!pmatchptr) pmatchptr = &pmatch;

      if (pregptr && pregptr->buffer)
         retval = 0;
      else
      {
         if (MatchType != SMATCH_REGEX) pptr++;
         retval = regcomp (pregptr = &preg, pptr, REGEX_C_FLAGS);
      }

      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 (rqptr, FI_LI, WATCH_MATCH, "NO regex");
            return (false);
         }
         else
         {
            if (!WatchThisMatch) return (true);
            WatchThis (rqptr, FI_LI, 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 (rqptr, FI_LI, WATCH_MATCH,
                    "COMPILE regex !AZ", ErrorString);
      else
      if (WATCHING(rqptr))
         WatchThis (rqptr, FI_LI, WATCH_MATCH,
                    "COMPILE regex !&Z !AZ", pptr, ErrorString);
      WriteFao (ErrorBuffer, sizeof(ErrorBuffer), NULL,
                "regex \"!AZ\" !AZ", pptr, ErrorString);
      ErrorNoticed (0, ErrorBuffer, FI_LI);
      return (false);
   }

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

   for (;;)
   {
      while (*pptr && *sptr && *pptr != '*' && *pptr != '%' &&
             tolower(*pptr) == tolower(*sptr))
      {
         pptr++;
         sptr++;
      }
      if (!*pptr && !*sptr)
      {
         if (pmatchptr)
         {
            pmatchptr[0].rm_so = 0;
            pmatchptr[0].rm_eo = sptr - StringPtr;
         }
         if (!WatchThisMatch) return (true);
         WatchThis (rqptr, FI_LI, 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 (rqptr, FI_LI, 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 (rqptr, FI_LI, WATCH_MATCH, "NO wildcard !&Z", pptr);
         return (false);
      }

      TwoWild = *(unsigned short*)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 (rqptr, FI_LI, 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 = tolower(*pptr);
            while (*sptr && tolower(*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 (rqptr, FI_LI, 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 && tolower(*pptr) == tolower(*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 (rqptr, FI_LI, 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 = tolower(*pptr);
            while (*sptr && tolower(*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 (rqptr, FI_LI, 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 && tolower(*pptr) == tolower(*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 (rqptr, FI_LI, 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 (rqptr, FI_LI, 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 (rqptr, FI_LI, 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 (rqptr, FI_LI, 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 (rqptr, FI_LI, 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 (rqptr, FI_LI, 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;

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

   retval = regcomp (pregptr, Pattern, REGEX_C_FLAGS);
   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,
REQUEST_AST NextTaskFunction
)
{
   static char  BeginPage [] =
"<FORM METHOD=POST ACTION=\"!AZ\">\n\
<P><TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<TR><TD>\n\
<TABLE CELLPADDING=3 CELLPSACING=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\
</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\
</BODY>\n\
</HTML>\n";

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

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, 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 */
         rqptr->NextTaskFunction = NextTaskFunction;
         BodyReadBegin (rqptr, &StringMatchReport, &BodyProcessReadAll);
         return;
      }
      NextTaskFunction = rqptr->NextTaskFunction;
      qptr = rqptr->rqBody.DataPtr;
   }
   else
      qptr = rqptr->rqHeader.QueryStringPtr;

   MatchType = 0;
   Pattern[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);
         SysDclAst (NextTaskFunction, 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, "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);
         SysDclAst (NextTaskFunction, rqptr);
         return;
      }
   }

   if (!MatchType) MatchType = SMATCH_STRING;

   RESPONSE_HEADER_200_HTML (rqptr);
   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, REGEX_C_FLAGS);
         else
            retval = regcomp (&preg, Pattern+1, REGEX_C_FLAGS);
         if (retval)
         {
            regerror (retval, &preg, Scratch, sizeof(Scratch));
            vecptr = FaoVector;
            *vecptr++ = String;
            *vecptr++ = Pattern;
            *vecptr++ = Scratch;
            *vecptr++ = "regex";
            *vecptr++ = MatchEnd;
            status = NetWriteFaol (rqptr, MatchFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", 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 = NetWriteFaol (rqptr, MatchFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", 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 = NetWriteFaol (rqptr, MatchFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", 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 = NetWriteFaol (rqptr, PmatchFao, &FaoVector);
               if (VMSnok (status))
                  ErrorNoticed (status, "NetWriteFaol()", FI_LI);
            }
         }
         status = NetWriteFaol (rqptr, PmatchEnd, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
      }

      vecptr = FaoVector;
      if (Result[0] && !retval)
      {
         status = StringMatchSubstitute (rqptr, String, Result, &pmatch,
                                         Scratch, sizeof(Scratch));
         if (VMSnok (status))
            WriteFao (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 = NetWriteFaol (rqptr, ResultFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", 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++ = Result;
   status = NetWriteFaol (rqptr, EndPageFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   SysDclAst (NextTaskFunction, 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 (NULL, FI_LI, 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 (NULL, FI_LI, 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 (NULL, FI_LI, WATCH_MOD__OTHER,
                  "StringUrlDecode() !&Z", String);

   cptr = sptr = String;
   while (*cptr)
   {
      switch (*cptr)
      {
#ifdef BLAH
         case '=' :
         case '&' :
            /* URL-forbidden characters */
            return (-1);
#endif

         case '+' :
            *sptr++ = ' ';
            cptr++;
            break;

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

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
       WatchThis (NULL, FI_LI, 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 (NULL, FI_LI, 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 then 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 (NULL, FI_LI, 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 (*(unsigned short*)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.
'StartPtrPtr' (if supplied) is set to the start of the value string,
'EndPtrPtr' (if supplied) is set to the end of the value string. 
'StringPtrPtr' is set to the first character following the value.  Returns an
indicative VMS status code.
*/ 

int StringSpanValue
(
char **StringPtrPtr,
char **StartPtrPtr,
char **EndPtrPtr
)
{
   char  *cptr, *sptr;

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

   if (!StringPtrPtr || !*StringPtrPtr) return (SS$_BADPARAM);
   cptr = sptr = *StringPtrPtr;
   if (*sptr == '\"' || *sptr == '\'') sptr++;
   if (StartPtrPtr) *StartPtrPtr = sptr;

   while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr))
   {
      if (*cptr == '\"')
      {
         cptr++;
         while (*cptr && *cptr != '\"')
         {
            if (*cptr == '\\') cptr++;
            if (*cptr) cptr++;
         }
         if (EndPtrPtr) *EndPtrPtr = cptr;
         if (*cptr) cptr++;
      }
      else
      if (*cptr == '\'')
      {
         cptr++;
         while (*cptr && *cptr != '\'')
         {
            if (*cptr == '\\') cptr++;
            if (*cptr) cptr++;
         }
         if (EndPtrPtr) *EndPtrPtr = cptr;
         if (*cptr) cptr++;
      }
      else
      {
         while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr))
         {
            if (*cptr == ';' && (ISLWS(*(cptr+1)) || EOL(*(cptr+1)))) break;
            if (*cptr == '\"' || *cptr == '\'') break;
            if (*cptr == '\\') cptr++;
            if (*cptr) cptr++;
         }
         if (EndPtrPtr) *EndPtrPtr = cptr;
         if (*cptr == ';' && (ISLWS(*(cptr+1)) || EOL(*(cptr+1)))) break;
      }
   }

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

/*****************************************************************************/
/*
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, *tptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (NULL, FI_LI, 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 (*(unsigned short*)cptr == '(\"' ||
          *(unsigned short*)cptr == '(\'') cptr++;
      if (*cptr == '\"')
      {
         cptr++;
         while (*cptr && *cptr != '\"' && sptr < zptr)
         {
            if (*cptr == '\\' && *(cptr+1)) 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+1)) cptr++;
            if (*cptr) *sptr++ = *cptr++;
         }
         if (*cptr) cptr++;
         if (*cptr == ',' || *cptr == ')')
         {
            while (*cptr == ',' || *cptr == ')') cptr++;
            break;
         }
      }
      else
      {
         while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr)
         {
            if (*cptr == ';' && (ISLWS(*(cptr+1)) || EOL(*(cptr+1)))) break;
            if (*cptr == '\\' && *(cptr+1)) cptr++;
            if (*cptr) *sptr++ = *cptr++;
         }
         if (*cptr == ';' && (ISLWS(*(cptr+1)) || EOL(*(cptr+1)))) break;
      }
   }
   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);
}

/*****************************************************************************/
/*
Place the result back into the null-terminated string space from whence it
came!  Effectively strips 'white-space delimited' strings of delimitting
quotes, escaped characters, etc., in situ!  Returns length of stripped string.
*/ 

int StringStripValue (char *StringPtr)

{
   char  *cptr, *sptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
                 "StringStripValue() !&Z", StringPtr);

   if (!StringPtr) return (0);
   cptr = sptr = StringPtr;

   if (*cptr == '\"')
   {
      cptr++;
      while (*cptr && *cptr != '\"')
      {
         if (*cptr == '\\')
         {
            cptr++;
            if (*cptr == 'n')
            {
               *sptr++ = '\n';
               cptr++;
            }
            else
            if (*cptr)
            {
               *sptr++ = *cptr;
               cptr++;
            }
         }
         else
            *sptr++ = *cptr++;
      }
   }
   else
   if (*cptr == '\'')
   {
      cptr++;
      while (*cptr && *cptr != '\'')
      {
         if (*cptr == '\\')
         {
            cptr++;
            if (*cptr == 'n')
            {
               *sptr++ = '\n';
               cptr++;
            }
            else
            if (*cptr)
            {
               *sptr++ = *cptr;
               cptr++;
            }
         }
         else
            *sptr++ = *cptr++;
      }
   }
   else
   {
      while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr))
      {
         if (*cptr == '\\')
         {
            cptr++;
            if (*cptr == 'n')
            {
               *sptr++ = '\n';
               cptr++;
            }
            else
            if (*cptr)
            {
               *sptr++ = *cptr;
               cptr++;
            }
         }
         else
            *sptr++ = *cptr++;
      }
   }
   *sptr = '\0';

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

   return (sptr - StringPtr);
}

/*****************************************************************************/
/*
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 (NULL, FI_LI, 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);
}

/*****************************************************************************/
/*
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 (NULL, FI_LI, 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);
}

/****************************************************************************/
/*
Copies null-terminated string 'SourcePtr' to 'DestinationPtr'.  Copies no more
than 'SizeOfDestination'-1 then terminates with a null character, effectively
truncating the string.  Returns length of source (not the copied) string.
*/

int strzcpy
(
char *DestinationPtr,
char *SourcePtr,
int SizeOfDestination
)
{
   char  *cptr, *sptr, *zptr;

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

   zptr = (sptr = DestinationPtr) + SizeOfDestination - 1;
   for (cptr = SourcePtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';
   while (*cptr++) sptr++;
   return (sptr - DestinationPtr);
}

/****************************************************************************/
/*
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.
*/ 
 
BOOL strsame
(
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);
}
 
/****************************************************************************/

