/***************************************************************************** /* Str[i]ng.c String support functions for HTTPd. Traditional U**x regular expressions: http://en.wikipedia.org/wiki/Regular_expression VERSION HISTORY --------------- 26-FEB-2021 MGD stristr() 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 #include #include #include #include /* VMS related header files */ #include #include /* application related header files */ #include "wasd.h" #define WASD_MODULE "STRNG" /********************/ /* external storage */ /********************/ #ifdef DBUG extern BOOL Debug; #else #define Debug 0 #endif extern int ToLowerCase[], ToUpperCase[]; 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 (TOLO(*pptr) != TOLO(*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 != '%' && TOLO(*pptr) == TOLO(*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 = TOLO(*pptr); while (*sptr && TOLO(*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 && TOLO(*pptr) == TOLO(*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 = TOLO(*pptr); while (*sptr && TOLO(*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 && TOLO(*pptr) == TOLO(*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 "*'" sequence, where 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 [] = "
\n\

\n\ \n\
\n\ \n"; static char MatchFao [] = "\n\ \n\ \n"; static char PmatchFao [] = "!UL. !7<{!UL,!UL}!> |!&;AZ|\n"; static char PmatchEnd [] = "\n"; static char ResultFao [] = "\n\ \n\ \n"; static char EndPageFao [] = "\ \n\ \ \n\ \n\ \n\ \n\
String:!&;AZ
Pattern:!&;AZ
Match:!&;AZ  (!AZ)!AZ"; static char MatchEnd [] = "
Result:!&@
!&@
String:
Pattern:
\ match \ greedy \ regex \  !8XL (!AZ)\
Result:\
\   \ \
\n\
\n\

\n\

[Server Admin]\n\ \n\ \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++ = "

";
            }
            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++ = "
";
         }
         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++ = "!&;AZ";
         *vecptr++ = Result;
         *vecptr++ = "|!&;AZ|";
         *vecptr++ = Scratch;
      }
      else
      {
         *vecptr++ = "!AZ";
         *vecptr++ = "(none)";
         *vecptr++ = "!AZ";
         *vecptr++ = "(none)";
      }
      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 (TOUP(*cptr) >= 'A' && TOUP(*cptr) <= 'F')
               *sptr = ((unsigned char)(TOUP(*cptr) - '7')) << 4;
            else
               return (-1);
            if (*cptr) cptr++;
            if (*cptr >= '0' && *cptr <= '9')
               *sptr |= *cptr - '0';
            else
            if (TOUP(*cptr) >= 'A' && TOUP(*cptr) <= 'F')
               *sptr |= TOUP(*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 (!stristr (*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 = stristr (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 = stristr (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)

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

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

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

   if (!sptr1 || !sptr2) return (NULL);
   while (*sptr1)
   {
      cptr1 = sptr1;
      cptr2 = sptr2;
      while (TOLO(*cptr1) == TOLO(*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.  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 (TOLO(*sptr1++) != TOLO(*sptr2++)) return (false);
      if (count)
         if (!--count) return (true);
   }
   if (*sptr1 || *sptr2)
      return (false);
   else
      return (true);
}
 
/****************************************************************************/