/* * This program is a server script for the DECthread HTTP_SERVER. It * provides support for clickable images, mapping regions defined in a * configuration file to URLs. * * This program assumes that it was invoked by the WWWEXEC decnet task and * the logical dnet_link is a process permanent file connected to the * the HTTP server. * * Image config file format: * Comment lines begin with "#", otherwise map entries start with * one of the keywords(abrev): default(def), circle(circ), * rectangle(rect), polygon(poly). * * default URL * Default URL to return if point not within any of the other * figures. * * circle (cx,cy) r URL * Return URL if point is within circle. * * rectangle (x1,y1) (x2,y2) URL * Retrun URL if point is within rectangle defined by any two * oposing corners. * * polygon (x1,y1) ... (xn,yn) URL * Return URL if point is within polygon defined by list of * points. * * Author: David Jones * Date: 19-MAR-1994 * Revised: 20-JUL-1994 Add xlate operation to inp file. * Revised: 27-SEP-1994 Support case sensitive paths. * Revised: 13-JAN-1994 Make default work indpendant of position. * Revised: 6-FEB-1995 Accept + as coordinate delimiter. * Revised: 23-JUN-1995 Fix bugs in polygon processing. * Revised: 10-JAN-1995 Declare sqrt() using math.h header file. */ #include #include #include #include #include #include "scriptlib.h" /* net_link_... routines */ #define CGI_RELOCATE "location: %s\r\n\r\n" struct stream_ctx { FILE *f; /* Input file */ int used, filled; char buffer[4096]; }; typedef struct stream_ctx *stream; struct token_list { int type; /* -1: URL, 0: text, 1: default, 2: rect 3: circle, 4: polygon */ char *s; /* Text pointer */ float x, y; }; static int error_abort ( char *status_line, char *message ); static int check_polygon(), get_command(), get_token(); /********************************************************************/ /* Main routine. */ int main ( int argc, char **argv ) { int tcnt, length, i, status, xlate_len; float x, y, d, min_x, max_x, min_y, max_y, dx, dy; char *inp, search_arg[256], tokbuf[4096], match_url[4096]; char bin_path[256], xlate_buf[256]; stream ctx; struct token_list tok[512]; /* * Validate arguments command line arguments. The config file name * is appended to the script name that invokes this program. */ if ( argc < 3 ) { fprintf( stderr, "Missing arguments, usage: prog method url\n"); exit ( 1 ); } /* * Open network link and get search/point argument. Note that network * connection is record oriented, a zero length record is allowed. */ status = net_link_open(); if ( (status&1) == 0 ) { perror("Network connect fail"); exit (status); } /* * Verify that URL starts with the appropriate path, then trim * off path and script name from the URL. */ status = net_link_query ( "", bin_path, sizeof(bin_path)-1, &length ); if ( (status&1) == 0 ) { perror("Network I/O failure"); exit (status); } if ( length < 0 ) exit ( 1 ); bin_path[length] = '\0'; /* terminate string */ if ( bin_path[0] == '/' ) { if ( 0 != strncmp(argv[2], bin_path, length) ) { fprintf( stderr, "Invalid URL (%s), must start with %s\n",argv[2], bin_path); exit ( 1 ); } for ( inp = &argv[2][length]; *inp && (*inp != '/'); inp++ ); /* * Translate the path. */ status = net_link_write ( "", 11 ); if ( (status&1) == 0 ) { perror("Network I/O failure"); exit (status); } status = net_link_query ( inp, xlate_buf, sizeof(xlate_buf)-1, &xlate_len ); if ( (status&1) == 0 ) { perror("Network I/O failure"); exit (status); } xlate_buf[xlate_len] = '\0'; inp = xlate_buf; } else { /* Script invoked via presenation rule, use ident as passed */ inp = argv[2]; } /* * Retrieve search string and decode coordinates. */ status = net_link_query ( "", search_arg, sizeof(search_arg)-1, &length ); if ( (status&1) == 0 ) { perror("Network I/O failure"); exit (status); } if ( length < 0 ) exit ( 1 ); search_arg[length] = '\0'; if ( !strchr ( search_arg, ',' ) ) { /* Search argument doesn't have ',', look for '+' and convert */ for ( i = 0; search_arg[i]; i++ ) if ( search_arg[i] == '+' ) { search_arg[i] = ','; break; } } if ( 2 != sscanf ( search_arg, "?%f,%f", &x, &y ) ) { printf("Bad search arguments: %s\n", search_arg ); error_abort ( "500 invalid request", "Error decoding map position arguments." ); } /* * Initialize structure for reading input file and open file. */ ctx = (stream) malloc ( sizeof(struct stream_ctx) ); ctx->used = ctx->filled = 0; ctx->f = fopen ( inp, "r", "mbc=32" ); /* configuration file */ if ( !ctx->f ) { error_abort ( "404 open failure", "Open failure on file" ); } /* * Process file test every figure to allow for overlapping regions. */ match_url[0] = '\0'; /* default to no match */ while ( (tcnt = get_command ( ctx, tok, 512, tokbuf, sizeof(tokbuf)))>1) { switch ( tok[0].type ) { case 1: /* Default if no match */ if ( match_url[0] == '\0' ) sprintf ( match_url, CGI_RELOCATE, tok[1].s ); break; case 2: /* rectangle */ min_x = tok[1].x < tok[2].x ? tok[1].x : tok[2].x; max_x = tok[1].x > tok[2].x ? tok[1].x : tok[2].x; min_y = tok[1].y < tok[2].y ? tok[1].y : tok[2].y; max_y = tok[1].y > tok[2].y ? tok[1].y : tok[2].y; if ( (x >= min_x) && (x <= max_x) && (y >= min_y) && (y <= max_y) ) { sprintf ( match_url, CGI_RELOCATE, tok[3].s ); } break; case 3: /* circle */ dx = x - tok[1].x; dy = y - tok[1].y; if ( sqrt ( (dx*dx) + (dy*dy) ) <= tok[2].x ) { sprintf ( match_url, CGI_RELOCATE, tok[3].s ); } break; case 4: /* polygon */ check_polygon ( &tok, match_url, x, y ); break; } } fclose ( ctx->f ); /* * Either send error message or send relocate. */ if ( *match_url ) { /* * Send relocate. */ status = net_link_write ( "", 9 ); status = net_link_write ( match_url, strlen(match_url) ); status = net_link_write ( "", 10 ); } else { error_abort ( "500 no match", "No translation for image point" ); } status = net_link_close (); return 1; } static int error_abort ( char *status_line, char *message ) { int written, length, status; status = net_link_write ( "", 10 ); status = net_link_write ( status_line, strlen(status_line) ); status = net_link_write ( message, strlen(message) ); status = net_link_write ( "", 11 ); status = net_link_close(); exit ( 1 ); return 1; /* Keep the compiler happy */ } /**************************************************************************/ /* Test whether x/y point lies within polygon defined by point within * token list. */ static int check_polygon ( struct token_list *tok, char *match_url, double dblx, double dbly ) { int npnts, count, i, prev; float x1, y1, x2, y2, t, x, y; /* * Find number of points in polygon and duplicate first point */ x = dblx; y = dbly; for ( npnts = 0; tok[npnts+1].type > -1; npnts++ ); tok[npnts+1].x = tok[1].x; tok[npnts+1].y = tok[1].y; #ifdef DEBUG printf("Testing point (%f, %f)\n", x, y ); #endif /* * Count number of polygon sides that cross same y value and are * to left (less) than x value. Line segment with endpoint at * same y as test point only counts if it is the endpoint with lower y. */ for ( prev = count = 0, i = 1; i <= npnts; i++ ) { /* Set vertices. */ x1 = tok[i].x; y1 = tok[i].y; x2 = tok[i+1].x; y2 = tok[i+1].y; #ifdef DEBUG printf("segment[%d] = (%f,%f) (%f,%f)\n", i, x1, y1, x2, y2 ); #endif if ( ((y1 <= y) && (y2 > y)) || ((y1 > y) && (y2 <= y)) ) { #ifdef DEBUG printf(" segment in proper y range x range: %f %f\n", x1, x2); #endif /* * Determine if test point is to right of line segment. * Lying on a horizontal line counts as a YES since the * the 2 adjacent sides will match also and cancel each other. */ if ( (x >= x1) && (x >= x2) ) count++; /* easy test */ else if ( (x < x1) && (x < x2) ); /* to left */ else { /* Tougher case, find intercept position. */ float slope; slope = (x2 - x1) / (y2 - y1); x1 = x1 + (slope * (y - y1)); /* Test if we are 2 right of intercept position */ #ifdef DEBUG printf("Intercept x: %f y: %f test: %f\n", x1, y, x ); #endif if ( x >= x1 ) count++; } } else if ( (y1 == y2) && (y == y1) ) { /* * Edge is horizontal, see if test point on line and force * match. Otherwise endpoint test for the adjacent lines will * determine crossings. */ if ( ((x >= x1) && (x <= x2)) || ((x <= x1) && (x >= x2)) ) { count = 1; /* force successfull match */ break; } } #ifdef DEBUG if ( count > prev ) { printf(" point is right of segment\n"); prev = count; } #endif } /* * Now we have the count, determine if we are inside the polygon. * We are inside if we are 2 right of an odd number of sides. */ #ifdef DEBUG printf("polygon cross count = %d: %s", count, match_url ); #endif if ( (count % 2) == 1 ) { sprintf ( match_url, CGI_RELOCATE, tok[npnts+1].s ); } return 1; } /**************************************************************************/ /* Parse next directive out of input stream, value return is number of * tokens parsed */ static int get_command ( stream ctx, struct token_list *tok, int tok_limit, char *tokbuf, int tokbufsize ) { int i, tokpos, length, status, get_token(); struct { char *keyword, *abbrev; int code; } cmds[] = { { "default", "def", 1 }, { "rectangle", "rect", 2 }, { "circle", "circ", 3 }, { "polygon", "poly", 4 }, { "", "", -1 } }; /* * Load first token into callers buffer. */ tokpos = 0; tok[0].s = &tokbuf[tokpos]; length = get_token ( ctx, tok[0].s, tokbufsize ); if ( length <= 0 ) return length; tokpos += length; tokbuf[tokpos++] = '\0'; /* terminate string and allocate */ /* * Convert first token to code number. */ for ( i = 0; i < length; i++ ) tokbuf[i] = _tolower ( tokbuf[i] ); for ( tok[0].type = i = 0; cmds[i].code > 0; i++ ) { if ( (strcmp ( tokbuf, cmds[i].keyword ) == 0) || (strcmp ( tokbuf, cmds[i].abbrev ) == 0) ) { tok[0].type = cmds[i].code; break; } } if ( tok[0].type == 0 ) return 1; /* * Load additional tokens. */ for ( i = 1; i < tok_limit; i++ ) { /* * Read next token from input file, update tokpos. */ tok[i].s = &tokbuf[tokpos]; length = get_token ( ctx, tok[i].s, tokbufsize - tokpos-1 ); if ( length <= 0 ) return i; tokpos += length; tokbuf[tokpos++] = '\0'; /* * Convert x,y pairs. */ if ( *tok[i].s == '(' ) { status = sscanf ( tok[i].s, "(%f,%f)", &tok[i].x, &tok[i].y ); } /* * Interpret token in context of command type. */ switch ( tok[0].type ) { case 1: /* Default */ tok[i].type = -1; /* only argument URL */ return i+1; case 2: /* Rectangle */ if ( i < 3 ) { tok[i].type = 0; if ( *tok[i].s != '(' ) printf ( "Syntax error in rect\n" ); } else { tok[i].type = -1; return i + 1; } break; case 3: /* Circle */ if ( i == 1 ) { tok[i].type = 0; } else if ( i == 2 ) { status = sscanf ( tok[i].s, "%f", &tok[i].x ); tok[i].type = 0; } else { tok[i].type = -1; return i + 1; } break; case 4: /* Polygon */ if ( *tok[i].s == '(' ) { tok[i].type = 0; } else { tok[i].type = -1; /* Last one */ return i + 1; } } } } /**************************************************************************/ /* Parse next token out of input file. */ static int get_token ( stream ctx, char *token, int maxlen ) { int tlen, state, used, filled; char *linebuf, letter; /* * Skip whitespace */ linebuf = ctx->buffer; used = ctx->used; filled = ctx->filled; for ( state = 0; state >= 0; used++ ) { while ( used >= filled ) { used = 0; filled = fread (linebuf, sizeof(char), sizeof(ctx->buffer),ctx->f); if ( filled <= 0 ) { ctx->filled = -1; ctx->used = 0; return filled; } } if ( state == 0 ) { /* Look for first non-blank */ if ( !isspace(linebuf[used]) ) { if ( linebuf[used] == '#' ) state = 1; else { break; } } } else if ( state == 1 ) { /* Comment line, look for line terminator */ if ( linebuf[used] == '\n' ) state = 0; /* start at next line*/ } } /* * Scan for end of token. */ state = 0; if ( linebuf[used] == '(' ) state = 1; token[0] = linebuf[used++]; for ( tlen = 1; tlen < maxlen; used++) { while ( used >= filled ) { used = 0; filled = fread (linebuf, sizeof(char), sizeof(ctx->buffer),ctx->f); if ( filled <= 0 ) { ctx->filled = -1; ctx->used = 0; return tlen; } } letter = linebuf[used]; if ( state == 0 ) { /* Break on whitespace or append to token string */ if ( isspace(letter) ) break; else token[tlen++] = letter; } else { /* Break on closing ')', ignore whitespace */ if ( !isspace(letter) ) { token[tlen++] = letter; if ( letter == ')' ) { used++; /* don't re-scan the ')' next time */ break; } } } } ctx->used = used; ctx->filled = filled; return tlen; }