/*****************************************************************************/
/*
                                  MapCon.c

Mapping conditional processing.

See MAPURL.C for description of "Mapping Conditionals".


VERSION HISTORY
---------------
07-OCT-2004  MGD  unbundled from MAPURL.C
*/
/*****************************************************************************/

#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

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

#define WASD_MODULE "MAPCON"

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

extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Scan the conditional string evaluating the conditions!
Return true or false.
Anything it cannot understand it ignores!
*/

BOOL MapConString
(
REQUEST_STRUCT *rqptr,
MAP_RULE_META *mrptr,
REQUEST_PATHSET *rqpsptr
)
{
   BOOL  NegateThisCondition,
         NegateEntireConditional,
         Result,
         SoFarSoGood;
   int  idx, status,
        ConditionalCount,
        WatchThisOne;
   char  *cptr, *sptr, *tptr, *zptr,
         *CurrentPtr;
   char  Scratch [2048];
   DICT_ENTRY_STRUCT  *denptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_MAPURL))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                 "MapConString() !&Z", mrptr->ConditionalPtr);

   if (WATCHING (rqptr, WATCH_MAPPING))
      WatchThisOne = WATCH_MAPPING;
   else
      WatchThisOne = 0;

   CurrentPtr = NULL;
   ConditionalCount = 0;
   NegateEntireConditional = NegateThisCondition = SoFarSoGood = false;
   cptr = mrptr->ConditionalPtr;
   if (WATCH_CAT && WatchThisOne)
      WatchDataFormatted ("conditional !AZ\n", cptr);
   while (*cptr)
   {
      while (ISLWS(*cptr)) cptr++;
      if (!*cptr) break;

      if (*cptr == '[' || SAME2(cptr,'!['))
      {
         if (*cptr == '!')
         {
            NegateEntireConditional = true;
            cptr++;
         }
         else
            NegateEntireConditional = false;
         cptr++;
         ConditionalCount = 0;
         SoFarSoGood = false;
         continue;
      }
      if (*cptr == ']')
      {
         cptr++;
         if (NegateEntireConditional)
         {
            SoFarSoGood = !SoFarSoGood;
            NegateEntireConditional = false;
         }
         if (ConditionalCount && !SoFarSoGood)
         {
            cptr = "";
            break;
         }
         continue;
      }
      if (SoFarSoGood)
      {
         if (NegateEntireConditional)
         {
            SoFarSoGood = !SoFarSoGood;
            NegateEntireConditional = false;
         }
         /* at least one OK, skip to the end of the conditional */
         while (*cptr && *cptr != ']') cptr++;
         /* remember, there may be more than one per line */
         if (*cptr) continue;
         break;
      }

      CurrentPtr = cptr;
      NegateThisCondition = Result = false;
      zptr = (sptr = Scratch) + sizeof(Scratch)-1;

      if (*cptr == '!')
      {
         cptr++;
         NegateThisCondition = true;
      }

      switch (*(USHORTPTR)cptr)
      {
         case 'AC' :

            /*************/
            /* "Accept:" */
            /*************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (rqptr->rqHeader.AcceptPtr)
               Result = MapConList (rqptr, Scratch,
                                    rqptr->rqHeader.AcceptPtr);
            break;
             
         case 'AL' :

            /**********************/
            /* "Accept-Language:" */
            /**********************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = MapConList (rqptr, Scratch,
                                 rqptr->rqHeader.AcceptLangPtr);
            break;

         case 'AS' :

            /*********************/
            /* "Accept-Charset:" */
            /*********************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = MapConList (rqptr, Scratch,
                                 rqptr->rqHeader.AcceptCharsetPtr);
            break;

         case 'CA' :

            /************/
            /* callout? */
            /************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']') cptr++;
            Result = rqptr->rqCgi.CalloutInProgress;
            break;

         case 'CK' :

            /**********/
            /* cookie */
            /**********/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.CookiePtr,
                                             Scratch);
            break;

         case 'DR' :

            /*****************/
            /* document root */
            /*****************/

            /* map=root=<string> */
            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (rqpsptr->MapRootPtr)
               Result = StringMatchGreedyRegex (rqptr,
                                                rqpsptr->MapRootPtr,
                                                Scratch);
            else
               Result = false;
            break;

         case 'EX' :

            /**********************/
            /* extended file path */
            /**********************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']') cptr++;
            Result = rqptr->PathOdsExtended;
            break;

         case 'HM' :

            /****************************/
            /* client host network mask */
            /****************************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            tptr = Scratch;  /* preserve string address using ptr */
            status = TcpIpNetMask (rqptr, WATCH_MAPPING, &tptr,
                                   &rqptr->ClientPtr->IpAddress);
            /* if there's a problem with the mask then just fail it */
            if (VMSnok(status) && status != SS$_UNREACHABLE) return (false);
            Result = (status == SS$_NORMAL);
            break;

         case 'HO' :

            /****************************/
            /* client host name/address */
            /****************************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            for (sptr = Scratch; *sptr && !isalnum(*sptr); sptr++);
            if (isdigit(*sptr))
               sptr = &rqptr->ClientPtr->IpAddressString;
            else
               sptr = rqptr->ClientPtr->Lookup.HostName;
            Result = StringMatchGreedyRegex (rqptr, sptr, Scratch);
            break;

         case 'FO' :

            /*******************/
            /* proxy forwarded */
            /*******************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            /* it's unlikely we'll have any of this header field */
            if (rqptr->rqHeader.ForwardedPtr)
               Result = MapConList (rqptr, Scratch,
                                    rqptr->rqHeader.ForwardedPtr);
            break;

         case 'ME' :

            /***************/
            /* HTTP method */
            /***************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (Scratch[0] == '?')
               Result = (rqptr->rqHeader.Method == HTTP_METHOD_EXTENSION);
            else
               Result = StringMatchGreedyRegex (rqptr,
                                                rqptr->rqHeader.MethodName,
                                                Scratch);
            break;

         case 'MP' :

            /*********************/
            /* being mapped path */
            /*********************/

            /* path remaining after script/exec rule, or after a 'map' rule */
            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (rqptr->MetaConMappedPtr)
               Result = StringMatchGreedyRegex (rqptr,
                                                rqptr->MetaConMappedPtr,
                                                Scratch);
            else
               Result = false;
            break;

         case 'NO' :

            /***********/
            /* notepad */
            /***********/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (rqptr->NotePadPtr)
               Result = StringMatchGreedyRegex (rqptr,
                                                rqptr->NotePadPtr,
                                                Scratch);
            else
               Result = false;
            break;

         case 'PA' :

            /********/
            /* pass */
            /********/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = (Scratch[0] - '0' == rqptr->MetaConPass);
            break;

         case 'PI' :

            /*************/
            /* path-info */
            /*************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.PathInfoPtr,
                                             Scratch);
            break;

         case 'QS' :

            /****************/
            /* query string */
            /****************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.QueryStringPtr,
                                             Scratch);
            break;

         case 'RC' :

            /********************/
            /* redirected count */
            /********************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (Scratch[0])
               Result = (Scratch[0] - '0' == rqptr->RedirectCount);
            else
               Result = rqptr->RedirectCount;
            break;

         case 'RF' :

            /****************/
            /* refering URL */
            /****************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.RefererPtr,
                                             Scratch);
            break;

         case 'RQ' :

            /****************4*/
            /* request field */
            /*****************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && \
                   *cptr != ':' && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_REQUEST,
                                     Scratch, sptr - Scratch))
            {
               if (*sptr == ':')
               {
                  sptr++;
                  while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
                  {
                     if (*cptr == '\\') cptr++;
                     if (*cptr) *sptr++ = *cptr++;
                  }
                  *sptr = '\0';
                  Result = StringMatchGreedyRegex (rqptr, sptr, Scratch);
               }
               else
                  Result = true;
               if (Result) break;
            }

            break;

         case 'RU' :

            /***************/
            /* request URI */
            /***************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.RequestUriPtr,
                                             Scratch);
            break;

         case 'SC' :

            /****************************************/
            /* request scheme ("http:" or "https:") */
            /****************************************/

            ConditionalCount++;
            cptr += 3;
            /* also ignore any trailing colon on the scheme string */
            while (*cptr && !ISLWS(*cptr) && *cptr != ':' && *cptr != ']' &&
                   sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (*cptr == ':') cptr++;
            /* any trailing colon has been stripped */
            if (strsame (Scratch, "https", -1) &&
                rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS)
               Result = true;
            else
            if (strsame (Scratch, "http", -1) &&
                rqptr->ServicePtr->RequestScheme == SCHEME_HTTP)
               Result = true;
            else
               Result = false;

            break;

         case 'SN' :

            /***************/
            /* server name */
            /***************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->ServicePtr->ServerHostName,
                                             Scratch);
            break;

         case 'SP' :

            /***************/
            /* server port */
            /***************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                           rqptr->ServicePtr->ServerPortString,
                                             Scratch);
            break;

         case 'ST' :

            /***************/
            /* script name */
            /***************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (sptr = rqptr->MetaConScriptPtr)
            {
               if (*sptr == '+')
               {
                  /* CGIplus indicator */
                  *sptr = '/';
                  Result = StringMatchGreedyRegex (rqptr, sptr, Scratch);
                  *sptr = '+';
               }
               else
                  Result = StringMatchGreedyRegex (rqptr, sptr, Scratch);
            }
            else
               Result = false;
            break;

         case 'UA' :

            /*****************/
            /* "User-Agent:" */
            /*****************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.UserAgentPtr,
                                             Scratch);
            break;

         case 'VS' :
         case 'HH' :  /* backward compatibility */

            /******************************************/
            /* virtual service ("Host:") name/address */
            /******************************************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.HostPtr,
                                             Scratch);
            break;

         case 'XF' :

            /*******************/
            /* x-forwarded-for */
            /*******************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.XForwardedForPtr,
                                             Scratch);
            break;

         default :

            /*******************/
            /* unknown, ignore */
            /*******************/

            /* should never occur due to basic check in MapUrl_ConfigLoad()! */
            if (WATCH_CAT && WatchThisOne)
               WatchDataFormatted ("IGNORE !AZ\n", CurrentPtr);
            while (*cptr && !ISLWS(*cptr) && *cptr != ']') cptr++;
            continue;
      }

      if (NegateThisCondition)
         SoFarSoGood = SoFarSoGood || !Result;
      else
         SoFarSoGood = SoFarSoGood || Result;

      if (WATCH_CAT && WatchThisOne)
         WatchDataFormatted ("!AZ !AZ\n",
                             SoFarSoGood ? "MATCH" : "NOMATCH", CurrentPtr);
   }

   if (!ConditionalCount)
      SoFarSoGood = true;
   else
   if (NegateEntireConditional)
      SoFarSoGood = !SoFarSoGood;

   if (WATCH_CAT && WatchThisOne)
      WatchDataFormatted ("!AZ conditional\n",
                          SoFarSoGood ? "PASSED" : "FAILED");

   return (SoFarSoGood);
}

/*****************************************************************************/
/*
Compare single "??:" conditional string to each of a comma-separated list in
the form "item" or "item1, item2, item3", etc. String may contain '*' and '%'
wildcards.  We can munge the list string like this because we're operating at
AST-delivery level and cannot be preempted!
*/

BOOL MapConList
(
REQUEST_STRUCT *rqptr,
char *String,
char *List
)
{
   char  ch;
   char  *cptr, *lptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_MAPURL))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                 "MapConList() !&Z !&Z", String, List);

   if (!(lptr = List)) return (false);
   if (!String) return (false);
   while (*lptr)
   {
      /* spaces and commas are element separators, step over */
      while (ISLWS(*lptr) || *lptr == ',') lptr++;
      /* if end of list (or empty) then finished */
      if (!*lptr) return (false);
      /* find end of this element, save next character, terminate element */
      for (cptr = lptr; *lptr && !ISLWS(*lptr) && *lptr != ','; lptr++);
      ch = *lptr;
      *lptr = '\0';
      /* look for what we want */
      if (StringMatchGreedyRegex (rqptr, cptr, String))
      {
         /* restore list */
         *lptr = ch;
         return (true);
      }
      /* restore list */
      *lptr = ch;
   }
   /* ran out of list elements, so it can't be a match! */
   return (false);
}

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

