ismap.c a mapper program

hoesel@chem.rug.nl (frans van hoesel)
From: hoesel@chem.rug.nl (frans van hoesel)
Message-id: <9308241157.AA00619@Xtreme>
Subject: ismap.c a mapper program
To: www-talk@nxoc01.cern.ch (www-talk)
Date: Tue, 24 Aug 1993 13:57:42 +0100 (DST)
X-Mailer: ELM [version 2.4 PL5]
Content-Type: text
Content-Length: 10613     
Status: RO
Hi,

below I include the ismap.c program I use from the EXPO terrain map.
You can use it as you like. If time comes to include it in the httpd itself,
I would prefer to do that myself (some good ideas!!) but if you
use it at a seperate port number, it is all yours.

no garantees!!

frans van hoesel

hoesel@chem.rug.nl
(on vacattion for the next two weeks)

------------------------ ismap.c ---------------------------




/*___________________________________________________________________
 | ismap.c 
 |
 | ismap decoding program written by 
 |
 |                        Frans van Hoesel (c) 1993
 |
 | ismap works by comparing the requested filename in the URL (without leading
 | path components) with the in files the directory ISMAP_PATH.
 | The mapfile found in that directory have a very simple syntax
 | it has lines in the form of
 | <some test function> <some file to send>
 | were <some test function> is one of:
 |      rect (xmin, ymin, xmax, ymax)
 |		returns true if the mouse coordinates are within
 |		the rectangle (mind the order of the corners)
 |	circle (xcenter, ycenter, radius)
 |		returns true if the coordinates are inside the circle
 |	noismap
 |		returns true if the browser does not support the ismap
 |		feature.
 |	default
 |		always true and *MUST* be present, for ismap to work
 |		properly
 | and <some file to send> is the absolute path to the file which is
 | returned to the browser if the test returned true.
 | all test are performed in a top-down order, and only the first that
 | returns true is executed. Therefore the default must be the last
 | clause specified.
 | comments start at '#'
 | 
 | for efficienty reasons, it is best to put the test in such an order
 | that the one that is most likely to return true is near the top.
 | 
 | IMPORTANT: as this ismap decoder runs from another port number you will
 | need to convert any relative links you might have to full URLs, but only
 | for documents referenced via ismap.
 | 
 | compile this using an ansi compiler (gcc?)
 | 
 | you should set this up by editing your /etc/services file
 | It should contain a line like:
 |    ismap	8988/tcp	# mapping that works 
 | and in your /etc/inetd.conf:
 |    ismap stream tcp nowait http /usr/local/bin/ismap ismap
 | where http is the user-id which has access to the map files and the
 | documents returned by the ismap program; /usr/local/bin is the asumed
 | location of the binairy (you might consider /usr/local/etc/httpd/ismap)
 | you'll need a kill -HUP <process id of inetd>
 | and if you are running YP, you'll need to push the sevices over the network
 |
 | Some of the code in here is untested, so if you find any bugs
 | send them to hoesel@chem.rug.nl
 | In near future I'll add poly(x1,y1,x2,y2,....) that test for abritrary
 | polygons. (for now you can use multiple rect() to approximate the polygone)
 |
 | ++++++++++++++++++++++ READ THIS IF YOU ARE A DEVELOPPER +++++++++++++++++
 |
 | If only the browser all around the world would recognize the file type
 | by looking at the correct place for the extension then somefile.gif?234,345 
 | would known to return a gif image, then the ismap would be served from
 | the httpd directly (and we could add a feature for searchable index too). 
 | (this is ofcourse a hint to all the browser programmers out there )
 | Having one single and simple httpd that would serve all this is needed so
 | people will use ismap and searchable index much more. I'll be happy to
 | modify the httpd. // in fact I could already offer you a modified
 | httpd, but that would only work for html files //

 | example map file:
 | # this is an example mapfile
 | default		/usr/data/default.html
 | rect(10,20,100,200)	/usr/data/rect1.html
 | rect(20,30,100,400)  /usr/data/rect2.html
 | noismap		/usr/data/sorry.html
 
 | store this file in the path specified below
 | eg. /usr/local/etc/httpd/mapfiles/mymap.html 
 | (note that this extension is html (so browser might know that you are
 | returning html files (but that could well be gifs, au, etc)
 | And reference it in a document as:
 | <a href="http://site.dom:8988/some_path_which_is_not_used/mymap.html">
 |   <img src="http://site.dom/your_image.gif" ismap></a>

 | it is expected that you offer an alternative way to access rect1.html and
 | rect2.html from the document sorry.html, so people without ismap support
 | can still use it.

 | error message are send as html files. so if you have problems in using
 | this, change your returned type to html (instead of gif or au) so you can
 | read these messages. I guess most people will returns html files, so
 | you will not have to do anything special to get the error messages.
 
 | frans van hoesel 1993
 | hoesel@chem.rug.nl
*/

/* 
 * the only thing you need to configure:
 * set ISMAP_PATH to the directory where the mapfiles can be found
 * make sure to add an ending '/' and that the files are readable by
 * the owner of this program (most likely set in inetd.conf)
 */
#define ISMAP_PATH "/usr/local/etc/httpd/mapfiles/"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>

#define MAXLEN 256

static char text[MAXLEN + 2];
static char *chptr;
static FILE *mapfile;
static char filename[MAXLEN];
static int  linenum;

void errormsg(char *format, ...) {
	va_list args;

	va_start(args, format);
	puts("<title>Error</title>");
	puts("<h1>Error from server:</h1>");
	vprintf(format, args);
	va_end(args);
	exit (0);
}

void sendfile (char *file) {
	FILE *f;
	int c;

	f = fopen(file, "rb");
	if (f == NULL) {
		errormsg("cannot open file '%s'", file);
		exit(0);
	}
	while (!feof(f)) {
		c=fgetc(f);
		putchar(c);
	}
}

void skipblanks(void) {

loop:
	while (*chptr == ' ' || *chptr == '\t' || *chptr == '\n')
		chptr++;
	if (*chptr == '\0' || *chptr == '#') {
		/* time to read the next line */
		if (fgets(text, MAXLEN+1, mapfile) == NULL) {
			errormsg("bad map file '%s' line %d",
			filename, linenum);
		}
		linenum++;
		chptr = text;
		goto loop;
	}
}

void expect_ch(char c) {

	skipblanks();
	if (*chptr == c)
		chptr++;
	else
		errormsg("syntax error in map file '%s' line %d : '%c' expected",
			filename, linenum, c);
}

int accept_int(int *x) {
	
	char number[11];
	int n;

	skipblanks();
	if (! isdigit(*chptr)) 
		errormsg("error in map file '%s' line %d : digit expected", filename,
			linenum);
	number[0] = *chptr++;
	n=1;
	while (n<10 && isdigit (*chptr)) {
		number[n] = *chptr;
		chptr++;
		n++;
	}
	number[n] = '\0';
	*x = atoi(number);
	return (*x);
}

char *accept_string(char *str) {
	
	char *st;

	skipblanks();
	st = str;
	while(*chptr != '\0' && *chptr != ' ' && *chptr != '\t' &&
		*chptr != '\n') {
		*st++ = *chptr++;
	}
	*st = '\0';
	if (strlen(str) == 0) 
		errormsg("error in map file '%s' line %d: emtpy string",
			filename, linenum);
	return (str);
}


	
main() {

	int x,y;
	int noismap;
	char *qmark;
	int len;
	char *first;
	char *last;
	int n;

	char fullpath[MAXLEN * 2];

	if (fgets(text, MAXLEN, stdin) == NULL) {
		/* apparently nothing, this is very unexpected 
		 * but there is nothing I can do
		 * I'll return some html file, which may be is
		 * the wrong file type, but who cares
		 */
		errormsg("no connection");
	}
	text[MAXLEN+1] = '\0';

	len = strlen(text);
	/* strip of trailing blanks */
	while (len > 0 && (text[len-1] == ' ' || text[len-1] == '\t' ||
			text[len-1] =='\n' || text[len-1] =='\r' )) {
		len--;
	}
	text[len] = '\0';
	qmark = strrchr(text, '?');
	if (qmark == NULL) {
		/* browser doesn't support ismap */
		noismap = 1;
		/* find the end of filename */
		last = text+len;
	} else {
		noismap = 0;
		last = qmark - 1;
	}
	first = last;
	while (first >= text && *first != '/' &&
			*first != ' ' && *first != '\t') 
		first--;
	if (first < text || *first == ' ' || *first == '\t') {
		errormsg("no such file");
	}
	if (*first == '/') 
		first++;
	
	/* ok assume that the file name is from text[first] to text[last] */

	strcpy(fullpath, ISMAP_PATH);
	strncpy(filename, first, last-first+1);
	filename[last-first+1] = '\0';
	if (strlen(filename) == 0)
		errormsg("error in href to map file");
	strcat(fullpath, filename);

	mapfile = fopen(fullpath,"r");
	if (mapfile == NULL)
		errormsg("cannot open requested map file '%s'", fullpath);
	
	/* when there is a question mark, read the coordinates */
	x = y = 0;
	if (qmark != NULL) {
		n = sscanf(qmark+1, "%d,%d", &x, &y);
		if (n != 2) {
			/* couldn't read the coordinates, act as
			 * if ismap was unsupported
			 */
			qmark = NULL;
			noismap = 1;
		}
	}
	/* start reading the map file */
	/* it is asumed that no line in the mapfile is longer than
	 * MAXLEN; it won't break memory allocation if there are
	 * longer lines, but these lines won't work (maybe in some
	 * rare case the next few lines too.
	 * really nothing to get upset about, just fold your lines (at
	 * positions were balnks are allowed)
	 * This loop re-uses the variable fullpath
	 */
	linenum = 0;
	while (fgets(text, MAXLEN+1, mapfile) != NULL) {
		linenum ++;
		chptr = text;
		skipblanks();
		/* find the keywords, ignoring any other lines .
		 * switch on the first character, and compare the rest
		 */
		switch (*chptr) {

		  case 'r': {
			int xmin, ymin, xmax, ymax;

			if (strncmp(chptr+1,"ect",3) == 0) {
				chptr+=4;
				expect_ch('(');
				accept_int(&xmin);
				expect_ch(',');
				accept_int(&ymin);
				expect_ch(',');
				accept_int(&xmax);
				expect_ch(',');
				accept_int(&ymax);
				expect_ch(')');
				if (x <= xmax && x >= xmin && y >= ymin
						&& y<=ymax) {
					accept_string(fullpath);
					sendfile(fullpath);
					return (0);
				}
			};
			break;
			}
		  case 'c': {
			int xcenter, ycenter;
			int radius;

			if (strncmp(chptr+1,"ircle",5) == 0) {

				chptr+=6;
				expect_ch('(');
				accept_int(&xcenter);
				expect_ch(',');
				accept_int(&ycenter);
				expect_ch(',');
				accept_int(&radius);
				expect_ch(')');
				if ((x-xcenter)*(x-xcenter) +
						(y-ycenter)*(y-ycenter) <=
						radius*radius) {
					accept_string(fullpath);
					sendfile(fullpath);
					return (0);
				}
			}
			break;
			}
		  case 'd': {
			if (strncmp(chptr+1,"efault",6) == 0) {
				chptr+=7;
				accept_string(fullpath);
				sendfile(fullpath);
				return (0);
			}
			break;
			}
		  case 'n': {
			if (strncmp(chptr+1,"oismap",6) == 0) {
				chptr+=7;
				if (noismap == 1) {
					accept_string(fullpath);
					sendfile(fullpath);
					return (0);
				}
			}
			break;
			}
		}
	}
	errormsg("error in mapfile '%s': EOF before default", filename);
	return (0);
}