]> granicus.if.org Git - apache/blob - modules/mappers/mod_imagemap.c
9bf8c1659f152f7c6af289078e17c2a4c5ec11e4
[apache] / modules / mappers / mod_imagemap.c
1 /* Copyright 1999-2005 The Apache Software Foundation or its licensors, as
2  * applicable.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /*
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
21  * of imagemap.c.
22  *
23  * Contributors to this code include:
24  *
25  * Kevin Hughes, kevinh@pulua.hcc.hawaii.edu
26  *
27  * Eric Haines, erich@eye.com
28  * "macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com
29  *
30  * Randy Terbush, randy@zyzzyva.com
31  * port to Apache module format, "base_uri" and support for relative URLs
32  *
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
37  *
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.
49  *
50  * Mark Cox, mark@ukweb.com, Allow relative URLs even when no base specified
51  */
52
53 #include "apr.h"
54 #include "apr_strings.h"
55 #include "apr_lib.h"
56
57 #define APR_WANT_STDIO          /* for sscanf() */
58 #define APR_WANT_STRFUNC
59 #include "apr_want.h"
60
61 #include "ap_config.h"
62 #include "httpd.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"
68 #include "http_log.h"
69 #include "util_script.h"
70 #include "mod_core.h"
71
72
73 #define IMAP_MAGIC_TYPE "application/x-httpd-imap"
74 #define MAXVERTS 100
75 #define X 0
76 #define Y 1
77
78 #define IMAP_MENU_DEFAULT "formatted"
79 #define IMAP_DEFAULT_DEFAULT "nocontent"
80 #define IMAP_BASE_DEFAULT "map"
81
82 #ifdef SUNOS4
83 double strtod();                /* SunOS needed this */
84 #endif
85
86 module AP_MODULE_DECLARE_DATA imagemap_module;
87
88 typedef struct {
89     char *imap_menu;
90     char *imap_default;
91     char *imap_base;
92 } imap_conf_rec;
93
94 static void *create_imap_dir_config(apr_pool_t *p, char *dummy)
95 {
96     imap_conf_rec *icr =
97     (imap_conf_rec *) apr_palloc(p, sizeof(imap_conf_rec));
98
99     icr->imap_menu = NULL;
100     icr->imap_default = NULL;
101     icr->imap_base = NULL;
102
103     return icr;
104 }
105
106 static void *merge_imap_dir_configs(apr_pool_t *p, void *basev, void *addv)
107 {
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;
111
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;
116
117     return new;
118 }
119
120
121 static const command_rec imap_cmds[] =
122 {
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, "
126                   "unformatted"),
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, "
130                   "menu, URL"),
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)"),
134     {NULL}
135 };
136
137 static int pointinrect(const double point[2], double coords[MAXVERTS][2])
138 {
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];
143     }
144     else {
145         max[0] = coords[1][X];
146         min[0] = coords[0][X];
147     }
148
149     if (coords[0][Y] > coords[1][Y]) {
150         max[1] = coords[0][Y];
151         min[1] = coords[1][Y];
152     }
153     else {
154         max[1] = coords[1][Y];
155         min[1] = coords[0][Y];
156     }
157
158     return ((point[X] >= min[0] && point[X] <= max[0]) &&
159             (point[Y] >= min[1] && point[Y] <= max[1]));
160 }
161
162 static int pointincircle(const double point[2], double coords[MAXVERTS][2])
163 {
164     double radius1, radius2;
165
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]));
168
169     radius2 = ((coords[0][Y] - point[Y]) * (coords[0][Y] - point[Y]))
170         + ((coords[0][X] - point[X]) * (coords[0][X] - point[X]));
171
172     return (radius2 <= radius1);
173 }
174
175 #define fmin(a,b) (((a)>(b))?(b):(a))
176 #define fmax(a,b) (((a)>(b))?(a):(b))
177
178 static int pointinpoly(const double point[2], double pgon[MAXVERTS][2])
179 {
180     int i, numverts, crossings = 0;
181     double x = point[X], y = point[Y];
182
183     for (numverts = 0; pgon[numverts][X] != -1 && numverts < MAXVERTS;
184         numverts++) {
185         /* just counting the vertexes */
186     }
187
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);
194
195         if ((y1 >= y) != (y2 >= y)) {
196             crossings +=y2 - y1 >= 0 ? d >= 0 : d <= 0;
197         }
198         if (!d && fmin(x1,x2) <= x && x <= fmax(x1,x2)
199             && fmin(y1,y2) <= y && y <= fmax(y1,y2)) {
200             return 1;
201         }
202     }
203     return crossings & 0x01;
204 }
205
206
207 static int is_closer(const double point[2], double coords[MAXVERTS][2],
208                      double *closest)
209 {
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]));
214
215     if (point[X] < 0 || point[Y] < 0) {
216         return (0);          /* don't mess around with negative coordinates */
217     }
218
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 */
223     }
224
225     return (0);              /* if it's not the first or closest */
226
227 }
228
229 static double get_x_coord(const char *args)
230 {
231     char *endptr;               /* we want it non-null */
232     double x_coord = -1;        /* -1 is returned if no coordinate is given */
233
234     if (args == NULL) {
235         return (-1);            /* in case we aren't passed anything */
236     }
237
238     while (*args && !apr_isdigit(*args) && *args != ',') {
239         args++;                 /* jump to the first digit, but not past
240                                    a comma or end */
241     }
242
243     x_coord = strtod(args, &endptr);
244
245     if (endptr > args) {        /* if a conversion was made */
246         return (x_coord);
247     }
248
249     return (-1);                /* else if no conversion was made,
250                                    or if no args was given */
251 }
252
253 static double get_y_coord(const char *args)
254 {
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 */
258
259     if (args == NULL) {
260         return (-1);            /* in case we aren't passed anything */
261     }
262
263     start_of_y = ap_strchr_c(args, ',');     /* the comma */
264
265     if (start_of_y) {
266
267         start_of_y++;           /* start looking at the character after
268                                    the comma */
269
270         while (*start_of_y && !apr_isdigit(*start_of_y)) {
271             start_of_y++;       /* jump to the first digit, but not
272                                    past the end */
273         }
274
275         y_coord = strtod(start_of_y, &endptr);
276
277         if (endptr > start_of_y) {
278             return (y_coord);
279         }
280     }
281
282     return (-1);                /* if no conversion was made, or
283                                    no comma was found in args */
284 }
285
286
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.
291  *
292  * Otherwise set *quoted_part to NULL, and leave *string alone.
293  */
294 static void read_quoted(char **string, char **quoted_part)
295 {
296     char *strp = *string;
297
298     /* assume there's no quoted part */
299     *quoted_part = NULL;
300
301     while (apr_isspace(*strp)) {
302         strp++;                 /* go along string until non-whitespace */
303     }
304
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 */
308
309         while (*strp && *strp != '"') {
310             ++strp;             /* skip the quoted portion */
311         }
312
313         *strp = '\0';           /* end the string with a NUL */
314
315         strp++;                 /* step over the last double quote */
316         *string = strp;
317     }
318 }
319
320 /*
321  * returns the mapped URL or NULL.
322  */
323 static char *imap_url(request_rec *r, const char *base, const char *value)
324 {
325 /* translates a value into a URL. */
326     int slen, clen;
327     char *string_pos = NULL;
328     const char *string_pos_const = NULL;
329     char *directory = NULL;
330     const char *referer = NULL;
331     char *my_base;
332
333     if (!strcasecmp(value, "map") || !strcasecmp(value, "menu")) {
334         return ap_construct_url(r->pool, r->uri, r);
335     }
336
337     if (!strcasecmp(value, "nocontent") || !strcasecmp(value, "error")) {
338         return apr_pstrdup(r->pool, value);      /* these are handled elsewhere,
339                                                 so just copy them */
340     }
341
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);
346         }
347         else {
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
352              * code much
353              */
354             value = "";      /* if 'referer' but no referring page,
355                                 null the value */
356         }
357     }
358
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 */
363     }
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);
368     }
369
370     if (!base || !*base) {
371         if (value && *value) {
372             return apr_pstrdup(r->pool, value); /* no base: use what is given */
373         }
374         /* no base, no value: pick a simple default */
375         return ap_construct_url(r->pool, "/", r);
376     }
377
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);
383         return NULL;
384     }
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 */
390             continue;
391         }
392         if (*string_pos == '/') {       /* the first single slash */
393             if (value[0] == '/') {
394                 *string_pos = '\0';
395             }                   /* if the URL from the map starts from root,
396                                    end the base URL string at the first single
397                                    slash */
398             else {
399                 directory = string_pos;         /* save the start of
400                                                    the directory portion */
401
402                 string_pos = strrchr(string_pos, '/');  /* now reuse
403                                                            string_pos */
404                 string_pos++;   /* step over that last slash */
405                 *string_pos = '\0';
406             }                   /* but if the map url is relative, leave the
407                                    slash on the base (if there is one) */
408             break;
409         }
410         string_pos++;           /* until we get to the end of my_base without
411                                    finding a slash by itself */
412     }
413
414     while (!strncmp(value, "../", 3) || !strcmp(value, "..")) {
415
416         if (directory && (slen = strlen(directory))) {
417
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
422                portion was found */
423
424             clen = slen - 1;
425
426             while ((slen - clen) == 1) {
427
428                 if ((string_pos = strrchr(directory, '/'))) {
429                     *string_pos = '\0';
430                 }
431                 clen = strlen(directory);
432                 if (clen == 0) {
433                     break;
434                 }
435             }
436
437             value += 2;         /* jump over the '..' that we found in the
438                                    value */
439         }
440         else if (directory) {
441             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
442                         "invalid directory name in map file: %s", r->uri);
443             return NULL;
444         }
445
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 */
451         }
452
453     }                           /* by this point, value does not start
454                                    with '..' */
455
456     if (value && *value) {
457         return apr_pstrcat(r->pool, my_base, value, NULL);
458     }
459     return my_base;
460 }
461
462 static int imap_reply(request_rec *r, char *redirect)
463 {
464     if (!strcasecmp(redirect, "error")) {
465         /* they actually requested an error! */
466         return HTTP_INTERNAL_SERVER_ERROR;
467     }
468     if (!strcasecmp(redirect, "nocontent")) {
469         /* tell the client to keep the page it has */
470         return HTTP_NO_CONTENT;
471     }
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;
476     }
477     return HTTP_INTERNAL_SERVER_ERROR;
478 }
479
480 static void menu_header(request_rec *r, char *menu)
481 {
482     ap_set_content_type(r, "text/html");
483
484     ap_rvputs(r, DOCTYPE_HTML_3_2, "<html><head>\n<title>Menu for ", r->uri,
485            "</title>\n</head><body>\n", NULL);
486
487     if (!strcasecmp(menu, "formatted")) {
488         ap_rvputs(r, "<h1>Menu for ", r->uri, "</h1>\n<hr />\n\n", NULL);
489     }
490
491     return;
492 }
493
494 static void menu_blank(request_rec *r, char *menu)
495 {
496     if (!strcasecmp(menu, "formatted")) {
497         ap_rputs("\n", r);
498     }
499     if (!strcasecmp(menu, "semiformatted")) {
500         ap_rputs("<br />\n", r);
501     }
502     if (!strcasecmp(menu, "unformatted")) {
503         ap_rputs("\n", r);
504     }
505     return;
506 }
507
508 static void menu_comment(request_rec *r, char *menu, char *comment)
509 {
510     if (!strcasecmp(menu, "formatted")) {
511         ap_rputs("\n", r);         /* print just a newline if 'formatted' */
512     }
513     if (!strcasecmp(menu, "semiformatted") && *comment) {
514         ap_rvputs(r, comment, "\n", NULL);
515     }
516     if (!strcasecmp(menu, "unformatted") && *comment) {
517         ap_rvputs(r, comment, "\n", NULL);
518     }
519     return;                     /* comments are ignored in the
520                                    'formatted' form */
521 }
522
523 static void menu_default(request_rec *r, char *menu, char *href, char *text)
524 {
525     if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
526         return;                 /* don't print such lines, these aren't
527                                    really href's */
528     }
529     if (!strcasecmp(menu, "formatted")) {
530         ap_rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text,
531                "</a></pre>\n", NULL);
532     }
533     if (!strcasecmp(menu, "semiformatted")) {
534         ap_rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text,
535                "</a></pre>\n", NULL);
536     }
537     if (!strcasecmp(menu, "unformatted")) {
538         ap_rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL);
539     }
540     return;
541 }
542
543 static void menu_directive(request_rec *r, char *menu, char *href, char *text)
544 {
545     if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
546         return;                 /* don't print such lines, as this isn't
547                                    really an href */
548     }
549     if (!strcasecmp(menu, "formatted")) {
550         ap_rvputs(r, "<pre>          <a href=\"", href, "\">", text,
551                "</a></pre>\n", NULL);
552     }
553     if (!strcasecmp(menu, "semiformatted")) {
554         ap_rvputs(r, "<pre>          <a href=\"", href, "\">", text,
555                "</a></pre>\n", NULL);
556     }
557     if (!strcasecmp(menu, "unformatted")) {
558         ap_rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL);
559     }
560     return;
561 }
562
563 static void menu_footer(request_rec *r)
564 {
565     ap_rputs("\n\n</body>\n</html>\n", r);         /* finish the menu */
566 }
567
568 static int imap_handler_internal(request_rec *r)
569 {
570     char input[MAX_STRING_LEN];
571     char *directive;
572     char *value;
573     char *href_text;
574     char *base;
575     char *redirect;
576     char *mapdflt;
577     char *closest = NULL;
578     double closest_yet = -1;
579     apr_status_t status;
580
581     double testpoint[2];
582     double pointarray[MAXVERTS + 1][2];
583     int vertex;
584
585     char *string_pos;
586     int showmenu = 0;
587
588     imap_conf_rec *icr;
589
590     char *imap_menu;
591     char *imap_default;
592     char *imap_base;
593
594     ap_configfile_t *imap;
595
596     icr = ap_get_module_config(r->per_dir_config, &imagemap_module);
597
598     imap_menu = icr->imap_menu ? icr->imap_menu : IMAP_MENU_DEFAULT;
599     imap_default = icr->imap_default
600       ?  icr->imap_default : IMAP_DEFAULT_DEFAULT;
601     imap_base = icr->imap_base ? icr->imap_base : IMAP_BASE_DEFAULT;
602
603     status = ap_pcfg_openfile(&imap, r->pool, r->filename);
604
605     if (status != APR_SUCCESS) {
606         return HTTP_NOT_FOUND;
607     }
608
609     base = imap_url(r, NULL, imap_base);         /* set base according
610                                                     to default */
611     if (!base) {
612         return HTTP_INTERNAL_SERVER_ERROR;
613     }
614     mapdflt = imap_url(r, NULL, imap_default);   /* and default to
615                                                     global default */
616     if (!mapdflt) {
617         return HTTP_INTERNAL_SERVER_ERROR;
618     }
619
620     testpoint[X] = get_x_coord(r->args);
621     testpoint[Y] = get_y_coord(r->args);
622
623     if ((testpoint[X] == -1 || testpoint[Y] == -1) ||
624         (testpoint[X] == 0 && testpoint[Y] == 0)) {
625         /* if either is -1 or if both are zero (new Lynx) */
626         /* we don't have valid coordinates */
627         testpoint[X] = -1;
628         testpoint[Y] = -1;
629         if (strncasecmp(imap_menu, "none", 2)) {
630             showmenu = 1;       /* show the menu _unless_ ImapMenu is
631                                    'none' or 'no' */
632         }
633     }
634
635     if (showmenu) {             /* send start of imagemap menu if
636                                    we're going to */
637         menu_header(r, imap_menu);
638     }
639
640     while (!ap_cfg_getline(input, sizeof(input), imap)) {
641         if (!input[0]) {
642             if (showmenu) {
643                 menu_blank(r, imap_menu);
644             }
645             continue;
646         }
647
648         if (input[0] == '#') {
649             if (showmenu) {
650                 menu_comment(r, imap_menu, input + 1);
651             }
652             continue;
653         }                       /* blank lines and comments are ignored
654                                    if we aren't printing a menu */
655
656         /* find the first two space delimited fields, recall that
657          * ap_cfg_getline has removed leading/trailing whitespace.
658          *
659          * note that we're tokenizing as we go... if we were to use the
660          * ap_getword() class of functions we would end up allocating extra
661          * memory for every line of the map file
662          */
663         string_pos = input;
664         if (!*string_pos) {   /* need at least two fields */
665             goto need_2_fields;
666         }
667
668         directive = string_pos;
669         while (*string_pos && !apr_isspace(*string_pos)) {   /* past directive */
670             ++string_pos;
671         }
672         if (!*string_pos) {   /* need at least two fields */
673             goto need_2_fields;
674         }
675         *string_pos++ = '\0';
676
677         if (!*string_pos) {   /* need at least two fields */
678             goto need_2_fields;
679         }
680         while(*string_pos && apr_isspace(*string_pos)) { /* past whitespace */
681             ++string_pos;
682         }
683
684         value = string_pos;
685         while (*string_pos && !apr_isspace(*string_pos)) {   /* past value */
686             ++string_pos;
687         }
688         if (apr_isspace(*string_pos)) {
689             *string_pos++ = '\0';
690         }
691         else {
692             /* end of input, don't advance past it */
693             *string_pos = '\0';
694         }
695
696         if (!strncasecmp(directive, "base", 4)) {       /* base, base_uri */
697             base = imap_url(r, NULL, value);
698             if (!base) {
699                 goto menu_bail;
700             }
701             continue;           /* base is never printed to a menu */
702         }
703
704         read_quoted(&string_pos, &href_text);
705
706         if (!strcasecmp(directive, "default")) {        /* default */
707             mapdflt = imap_url(r, NULL, value);
708             if (!mapdflt) {
709                 goto menu_bail;
710             }
711             if (showmenu) {     /* print the default if there's a menu */
712                 redirect = imap_url(r, base, mapdflt);
713                 if (!redirect) {
714                     goto menu_bail;
715                 }
716                 menu_default(r, imap_menu, redirect,
717                              href_text ? href_text : mapdflt);
718             }
719             continue;
720         }
721
722         vertex = 0;
723         while (vertex < MAXVERTS &&
724                sscanf(string_pos, "%lf%*[, ]%lf",
725                       &pointarray[vertex][X], &pointarray[vertex][Y]) == 2) {
726             /* Now skip what we just read... we can't use ANSIism %n */
727             while (apr_isspace(*string_pos)) {      /* past whitespace */
728                 string_pos++;
729             }
730             while (apr_isdigit(*string_pos)) {      /* and the 1st number */
731                 string_pos++;
732             }
733             string_pos++;       /* skip the ',' */
734             while (apr_isspace(*string_pos)) {      /* past any more whitespace */
735                 string_pos++;
736             }
737             while (apr_isdigit(*string_pos)) {      /* 2nd number */
738                 string_pos++;
739             }
740             vertex++;
741         }                       /* so long as there are more vertices to
742                                    read, and we have room, read them in.
743                                    We start where we left off of the last
744                                    sscanf, not at the beginning. */
745
746         pointarray[vertex][X] = -1;     /* signals the end of vertices */
747
748         if (showmenu) {
749             if (!href_text) {
750                 read_quoted(&string_pos, &href_text);     /* href text could
751                                                              be here instead */
752             }
753             redirect = imap_url(r, base, value);
754             if (!redirect) {
755                 goto menu_bail;
756             }
757             menu_directive(r, imap_menu, redirect,
758                            href_text ? href_text : value);
759             continue;
760         }
761         /* note that we don't make it past here if we are making a menu */
762
763         if (testpoint[X] == -1 || pointarray[0][X] == -1) {
764             continue;           /* don't try the following tests if testpoints
765                                    are invalid, or if there are no
766                                    coordinates */
767         }
768
769         if (!strcasecmp(directive, "poly")) {   /* poly */
770
771             if (pointinpoly(testpoint, pointarray)) {
772                 ap_cfg_closefile(imap);
773                 redirect = imap_url(r, base, value);
774                 if (!redirect) {
775                     return HTTP_INTERNAL_SERVER_ERROR;
776                 }
777                 return (imap_reply(r, redirect));
778             }
779             continue;
780         }
781
782         if (!strcasecmp(directive, "circle")) {         /* circle */
783
784             if (pointincircle(testpoint, pointarray)) {
785                 ap_cfg_closefile(imap);
786                 redirect = imap_url(r, base, value);
787                 if (!redirect) {
788                     return HTTP_INTERNAL_SERVER_ERROR;
789                 }
790                 return (imap_reply(r, redirect));
791             }
792             continue;
793         }
794
795         if (!strcasecmp(directive, "rect")) {   /* rect */
796
797             if (pointinrect(testpoint, pointarray)) {
798                 ap_cfg_closefile(imap);
799                 redirect = imap_url(r, base, value);
800                 if (!redirect) {
801                     return HTTP_INTERNAL_SERVER_ERROR;
802                 }
803                 return (imap_reply(r, redirect));
804             }
805             continue;
806         }
807
808         if (!strcasecmp(directive, "point")) {  /* point */
809
810             if (is_closer(testpoint, pointarray, &closest_yet)) {
811                 closest = apr_pstrdup(r->pool, value);
812             }
813
814             continue;
815         }                       /* move on to next line whether it's
816                                    closest or not */
817
818     }                           /* nothing matched, so we get another line! */
819
820     ap_cfg_closefile(imap);        /* we are done with the map file; close it */
821
822     if (showmenu) {
823         menu_footer(r);         /* finish the menu and we are done */
824         return OK;
825     }
826
827     if (closest) {             /* if a 'point' directive has been seen */
828         redirect = imap_url(r, base, closest);
829         if (!redirect) {
830             return HTTP_INTERNAL_SERVER_ERROR;
831         }
832         return (imap_reply(r, redirect));
833     }
834
835     if (mapdflt) {             /* a default should be defined, even if
836                                   only 'nocontent' */
837         redirect = imap_url(r, base, mapdflt);
838         if (!redirect) {
839             return HTTP_INTERNAL_SERVER_ERROR;
840         }
841         return (imap_reply(r, redirect));
842     }
843
844     return HTTP_INTERNAL_SERVER_ERROR;        /* If we make it this far,
845                                                  we failed. They lose! */
846
847 need_2_fields:
848     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
849                 "map file %s, line %d syntax error: requires at "
850                 "least two fields", r->uri, imap->line_number);
851     /* fall through */
852 menu_bail:
853     ap_cfg_closefile(imap);
854     if (showmenu) {
855         /* There's not much else we can do ... we've already sent the headers
856          * to the client.
857          */
858         ap_rputs("\n\n[an internal server error occured]\n", r);
859         menu_footer(r);
860         return OK;
861     }
862     return HTTP_INTERNAL_SERVER_ERROR;
863 }
864
865 static int imap_handler(request_rec *r)
866 {
867     /* Optimization: skip the allocation of large local variables on the
868      * stack (in imap_handler_internal()) on requests that aren't using
869      * imagemaps
870      */
871     if (r->method_number != M_GET || (strcmp(r->handler,IMAP_MAGIC_TYPE)
872                                       && strcmp(r->handler, "imap-file"))) {
873         return DECLINED;
874     }
875     else {
876         return imap_handler_internal(r);
877     }
878 }
879
880 static void register_hooks(apr_pool_t *p)
881 {
882     ap_hook_handler(imap_handler,NULL,NULL,APR_HOOK_MIDDLE);
883 }
884
885 module AP_MODULE_DECLARE_DATA imagemap_module =
886 {
887     STANDARD20_MODULE_STUFF,
888     create_imap_dir_config,     /* dir config creater */
889     merge_imap_dir_configs,     /* dir merger --- default is to override */
890     NULL,                       /* server config */
891     NULL,                       /* merge server config */
892     imap_cmds,                  /* command apr_table_t */
893     register_hooks              /* register hooks */
894 };