/*****************************************************************************/
/*
                                 IsMap.c

Image-mapping module, integrated into the HTTPd (at no great cost), removing 
the overhead of image mapping via a script.

This module is designed to be a single task within a request.  Multiple IsMap
tasks within the thread are not supported, and the IsMap module does not call
any other tasks.  Essentially the IsMap functionality provides all the
response required for an image mapping request.

It relies on the function RequestEnd() to do the actual redirection, either
locally, or to generate a response providing an absolute path to the client. 

All of the "smarts" for this application have been plagiarized from the NCSA 
imagemap.c application (v1.8).  The routines directly lifted from that program 
are grouped together at the end of this code module.  Due acknowlegement to 
the original authors and maintainers.  Any copyright over sections of that 
code is also acknowleged: 

  ** mapper 1.2
  ** 7/26/93 Kevin Hughes, kevinh@pulua.hcc.hawaii.edu
  ** "macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com

Up until this module was put together WASD was reliant on a pre-compiled 
version of the CERN htimage.exe program used as a script.  Having this gives a 
certain independence, functionality integrated with the HTTP server, 
documented code (have a look at imagemap.c!), and better error reporting. 

This application allows the image configuration file to be specified in either 
CERN and NCSA format (or a mix of the two for that matter).  It is slightly 
more efficient to use the NCSA format, and this is suggested anyway, but the 
CERN format is provided for backward compatibility.  The region type keywords 
must supply at least 3 characters (although four is generally peferable).  
Comments can be prefixed by '#' or '!' characters, and blank lines are 
ignored.  Lines may be continued by making a '\' character the last on the
line.  The maximum line length is 255 characters.

As an extension to both of the NCSA and CERN mapping applications, this HTTPd 
can accept and process server-side relative paths.   Hence it not only 
processes full URL's like "http://host/area/document.html", local absolute 
URL's like "/area/document.html", but also local relative URLs such as 
"../area/document.html", etc.  This provides for greater ease of mobility for 
mapped documents. 


NCSA FORMAT
-----------
default url
circle url x,y x,y              # centre-point then edge-point
point url x,y
polygon url x,y x,y [x,y ...]
rectangle url x,y x,y


CERN FORMAT
-----------
default url
circle (x,y) r url              # centre-point then radius
point (x,y) url
polygon (x,y) (x,y) [(x,y) ...] url
rectangle (x,y) (x,y) url


VERSION HISTORY
---------------
04-AUG-2001  MGD  support module WATCHing
30-DEC-2000  MGD  rework for FILE.C getting file contents in-memory
01-JAN-2000  MGD  no significant modifications for ODS-5 (no NAM block)
19-AUG-1998  MGD  allow for continued lines
17-AUG-1997  MGD  message database,
                  SYSUAF-authenticated users security-profile
27-FEB-1997  MGD  delete on close for "temporary" files
01-FEB-1997  MGD  HTTPd version 4
01-DEC-1995  MGD  new for HTTPd version 3
*/
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

/* standard C header files */
#include <stdio.h>
#include <ctype.h>
#include <math.h>

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

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

#define WASD_MODULE "ISMAP"

#define ISMAP_PATH_SIZE_MAX 512

/* required by the NCSA mapping functions, used by this module */
#define X 0
#define Y 1

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

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

extern char  SoftwareID[];
extern ACCOUNTING_STRUCT  *AccountingPtr;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Using the configuration file (contents supplied by a preceding call to
FileBegin()) interpret the mappings.
*/

IsMapBegin (REQUEST_STRUCT *rqptr)

{
   int  Length;
   char  *cptr, *sptr, *zptr;
   FILE_CONTENT  *fcptr;
   ISMAP_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "IsMapBegin()");

   if (!rqptr->AccountingDone++)
      InstanceGblSecIncrLong (&AccountingPtr->DoIsMapCount);

   /* set up the task structure (only ever one per request!) */
   rqptr->IsMapTaskPtr = tkptr =
      (ISMAP_TASK*)VmGetHeap (rqptr, sizeof(ISMAP_TASK));

   /* take control of the file contents structure */
   tkptr->FileContentPtr = fcptr = rqptr->FileContentPtr;
   rqptr->FileContentPtr = NULL;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE))
      WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "ISMAP !AZ !UL bytes",
                 fcptr->FileName, fcptr->ContentLength);

   /* get the start of the file contents */
   tkptr->LinePtr = tkptr->ParsePtr = fcptr->ContentPtr;

   /* get the coordinate of the mouse-click */
   cptr = rqptr->rqHeader.QueryStringPtr;
   while (ISLWS(*cptr)) cptr++;
   if (Debug) fprintf (stdout, "cptr |%s|\n", cptr);
   if (isdigit(*cptr))
   {
      tkptr->IsMapClickCoord[X] = (double)atoi(cptr);
      while (*cptr && isdigit(*cptr)) cptr++;
      while (ISLWS(*cptr) || *cptr == ',') cptr++;
      if (isdigit(*cptr))
         tkptr->IsMapClickCoord[Y] = (double)atoi(cptr);
      else
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_ISMAP_COORDINATE), FI_LI);
         IsMapEnd (rqptr);
         return;
      }
   }
   else
   {
      rqptr->rqResponse.HttpStatus = 400;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_ISMAP_COORDINATE), FI_LI);
      IsMapEnd (rqptr);
      return;
   }

   /* allocate heap memory to accomodate the largest allowable path */
   rqptr->rqResponse.LocationPtr = VmGetHeap (rqptr, ISMAP_PATH_SIZE_MAX+1);

   tkptr->IsMapCharNumber = tkptr->IsMapLineNumber = 0;
   tkptr->IsMapClosestPoint = HUGE_VAL;

   while (*(tkptr->LinePtr = tkptr->ParsePtr))
   {
      for (;;)
      {
         for (cptr = tkptr->ParsePtr; *cptr && *cptr != '\n'; cptr++);
         if (cptr > tkptr->ParsePtr && cptr[-1] == '\\')
         {
            /* continuation character, move line down two characters */
            sptr = (zptr = cptr) - 2;
            while (sptr >= tkptr->LinePtr) *zptr-- = *sptr--;
            tkptr->LinePtr += 2;
            continue;
         }
         if (*cptr) *cptr++ = '\0';
         tkptr->ParsePtr = cptr;
         break;
      }

      IsMapParseLine (rqptr);

      /* line number zeroed indicates successful mapping */
      if (!tkptr->IsMapLineNumber || ERROR_REPORTED (rqptr)) break;
   }

   IsMapEnd (rqptr);
}

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

IsMapEnd (REQUEST_STRUCT *rqptr)

{
   ISMAP_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "IsMapEnd() !&Z",
                 rqptr->rqResponse.LocationPtr);

   tkptr = rqptr->IsMapTaskPtr;

   /* if an error was generated then forget any redirection */
   if (ERROR_REPORTED (rqptr))
      rqptr->rqResponse.LocationPtr = NULL;
   else
   if (!rqptr->rqResponse.LocationPtr[0])
      IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_DEFAULT), FI_LI);

   SysDclAst (tkptr->FileContentPtr->NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Parse the configuration file line for mapping directives in either the NCSA or
CERN format.  If a mapping successful reset the line number storage to zero to
indicate this.
*/ 

IsMapParseLine (REQUEST_STRUCT *rqptr)

{
   BOOL  CernCompliant;
   int  ccnt,
        PathSize;
   double  Distance;
   double  CoordArray [MAXVERTS][2];
   char  *cptr, *lptr, *sptr, *zptr;
   char  Path [ISMAP_PATH_SIZE_MAX+1],
         RegionKeyword [256];
   ISMAP_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "IsMapParseLine() !&Z",
                 rqptr->IsMapTaskPtr->LinePtr);

   tkptr = rqptr->IsMapTaskPtr;

   tkptr->IsMapLineNumber++;
   lptr = tkptr->LinePtr;
   CernCompliant = false;
   PathSize = 0;

   while (ISLWS(*lptr)) lptr++;
   if (!*lptr || *lptr == '#' || *lptr == '!') return;

   /**********************/
   /* get region keyword */
   /**********************/

   zptr = (sptr = RegionKeyword) + sizeof(RegionKeyword);
   while (*lptr && !ISLWS(*lptr) && sptr < zptr)
      *sptr++ = tolower(*lptr++);
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      return;
   }
   *sptr = '\0';
   if (Debug) fprintf (stdout, "RegionKeyword |%s|\n", RegionKeyword);

   if (!(!memcmp (RegionKeyword, "cir", 3) ||
         !memcmp (RegionKeyword, "poi", 3) ||
         !memcmp (RegionKeyword, "pol", 3) ||
         !memcmp (RegionKeyword, "rec", 3) ||
         !memcmp (RegionKeyword, "def", 3)))
   {
      IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_REGION), FI_LI);
      return;
   }

   while (ISLWS(*lptr)) lptr++;
   if (!*lptr || *lptr == '#' || *lptr == '!')
   {
      tkptr->IsMapCharNumber = lptr - tkptr->LinePtr + 1;
      IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_INCOMPLETE), FI_LI);
      return;
   }

   /************/
   /* get path */
   /************/

   if (isalpha(*lptr) || *lptr == '/' || *lptr == '.')
   {
      /******************/
      /* NCSA compliant */
      /******************/

      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
         WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "NCSA");
      zptr = (sptr = Path) + sizeof(Path);
      while (*lptr && !ISLWS(*lptr) && sptr < zptr) *sptr++ = *lptr++;
      if (sptr >= zptr)
      {
         ErrorGeneralOverflow (rqptr, FI_LI);
         return;
      }
      *sptr++ = '\0';
      PathSize = sptr - Path;
      if (Debug) fprintf (stdout, "Path |%s|\n", Path);
   }
   else
   if (isdigit(*lptr) || *lptr == '(')
   {
      /******************/
      /* CERN compliant */
      /******************/

      /*
          The CERN configuration file has the path following the
          coordinates.  Skip over any coordinates here looking
          for the path, terminate after them, get the path, resume.
          Turn any coordinate-grouping parentheses into spaces.
      */
      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
         WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "CERN");
      CernCompliant = true;

      /* skip over everything looking like coordinates */
      cptr = lptr;
      while (*cptr && (ISLWS(*cptr) || isdigit(*cptr) ||
             *cptr == '(' || *cptr == ',' || *cptr == ')'))
      {
         if (*cptr == '(' || *cptr == ')')
            *cptr++ = ' ';
         else
            cptr++;
      }

      if (cptr[0] && ISLWS(cptr[-1]))
         cptr[-1] = '\0';
      else
      {
         tkptr->IsMapCharNumber = cptr - tkptr->LinePtr + 1;
         IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_CONFUSED), FI_LI);
         return;
      }

      zptr = (sptr = Path) + sizeof(Path);
      while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++;
      *sptr++ = '\0';
      PathSize = sptr - Path;
      if (Debug) fprintf (stdout, "Path |%s|\n", Path);
   }
   else
   {
      /***********************/
      /* specification error */
      /***********************/

      tkptr->IsMapCharNumber = lptr - tkptr->LinePtr + 1;
      IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_CONFUSED), FI_LI);
      return;
   }

   /*****************/
   /* default path? */
   /*****************/

   if (RegionKeyword[0] == 'd')
   {
      /* know it will fit, path size is the same and already checked! */
      memcpy (rqptr->rqResponse.LocationPtr, Path, PathSize);

      /* not interested in any more of the line */
      return;
   }

   /*************************/
   /* process coordinate(s) */
   /*************************/

   ccnt = 0;
   while (*lptr)
   {
      if (Debug) fprintf (stdout, "lptr |%s|\n", lptr);
      while (ISLWS(*lptr)) lptr++;
      if (!*lptr || *lptr == '#' || *lptr == '!') break;

      if (ccnt >= MAXVERTS-1)
      {
         IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_LIMIT), FI_LI);
         return;
      }

      if (!isdigit(*lptr))
      {
         /**************/
         /* should be! */
         /**************/

         tkptr->IsMapCharNumber = lptr - tkptr->LinePtr + 1;
         IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_CONFUSED), FI_LI);
         return;
      }

      /********************/
      /* get X coordinate */
      /********************/

      if (Debug) fprintf (stdout, "lptr |%s|\n", lptr);
      CoordArray[ccnt][X] = (double)atoi(lptr);
      while (*lptr && isdigit(*lptr)) lptr++;

      /*************************************/
      /* find comma-separated Y coordinate */
      /*************************************/

      while (ISLWS(*lptr) || *lptr == ',') lptr++;
      if (Debug) fprintf (stdout, "lptr |%s|\n", lptr);

      if (isdigit(*lptr))
      {
         /********************/
         /* get Y coordinate */
         /********************/

         CoordArray[ccnt][Y] = (double)atoi(lptr);
         while (*lptr && isdigit(*lptr)) lptr++;
      }
      else
      {
         /*******************/
         /* no Y coordinate */
         /*******************/

         if (CernCompliant && RegionKeyword[0] == 'c' && ccnt)
         {
            /*
               CERN image mapping "circle" is "(X,Y) radius".
               Fudge it by creating an "X,Y" out of the radius.
            */
            CoordArray[ccnt][Y] = CoordArray[ccnt-1][Y] + CoordArray[ccnt][X];
            CoordArray[ccnt][X] = CoordArray[ccnt-1][X];
         }
         else
         {
            tkptr->IsMapCharNumber =
               lptr - tkptr->LinePtr + 1;
            IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_COORDINATE), FI_LI);
            return;
         }
      }
      if (Debug)
         fprintf (stdout, "CoordArray[%d] |%f|%f|\n",
                  ccnt, CoordArray[ccnt][X], CoordArray[ccnt][Y]);

      ccnt++;
   }

   if (!ccnt)
   {
      /* no coordinates have been supplied */
      tkptr->IsMapCharNumber = lptr - tkptr->LinePtr + 1;
      IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_INCOMPLETE), FI_LI);
      return;
   }

   /********************/
   /* lets try it out! */
   /********************/

   CoordArray[ccnt][X] = -1;

   if (RegionKeyword[0] == 'r')
   {
      if (ccnt != 2)
         IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_INCORRECT), FI_LI);
      else
      if (pointinrect (tkptr->IsMapClickCoord, CoordArray))
      {
         /* know it will fit, capacity is the same and already checked! */
         memcpy (rqptr->rqResponse.LocationPtr, Path, PathSize);
         /* indicate the mapping has been successful */
         tkptr->IsMapLineNumber = 0;
      }
      return;
   }

   if (RegionKeyword[0] == 'c')
   {
      if (ccnt != 2)
         IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_INCORRECT), FI_LI);
      else
      if (pointincircle (tkptr->IsMapClickCoord, CoordArray))
      {
         /* know it will fit, capacity is the same and already checked! */
         memcpy (rqptr->rqResponse.LocationPtr, Path, PathSize);
         /* indicate the mapping has been successful */
         tkptr->IsMapLineNumber = 0;
      }
      return;
   }

   if (!memcmp (RegionKeyword, "pol", 3))
   {
      if (ccnt < 3)
         IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_INCORRECT), FI_LI);
      else
      if (pointinpoly (tkptr->IsMapClickCoord, CoordArray))
      {
         /* know it will fit, capacity is the same and already checked! */
         memcpy (rqptr->rqResponse.LocationPtr, Path, PathSize);
         /* indicate the mapping has been successful */
         tkptr->IsMapLineNumber = 0;
      }
      return;
   }

   if (!memcmp (RegionKeyword, "poi", 3))
   {
      /* the essential functionality of this is also from NCSA imagemap.c */
      if (ccnt != 1)
         IsMapExplainError (rqptr, MsgFor(rqptr,MSG_ISMAP_INCORRECT), FI_LI);
      else
      {
         Distance = (tkptr->IsMapClickCoord[X] - CoordArray[0][X]) *
                    (tkptr->IsMapClickCoord[X] - CoordArray[0][X]) +
                    (tkptr->IsMapClickCoord[Y] - CoordArray[0][Y]) *
                    (tkptr->IsMapClickCoord[Y] - CoordArray[0][Y]);
         if (Distance < tkptr->IsMapClosestPoint)
         {
            tkptr->IsMapClosestPoint = Distance;
            /* know it will fit, capacity is the same and already checked! */
            memcpy (rqptr->rqResponse.LocationPtr, Path, PathSize);
         }
      }
      return;
   }
}

/*****************************************************************************/
/*
Generate a general error, with explanation about the image mapping error.
*/ 

IsMapExplainError
(
REQUEST_STRUCT *rqptr,
char *Explanation,
char *SourceFileName,
int SourceLineNumber
)
{
   static $DESCRIPTOR (ErrorMessageFaoDsc,
"!AZ.\n\
<P><I>(document requires correction)<I>\n");

   static $DESCRIPTOR (ErrorMessageLineFaoDsc,
"!AZ; at line !UL.\n\
<P><I>(document requires correction)<I>\n");

   static $DESCRIPTOR (ErrorMessageLineCharFaoDsc,
"!AZ; at line !UL, character !UL.\n\
<P><I>(document requires correction)<I>\n");

   unsigned short  Length;
   char  String [1024];
   ISMAP_TASK  *tkptr;
   $DESCRIPTOR (StringDsc, String);

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "IsMapExplainError()");

   /* get the pointer to the task structure from the thread storage */
   tkptr = rqptr->IsMapTaskPtr;

   if (!rqptr->rqResponse.HttpStatus) rqptr->rqResponse.HttpStatus = 501;

   if (tkptr->IsMapLineNumber)
      if (tkptr->IsMapCharNumber)
         sys$fao (&ErrorMessageLineCharFaoDsc, &Length, &StringDsc,
                  Explanation, tkptr->IsMapLineNumber,
                  tkptr->IsMapCharNumber);
      else
         sys$fao (&ErrorMessageLineFaoDsc, &Length, &StringDsc,
                  Explanation, tkptr->IsMapLineNumber);
   else
      sys$fao (&ErrorMessageFaoDsc, &Length, &StringDsc,
               Explanation);

   String[Length] = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", String);

   ErrorGeneral (rqptr, String, SourceFileName, SourceLineNumber);
}

/*****************************************************************************/
/*
NCSA imagemap.c routines.
*/

int pointinrect(double point[2], double coords[MAXVERTS][2])
{
        return ((point[X] >= coords[0][X] && point[X] <= coords[1][X]) &&
        (point[Y] >= coords[0][Y] && point[Y] <= coords[1][Y]));
}

int pointincircle(double point[2], double coords[MAXVERTS][2])
{
        int radius1, radius2;

        radius1 = ((coords[0][Y] - coords[1][Y]) * (coords[0][Y] -
        coords[1][Y])) + ((coords[0][X] - coords[1][X]) * (coords[0][X] -
        coords[1][X]));
        radius2 = ((coords[0][Y] - point[Y]) * (coords[0][Y] - point[Y])) +
        ((coords[0][X] - point[X]) * (coords[0][X] - point[X]));
        return (radius2 <= radius1);
}

int pointinpoly(double point[2], double pgon[MAXVERTS][2])
{
        int i, numverts, inside_flag, xflag0;
        int crossings;
        double *p, *stop;
        double tx, ty, y;

        for (i = 0; pgon[i][X] != -1 && i < MAXVERTS; i++)
                ;
        numverts = i;
        crossings = 0;

        tx = point[X];
        ty = point[Y];
        y = pgon[numverts - 1][Y];

        p = (double *) pgon + 1;
        if ((y >= ty) != (*p >= ty)) {
                if ((xflag0 = (pgon[numverts - 1][X] >= tx)) ==
                (*(double *) pgon >= tx)) {
                        if (xflag0)
                                crossings++;
                }
                else {
                        crossings += (pgon[numverts - 1][X] - (y - ty) *
                        (*(double *) pgon - pgon[numverts - 1][X]) /
                        (*p - y)) >= tx;
                }
        }

        stop = pgon[numverts];

        for (y = *p, p += 2; p < stop; y = *p, p += 2) {
                if (y >= ty) {
                        while ((p < stop) && (*p >= ty))
                                p += 2;
                        if (p >= stop)
                                break;
                        if ((xflag0 = (*(p - 3) >= tx)) == (*(p - 1) >= tx)) {
                                if (xflag0)
                                        crossings++;
                        }
                        else {
                                crossings += (*(p - 3) - (*(p - 2) - ty) *
                                (*(p - 1) - *(p - 3)) / (*p - *(p - 2))) >= tx;
                        }
                }
                else {
                        while ((p < stop) && (*p < ty))
                                p += 2;
                        if (p >= stop)
                                break;
                        if ((xflag0 = (*(p - 3) >= tx)) == (*(p - 1) >= tx)) {
                                if (xflag0)
                                        crossings++;
                        }
                        else {
                                crossings += (*(p - 3) - (*(p - 2) - ty) *
                                (*(p - 1) - *(p - 3)) / (*p - *(p - 2))) >= tx;
                        }
                }
        }
        inside_flag = crossings & 0x01;
        return (inside_flag);
}

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

