1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * This imagemap module started as a port of the original imagemap.c
19 * written by Rob McCool (11/13/93 robm@ncsa.uiuc.edu).
20 * This version includes the mapping algorithms found in version 1.3
23 * Contributors to this code include:
25 * Kevin Hughes, kevinh@pulua.hcc.hawaii.edu
27 * Eric Haines, erich@eye.com
28 * "macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com
30 * Randy Terbush, randy@zyzzyva.com
31 * port to Apache module format, "base_uri" and support for relative URLs
33 * James H. Cloos, Jr., cloos@jhcloos.com
34 * Added point datatype, using code in NCSA's version 1.8 imagemap.c
35 * program, as distributed with version 1.4.1 of their server.
36 * The point code is originally added by Craig Milo Rogers, Rogers@ISI.Edu
38 * Nathan Kurz, nate@tripod.com
39 * Rewrite/reorganization. New handling of default, base and relative URLs.
40 * New Configuration directives:
41 * ImapMenu {none, formatted, semiformatted, unformatted}
42 * ImapDefault {error, nocontent, referer, menu, URL}
43 * ImapBase {map, referer, URL}
44 * Support for creating non-graphical menu added. (backwards compatible):
45 * Old: directive URL [x,y ...]
46 * New: directive URL "Menu text" [x,y ...]
47 * or: directive URL x,y ... "Menu text"
48 * Map format and menu concept courtesy Joshua Bell, jsbell@acs.ucalgary.ca.
50 * Mark Cox, mark@ukweb.com, Allow relative URLs even when no base specified
54 #include "apr_strings.h"
57 #define APR_WANT_STDIO /* for sscanf() */
58 #define APR_WANT_STRFUNC
61 #include "ap_config.h"
63 #include "http_config.h"
64 #include "http_request.h"
65 #include "http_core.h"
66 #include "http_protocol.h"
67 #include "http_main.h"
69 #include "util_script.h"
73 #define IMAP_MAGIC_TYPE "application/x-httpd-imap"
78 #define IMAP_MENU_DEFAULT "formatted"
79 #define IMAP_DEFAULT_DEFAULT "nocontent"
80 #define IMAP_BASE_DEFAULT "map"
83 double strtod(); /* SunOS needed this */
86 module AP_MODULE_DECLARE_DATA imap_module;
94 static void *create_imap_dir_config(apr_pool_t *p, char *dummy)
97 (imap_conf_rec *) apr_palloc(p, sizeof(imap_conf_rec));
99 icr->imap_menu = NULL;
100 icr->imap_default = NULL;
101 icr->imap_base = NULL;
106 static void *merge_imap_dir_configs(apr_pool_t *p, void *basev, void *addv)
108 imap_conf_rec *new = (imap_conf_rec *) apr_pcalloc(p, sizeof(imap_conf_rec));
109 imap_conf_rec *base = (imap_conf_rec *) basev;
110 imap_conf_rec *add = (imap_conf_rec *) addv;
112 new->imap_menu = add->imap_menu ? add->imap_menu : base->imap_menu;
113 new->imap_default = add->imap_default ? add->imap_default
114 : base->imap_default;
115 new->imap_base = add->imap_base ? add->imap_base : base->imap_base;
121 static const command_rec imap_cmds[] =
123 AP_INIT_TAKE1("ImapMenu", ap_set_string_slot,
124 (void *)APR_OFFSETOF(imap_conf_rec, imap_menu), OR_INDEXES,
125 "the type of menu generated: none, formatted, semiformatted, "
127 AP_INIT_TAKE1("ImapDefault", ap_set_string_slot,
128 (void *)APR_OFFSETOF(imap_conf_rec, imap_default), OR_INDEXES,
129 "the action taken if no match: error, nocontent, referer, "
131 AP_INIT_TAKE1("ImapBase", ap_set_string_slot,
132 (void *)APR_OFFSETOF(imap_conf_rec, imap_base), OR_INDEXES,
133 "the base for all URL's: map, referer, URL (or start of)"),
137 static int pointinrect(const double point[2], double coords[MAXVERTS][2])
139 double max[2], min[2];
140 if (coords[0][X] > coords[1][X]) {
141 max[0] = coords[0][X];
142 min[0] = coords[1][X];
145 max[0] = coords[1][X];
146 min[0] = coords[0][X];
149 if (coords[0][Y] > coords[1][Y]) {
150 max[1] = coords[0][Y];
151 min[1] = coords[1][Y];
154 max[1] = coords[1][Y];
155 min[1] = coords[0][Y];
158 return ((point[X] >= min[0] && point[X] <= max[0]) &&
159 (point[Y] >= min[1] && point[Y] <= max[1]));
162 static int pointincircle(const double point[2], double coords[MAXVERTS][2])
164 double radius1, radius2;
166 radius1 = ((coords[0][Y] - coords[1][Y]) * (coords[0][Y] - coords[1][Y]))
167 + ((coords[0][X] - coords[1][X]) * (coords[0][X] - coords[1][X]));
169 radius2 = ((coords[0][Y] - point[Y]) * (coords[0][Y] - point[Y]))
170 + ((coords[0][X] - point[X]) * (coords[0][X] - point[X]));
172 return (radius2 <= radius1);
175 #define fmin(a,b) (((a)>(b))?(b):(a))
176 #define fmax(a,b) (((a)>(b))?(a):(b))
178 static int pointinpoly(const double point[2], double pgon[MAXVERTS][2])
180 int i, numverts, crossings = 0;
181 double x = point[X], y = point[Y];
183 for (numverts = 0; pgon[numverts][X] != -1 && numverts < MAXVERTS;
185 /* just counting the vertexes */
188 for (i = 0; i < numverts; i++) {
189 double x1=pgon[i][X];
190 double y1=pgon[i][Y];
191 double x2=pgon[(i + 1) % numverts][X];
192 double y2=pgon[(i + 1) % numverts][Y];
193 double d=(y - y1) * (x2 - x1) - (x - x1) * (y2 - y1);
195 if ((y1 >= y) != (y2 >= y)) {
196 crossings +=y2 - y1 >= 0 ? d >= 0 : d <= 0;
198 if (!d && fmin(x1,x2) <= x && x <= fmax(x1,x2)
199 && fmin(y1,y2) <= y && y <= fmax(y1,y2)) {
203 return crossings & 0x01;
207 static int is_closer(const double point[2], double coords[MAXVERTS][2],
210 double dist_squared = ((point[X] - coords[0][X])
211 * (point[X] - coords[0][X]))
212 + ((point[Y] - coords[0][Y])
213 * (point[Y] - coords[0][Y]));
215 if (point[X] < 0 || point[Y] < 0) {
216 return (0); /* don't mess around with negative coordinates */
219 if (*closest < 0 || dist_squared < *closest) {
220 *closest = dist_squared;
221 return (1); /* if this is the first point or is the closest yet
222 set 'closest' equal to this distance^2 */
225 return (0); /* if it's not the first or closest */
229 static double get_x_coord(const char *args)
231 char *endptr; /* we want it non-null */
232 double x_coord = -1; /* -1 is returned if no coordinate is given */
235 return (-1); /* in case we aren't passed anything */
238 while (*args && !apr_isdigit(*args) && *args != ',') {
239 args++; /* jump to the first digit, but not past
243 x_coord = strtod(args, &endptr);
245 if (endptr > args) { /* if a conversion was made */
249 return (-1); /* else if no conversion was made,
250 or if no args was given */
253 static double get_y_coord(const char *args)
255 char *endptr; /* we want it non-null */
256 const char *start_of_y = NULL;
257 double y_coord = -1; /* -1 is returned on error */
260 return (-1); /* in case we aren't passed anything */
263 start_of_y = ap_strchr_c(args, ','); /* the comma */
267 start_of_y++; /* start looking at the character after
270 while (*start_of_y && !apr_isdigit(*start_of_y)) {
271 start_of_y++; /* jump to the first digit, but not
275 y_coord = strtod(start_of_y, &endptr);
277 if (endptr > start_of_y) {
282 return (-1); /* if no conversion was made, or
283 no comma was found in args */
287 /* See if string has a "quoted part", and if so set *quoted_part to
288 * the first character of the quoted part, then hammer a \0 onto the
289 * trailing quote, and set *string to point at the first character
290 * past the second quote.
292 * Otherwise set *quoted_part to NULL, and leave *string alone.
294 static void read_quoted(char **string, char **quoted_part)
296 char *strp = *string;
298 /* assume there's no quoted part */
301 while (apr_isspace(*strp)) {
302 strp++; /* go along string until non-whitespace */
305 if (*strp == '"') { /* if that character is a double quote */
306 strp++; /* step over it */
307 *quoted_part = strp; /* note where the quoted part begins */
309 while (*strp && *strp != '"') {
310 ++strp; /* skip the quoted portion */
313 *strp = '\0'; /* end the string with a NUL */
315 strp++; /* step over the last double quote */
321 * returns the mapped URL or NULL.
323 static char *imap_url(request_rec *r, const char *base, const char *value)
325 /* translates a value into a URL. */
327 char *string_pos = NULL;
328 const char *string_pos_const = NULL;
329 char *directory = NULL;
330 const char *referer = NULL;
333 if (!strcasecmp(value, "map") || !strcasecmp(value, "menu")) {
334 return ap_construct_url(r->pool, r->uri, r);
337 if (!strcasecmp(value, "nocontent") || !strcasecmp(value, "error")) {
338 return apr_pstrdup(r->pool, value); /* these are handled elsewhere,
342 if (!strcasecmp(value, "referer")) {
343 referer = apr_table_get(r->headers_in, "Referer");
344 if (referer && *referer) {
345 return ap_escape_html(r->pool, referer);
348 /* XXX: This used to do *value = '\0'; ... which is totally bogus
349 * because it hammers the passed in value, which can be a string
350 * constant, or part of a config, or whatever. Total garbage.
351 * This works around that without changing the rest of this
354 value = ""; /* if 'referer' but no referring page,
359 string_pos_const = value;
360 while (apr_isalpha(*string_pos_const)) {
361 string_pos_const++; /* go along the URL from the map
362 until a non-letter */
364 if (*string_pos_const == ':') {
365 /* if letters and then a colon (like http:) */
366 /* it's an absolute URL, so use it! */
367 return apr_pstrdup(r->pool, value);
370 if (!base || !*base) {
371 if (value && *value) {
372 return apr_pstrdup(r->pool, value); /* no base: use what is given */
374 /* no base, no value: pick a simple default */
375 return ap_construct_url(r->pool, "/", r);
378 /* must be a relative URL to be combined with base */
379 if (ap_strchr_c(base, '/') == NULL && (!strncmp(value, "../", 3)
380 || !strcmp(value, ".."))) {
381 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
382 "invalid base directive in map file: %s", r->uri);
385 my_base = apr_pstrdup(r->pool, base);
386 string_pos = my_base;
387 while (*string_pos) {
388 if (*string_pos == '/' && *(string_pos + 1) == '/') {
389 string_pos += 2; /* if there are two slashes, jump over them */
392 if (*string_pos == '/') { /* the first single slash */
393 if (value[0] == '/') {
395 } /* if the URL from the map starts from root,
396 end the base URL string at the first single
399 directory = string_pos; /* save the start of
400 the directory portion */
402 string_pos = strrchr(string_pos, '/'); /* now reuse
404 string_pos++; /* step over that last slash */
406 } /* but if the map url is relative, leave the
407 slash on the base (if there is one) */
410 string_pos++; /* until we get to the end of my_base without
411 finding a slash by itself */
414 while (!strncmp(value, "../", 3) || !strcmp(value, "..")) {
416 if (directory && (slen = strlen(directory))) {
418 /* for each '..', knock a directory off the end
419 by ending the string right at the last slash.
420 But only consider the directory portion: don't eat
421 into the server name. And only try if a directory
426 while ((slen - clen) == 1) {
428 if ((string_pos = strrchr(directory, '/'))) {
431 clen = strlen(directory);
437 value += 2; /* jump over the '..' that we found in the
440 else if (directory) {
441 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
442 "invalid directory name in map file: %s", r->uri);
446 if (!strncmp(value, "/../", 4) || !strcmp(value, "/..")) {
447 value++; /* step over the '/' if there are more '..'
448 to do. This way, we leave the starting
449 '/' on value after the last '..', but get
450 rid of it otherwise */
453 } /* by this point, value does not start
456 if (value && *value) {
457 return apr_pstrcat(r->pool, my_base, value, NULL);
462 static int imap_reply(request_rec *r, char *redirect)
464 if (!strcasecmp(redirect, "error")) {
465 /* they actually requested an error! */
466 return HTTP_INTERNAL_SERVER_ERROR;
468 if (!strcasecmp(redirect, "nocontent")) {
469 /* tell the client to keep the page it has */
470 return HTTP_NO_CONTENT;
472 if (redirect && *redirect) {
473 /* must be a URL, so redirect to it */
474 apr_table_setn(r->headers_out, "Location", redirect);
475 return HTTP_MOVED_TEMPORARILY;
477 return HTTP_INTERNAL_SERVER_ERROR;
480 static void menu_header(request_rec *r, char *menu)
482 ap_set_content_type(r, "text/html; charset=ISO-8859-1");
484 ap_rvputs(r, DOCTYPE_HTML_3_2, "<html><head>\n<title>Menu for ",
485 ap_escape_html(r->pool, r->uri),
486 "</title>\n</head><body>\n", NULL);
488 if (!strcasecmp(menu, "formatted")) {
489 ap_rvputs(r, "<h1>Menu for ",
490 ap_escape_html(r->pool, r->uri),
491 "</h1>\n<hr />\n\n", NULL);
497 static void menu_blank(request_rec *r, char *menu)
499 if (!strcasecmp(menu, "formatted")) {
502 if (!strcasecmp(menu, "semiformatted")) {
503 ap_rputs("<br />\n", r);
505 if (!strcasecmp(menu, "unformatted")) {
511 static void menu_comment(request_rec *r, char *menu, char *comment)
513 if (!strcasecmp(menu, "formatted")) {
514 ap_rputs("\n", r); /* print just a newline if 'formatted' */
516 if (!strcasecmp(menu, "semiformatted") && *comment) {
517 ap_rvputs(r, comment, "\n", NULL);
519 if (!strcasecmp(menu, "unformatted") && *comment) {
520 ap_rvputs(r, comment, "\n", NULL);
522 return; /* comments are ignored in the
526 static void menu_default(request_rec *r, char *menu, char *href, char *text)
528 if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
529 return; /* don't print such lines, these aren't
532 if (!strcasecmp(menu, "formatted")) {
533 ap_rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text,
534 "</a></pre>\n", NULL);
536 if (!strcasecmp(menu, "semiformatted")) {
537 ap_rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text,
538 "</a></pre>\n", NULL);
540 if (!strcasecmp(menu, "unformatted")) {
541 ap_rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL);
546 static void menu_directive(request_rec *r, char *menu, char *href, char *text)
548 if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
549 return; /* don't print such lines, as this isn't
552 if (!strcasecmp(menu, "formatted")) {
553 ap_rvputs(r, "<pre> <a href=\"", href, "\">", text,
554 "</a></pre>\n", NULL);
556 if (!strcasecmp(menu, "semiformatted")) {
557 ap_rvputs(r, "<pre> <a href=\"", href, "\">", text,
558 "</a></pre>\n", NULL);
560 if (!strcasecmp(menu, "unformatted")) {
561 ap_rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL);
566 static void menu_footer(request_rec *r)
568 ap_rputs("\n\n</body>\n</html>\n", r); /* finish the menu */
571 static int imap_handler_internal(request_rec *r)
573 char input[MAX_STRING_LEN];
580 char *closest = NULL;
581 double closest_yet = -1;
585 double pointarray[MAXVERTS + 1][2];
597 ap_configfile_t *imap;
599 icr = ap_get_module_config(r->per_dir_config, &imap_module);
601 imap_menu = icr->imap_menu ? icr->imap_menu : IMAP_MENU_DEFAULT;
602 imap_default = icr->imap_default
603 ? icr->imap_default : IMAP_DEFAULT_DEFAULT;
604 imap_base = icr->imap_base ? icr->imap_base : IMAP_BASE_DEFAULT;
606 status = ap_pcfg_openfile(&imap, r->pool, r->filename);
608 if (status != APR_SUCCESS) {
609 return HTTP_NOT_FOUND;
612 base = imap_url(r, NULL, imap_base); /* set base according
615 return HTTP_INTERNAL_SERVER_ERROR;
617 mapdflt = imap_url(r, NULL, imap_default); /* and default to
620 return HTTP_INTERNAL_SERVER_ERROR;
623 testpoint[X] = get_x_coord(r->args);
624 testpoint[Y] = get_y_coord(r->args);
626 if ((testpoint[X] == -1 || testpoint[Y] == -1) ||
627 (testpoint[X] == 0 && testpoint[Y] == 0)) {
628 /* if either is -1 or if both are zero (new Lynx) */
629 /* we don't have valid coordinates */
632 if (strncasecmp(imap_menu, "none", 2)) {
633 showmenu = 1; /* show the menu _unless_ ImapMenu is
638 if (showmenu) { /* send start of imagemap menu if
640 menu_header(r, imap_menu);
643 while (!ap_cfg_getline(input, sizeof(input), imap)) {
646 menu_blank(r, imap_menu);
651 if (input[0] == '#') {
653 menu_comment(r, imap_menu, input + 1);
656 } /* blank lines and comments are ignored
657 if we aren't printing a menu */
659 /* find the first two space delimited fields, recall that
660 * ap_cfg_getline has removed leading/trailing whitespace.
662 * note that we're tokenizing as we go... if we were to use the
663 * ap_getword() class of functions we would end up allocating extra
664 * memory for every line of the map file
667 if (!*string_pos) { /* need at least two fields */
671 directive = string_pos;
672 while (*string_pos && !apr_isspace(*string_pos)) { /* past directive */
675 if (!*string_pos) { /* need at least two fields */
678 *string_pos++ = '\0';
680 if (!*string_pos) { /* need at least two fields */
683 while(*string_pos && apr_isspace(*string_pos)) { /* past whitespace */
688 while (*string_pos && !apr_isspace(*string_pos)) { /* past value */
691 if (apr_isspace(*string_pos)) {
692 *string_pos++ = '\0';
695 /* end of input, don't advance past it */
699 if (!strncasecmp(directive, "base", 4)) { /* base, base_uri */
700 base = imap_url(r, NULL, value);
704 continue; /* base is never printed to a menu */
707 read_quoted(&string_pos, &href_text);
709 if (!strcasecmp(directive, "default")) { /* default */
710 mapdflt = imap_url(r, NULL, value);
714 if (showmenu) { /* print the default if there's a menu */
715 redirect = imap_url(r, base, mapdflt);
719 menu_default(r, imap_menu, redirect,
720 href_text ? href_text : mapdflt);
726 while (vertex < MAXVERTS &&
727 sscanf(string_pos, "%lf%*[, ]%lf",
728 &pointarray[vertex][X], &pointarray[vertex][Y]) == 2) {
729 /* Now skip what we just read... we can't use ANSIism %n */
730 while (apr_isspace(*string_pos)) { /* past whitespace */
733 while (apr_isdigit(*string_pos)) { /* and the 1st number */
736 string_pos++; /* skip the ',' */
737 while (apr_isspace(*string_pos)) { /* past any more whitespace */
740 while (apr_isdigit(*string_pos)) { /* 2nd number */
744 } /* so long as there are more vertices to
745 read, and we have room, read them in.
746 We start where we left off of the last
747 sscanf, not at the beginning. */
749 pointarray[vertex][X] = -1; /* signals the end of vertices */
753 read_quoted(&string_pos, &href_text); /* href text could
756 redirect = imap_url(r, base, value);
760 menu_directive(r, imap_menu, redirect,
761 href_text ? href_text : value);
764 /* note that we don't make it past here if we are making a menu */
766 if (testpoint[X] == -1 || pointarray[0][X] == -1) {
767 continue; /* don't try the following tests if testpoints
768 are invalid, or if there are no
772 if (!strcasecmp(directive, "poly")) { /* poly */
774 if (pointinpoly(testpoint, pointarray)) {
775 ap_cfg_closefile(imap);
776 redirect = imap_url(r, base, value);
778 return HTTP_INTERNAL_SERVER_ERROR;
780 return (imap_reply(r, redirect));
785 if (!strcasecmp(directive, "circle")) { /* circle */
787 if (pointincircle(testpoint, pointarray)) {
788 ap_cfg_closefile(imap);
789 redirect = imap_url(r, base, value);
791 return HTTP_INTERNAL_SERVER_ERROR;
793 return (imap_reply(r, redirect));
798 if (!strcasecmp(directive, "rect")) { /* rect */
800 if (pointinrect(testpoint, pointarray)) {
801 ap_cfg_closefile(imap);
802 redirect = imap_url(r, base, value);
804 return HTTP_INTERNAL_SERVER_ERROR;
806 return (imap_reply(r, redirect));
811 if (!strcasecmp(directive, "point")) { /* point */
813 if (is_closer(testpoint, pointarray, &closest_yet)) {
814 closest = apr_pstrdup(r->pool, value);
818 } /* move on to next line whether it's
821 } /* nothing matched, so we get another line! */
823 ap_cfg_closefile(imap); /* we are done with the map file; close it */
826 menu_footer(r); /* finish the menu and we are done */
830 if (closest) { /* if a 'point' directive has been seen */
831 redirect = imap_url(r, base, closest);
833 return HTTP_INTERNAL_SERVER_ERROR;
835 return (imap_reply(r, redirect));
838 if (mapdflt) { /* a default should be defined, even if
840 redirect = imap_url(r, base, mapdflt);
842 return HTTP_INTERNAL_SERVER_ERROR;
844 return (imap_reply(r, redirect));
847 return HTTP_INTERNAL_SERVER_ERROR; /* If we make it this far,
848 we failed. They lose! */
851 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
852 "map file %s, line %d syntax error: requires at "
853 "least two fields", r->uri, imap->line_number);
856 ap_cfg_closefile(imap);
858 /* There's not much else we can do ... we've already sent the headers
861 ap_rputs("\n\n[an internal server error occured]\n", r);
865 return HTTP_INTERNAL_SERVER_ERROR;
868 static int imap_handler(request_rec *r)
870 /* Optimization: skip the allocation of large local variables on the
871 * stack (in imap_handler_internal()) on requests that aren't using
874 if (r->method_number != M_GET || (strcmp(r->handler,IMAP_MAGIC_TYPE)
875 && strcmp(r->handler, "imap-file"))) {
879 return imap_handler_internal(r);
883 static void register_hooks(apr_pool_t *p)
885 ap_hook_handler(imap_handler,NULL,NULL,APR_HOOK_MIDDLE);
888 module AP_MODULE_DECLARE_DATA imap_module =
890 STANDARD20_MODULE_STUFF,
891 create_imap_dir_config, /* dir config creater */
892 merge_imap_dir_configs, /* dir merger --- default is to override */
893 NULL, /* server config */
894 NULL, /* merge server config */
895 imap_cmds, /* command apr_table_t */
896 register_hooks /* register hooks */