/*****************************************************************************/ /* 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_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include /* VMS related header files */ #include #include /* 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 */ /********************/ extern int ToLowerCase[], ToUpperCase[]; 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 */ if (rqptr->rqHeader.QueryStringLength) cptr = rqptr->rqHeader.QueryStringPtr; else cptr = ""; while (ISLWS(*cptr)) 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++ = TOLO(*lptr++); if (sptr >= zptr) { ErrorGeneralOverflow (rqptr, FI_LI); return; } *sptr = '\0'; if (!(MATCH3 (RegionKeyword, "cir") || MATCH3 (RegionKeyword, "poi") || MATCH3 (RegionKeyword, "pol") || MATCH3 (RegionKeyword, "rec") || MATCH3 (RegionKeyword, "def"))) { 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; } 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; } 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) { 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 */ /********************/ CoordArray[ccnt][X] = (double)atoi(lptr); while (*lptr && isdigit(*lptr)) lptr++; /*************************************/ /* find comma-separated Y coordinate */ /*************************************/ while (ISLWS(*lptr) || *lptr == ',') 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; } } 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 (MATCH3 (RegionKeyword, "pol")) { 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 (MATCH3 (RegionKeyword, "poi")) { /* 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\

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

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

(document requires correction)\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'; 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); } /*****************************************************************************/