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
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
82module AP_MODULE_DECLARE_DATA imagemap_module;
83
84typedef struct {
85    char *imap_menu;
86    char *imap_default;
87    char *imap_base;
88} imap_conf_rec;
89
90static void *create_imap_dir_config(apr_pool_t *p, char *dummy)
91{
92    imap_conf_rec *icr =
93    (imap_conf_rec *) apr_palloc(p, sizeof(imap_conf_rec));
94
95    icr->imap_menu = NULL;
96    icr->imap_default = NULL;
97    icr->imap_base = NULL;
98
99    return icr;
100}
101
102static void *merge_imap_dir_configs(apr_pool_t *p, void *basev, void *addv)
103{
104    imap_conf_rec *new = (imap_conf_rec *) apr_palloc(p, sizeof(imap_conf_rec));
105    imap_conf_rec *base = (imap_conf_rec *) basev;
106    imap_conf_rec *add = (imap_conf_rec *) addv;
107
108    new->imap_menu = add->imap_menu ? add->imap_menu : base->imap_menu;
109    new->imap_default = add->imap_default ? add->imap_default
110                                          : base->imap_default;
111    new->imap_base = add->imap_base ? add->imap_base : base->imap_base;
112
113    return new;
114}
115
116
117static const command_rec imap_cmds[] =
118{
119    AP_INIT_TAKE1("ImapMenu", ap_set_string_slot,
120                  (void *)APR_OFFSETOF(imap_conf_rec, imap_menu), OR_INDEXES,
121                  "the type of menu generated: none, formatted, semiformatted, "
122                  "unformatted"),
123    AP_INIT_TAKE1("ImapDefault", ap_set_string_slot,
124                  (void *)APR_OFFSETOF(imap_conf_rec, imap_default), OR_INDEXES,
125                  "the action taken if no match: error, nocontent, referer, "
126                  "menu, URL"),
127    AP_INIT_TAKE1("ImapBase", ap_set_string_slot,
128                  (void *)APR_OFFSETOF(imap_conf_rec, imap_base), OR_INDEXES,
129                  "the base for all URL's: map, referer, URL (or start of)"),
130    {NULL}
131};
132
133static int pointinrect(const double point[2], double coords[MAXVERTS][2])
134{
135    double max[2], min[2];
136    if (coords[0][X] > coords[1][X]) {
137        max[0] = coords[0][X];
138        min[0] = coords[1][X];
139    }
140    else {
141        max[0] = coords[1][X];
142        min[0] = coords[0][X];
143    }
144
145    if (coords[0][Y] > coords[1][Y]) {
146        max[1] = coords[0][Y];
147        min[1] = coords[1][Y];
148    }
149    else {
150        max[1] = coords[1][Y];
151        min[1] = coords[0][Y];
152    }
153
154    return ((point[X] >= min[0] && point[X] <= max[0]) &&
155            (point[Y] >= min[1] && point[Y] <= max[1]));
156}
157
158static int pointincircle(const double point[2], double coords[MAXVERTS][2])
159{
160    double radius1, radius2;
161
162    radius1 = ((coords[0][Y] - coords[1][Y]) * (coords[0][Y] - coords[1][Y]))
163        + ((coords[0][X] - coords[1][X]) * (coords[0][X] - coords[1][X]));
164
165    radius2 = ((coords[0][Y] - point[Y]) * (coords[0][Y] - point[Y]))
166        + ((coords[0][X] - point[X]) * (coords[0][X] - point[X]));
167
168    return (radius2 <= radius1);
169}
170
171#define fmin(a,b) (((a)>(b))?(b):(a))
172#define fmax(a,b) (((a)>(b))?(a):(b))
173
174static int pointinpoly(const double point[2], double pgon[MAXVERTS][2])
175{
176    int i, numverts, crossings = 0;
177    double x = point[X], y = point[Y];
178
179    for (numverts = 0; numverts < MAXVERTS && pgon[numverts][X] != -1;
180        numverts++) {
181        /* just counting the vertexes */
182    }
183
184    for (i = 0; i < numverts; i++) {
185        double x1=pgon[i][X];
186        double y1=pgon[i][Y];
187        double x2=pgon[(i + 1) % numverts][X];
188        double y2=pgon[(i + 1) % numverts][Y];
189        double d=(y - y1) * (x2 - x1) - (x - x1) * (y2 - y1);
190
191        if ((y1 >= y) != (y2 >= y)) {
192            crossings +=y2 - y1 >= 0 ? d >= 0 : d <= 0;
193        }
194        if (!d && fmin(x1,x2) <= x && x <= fmax(x1,x2)
195            && fmin(y1,y2) <= y && y <= fmax(y1,y2)) {
196            return 1;
197        }
198    }
199    return crossings & 0x01;
200}
201
202
203static int is_closer(const double point[2], double coords[MAXVERTS][2],
204                     double *closest)
205{
206    double dist_squared = ((point[X] - coords[0][X])
207                           * (point[X] - coords[0][X]))
208                          + ((point[Y] - coords[0][Y])
209                             * (point[Y] - coords[0][Y]));
210
211    if (point[X] < 0 || point[Y] < 0) {
212        return (0);          /* don't mess around with negative coordinates */
213    }
214
215    if (*closest < 0 || dist_squared < *closest) {
216        *closest = dist_squared;
217        return (1);          /* if this is the first point or is the closest yet
218                                set 'closest' equal to this distance^2 */
219    }
220
221    return (0);              /* if it's not the first or closest */
222
223}
224
225static double get_x_coord(const char *args)
226{
227    char *endptr;               /* we want it non-null */
228    double x_coord = -1;        /* -1 is returned if no coordinate is given */
229
230    if (args == NULL) {
231        return (-1);            /* in case we aren't passed anything */
232    }
233
234    while (*args && !apr_isdigit(*args) && *args != ',') {
235        args++;                 /* jump to the first digit, but not past
236                                   a comma or end */
237    }
238
239    x_coord = strtod(args, &endptr);
240
241    if (endptr > args) {        /* if a conversion was made */
242        return (x_coord);
243    }
244
245    return (-1);                /* else if no conversion was made,
246                                   or if no args was given */
247}
248
249static double get_y_coord(const char *args)
250{
251    char *endptr;               /* we want it non-null */
252    const char *start_of_y = NULL;
253    double y_coord = -1;        /* -1 is returned on error */
254
255    if (args == NULL) {
256        return (-1);            /* in case we aren't passed anything */
257    }
258
259    start_of_y = ap_strchr_c(args, ',');     /* the comma */
260
261    if (start_of_y) {
262
263        start_of_y++;           /* start looking at the character after
264                                   the comma */
265
266        while (*start_of_y && !apr_isdigit(*start_of_y)) {
267            start_of_y++;       /* jump to the first digit, but not
268                                   past the end */
269        }
270
271        y_coord = strtod(start_of_y, &endptr);
272
273        if (endptr > start_of_y) {
274            return (y_coord);
275        }
276    }
277
278    return (-1);                /* if no conversion was made, or
279                                   no comma was found in args */
280}
281
282
283/* See if string has a "quoted part", and if so set *quoted_part to
284 * the first character of the quoted part, then hammer a \0 onto the
285 * trailing quote, and set *string to point at the first character
286 * past the second quote.
287 *
288 * Otherwise set *quoted_part to NULL, and leave *string alone.
289 */
290static void read_quoted(char **string, char **quoted_part)
291{
292    char *strp = *string;
293
294    /* assume there's no quoted part */
295    *quoted_part = NULL;
296
297    while (apr_isspace(*strp)) {
298        strp++;                 /* go along string until non-whitespace */
299    }
300
301    if (*strp == '"') {         /* if that character is a double quote */
302        strp++;                 /* step over it */
303        *quoted_part = strp;    /* note where the quoted part begins */
304
305        while (*strp && *strp != '"') {
306            ++strp;             /* skip the quoted portion */
307        }
308
309        *strp = '\0';           /* end the string with a NUL */
310
311        strp++;                 /* step over the last double quote */
312        *string = strp;
313    }
314}
315
316/*
317 * returns the mapped URL or NULL.
318 */
319static const char *imap_url(request_rec *r, const char *base, const char *value)
320{
321/* translates a value into a URL. */
322    int slen, clen;
323    char *string_pos = NULL;
324    const char *string_pos_const = NULL;
325    char *directory = NULL;
326    const char *referer = NULL;
327    char *my_base;
328
329    if (!strcasecmp(value, "map") || !strcasecmp(value, "menu")) {
330        return ap_construct_url(r->pool, r->uri, r);
331    }
332
333    if (!strcasecmp(value, "nocontent") || !strcasecmp(value, "error")) {
334        return apr_pstrdup(r->pool, value);      /* these are handled elsewhere,
335                                                so just copy them */
336    }
337
338    if (!strcasecmp(value, "referer")) {
339        referer = apr_table_get(r->headers_in, "Referer");
340        if (referer && *referer) {
341            return referer;
342        }
343        else {
344            /* XXX:  This used to do *value = '\0'; ... which is totally bogus
345             * because it hammers the passed in value, which can be a string
346             * constant, or part of a config, or whatever.  Total garbage.
347             * This works around that without changing the rest of this
348             * code much
349             */
350            value = "";      /* if 'referer' but no referring page,
351                                null the value */
352        }
353    }
354
355    string_pos_const = value;
356    while (apr_isalpha(*string_pos_const)) {
357        string_pos_const++;           /* go along the URL from the map
358                                         until a non-letter */
359    }
360    if (*string_pos_const == ':') {
361        /* if letters and then a colon (like http:) */
362        /* it's an absolute URL, so use it! */
363        return apr_pstrdup(r->pool, value);
364    }
365
366    if (!base || !*base) {
367        if (value && *value) {
368            return apr_pstrdup(r->pool, value); /* no base: use what is given */
369        }
370        /* no base, no value: pick a simple default */
371        return ap_construct_url(r->pool, "/", r);
372    }
373
374    /* must be a relative URL to be combined with base */
375    if (ap_strchr_c(base, '/') == NULL && (!strncmp(value, "../", 3)
376        || !strcmp(value, ".."))) {
377        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00677)
378                    "invalid base directive in map file: %s", r->uri);
379        return NULL;
380    }
381    my_base = apr_pstrdup(r->pool, base);
382    string_pos = my_base;
383    while (*string_pos) {
384        if (*string_pos == '/' && *(string_pos + 1) == '/') {
385            string_pos += 2;    /* if there are two slashes, jump over them */
386            continue;
387        }
388        if (*string_pos == '/') {       /* the first single slash */
389            if (value[0] == '/') {
390                *string_pos = '\0';
391            }                   /* if the URL from the map starts from root,
392                                   end the base URL string at the first single
393                                   slash */
394            else {
395                directory = string_pos;         /* save the start of
396                                                   the directory portion */
397
398                string_pos = strrchr(string_pos, '/');  /* now reuse
399                                                           string_pos */
400                string_pos++;   /* step over that last slash */
401                *string_pos = '\0';
402            }                   /* but if the map url is relative, leave the
403                                   slash on the base (if there is one) */
404            break;
405        }
406        string_pos++;           /* until we get to the end of my_base without
407                                   finding a slash by itself */
408    }
409
410    while (!strncmp(value, "../", 3) || !strcmp(value, "..")) {
411
412        if (directory && (slen = strlen(directory))) {
413
414            /* for each '..',  knock a directory off the end
415               by ending the string right at the last slash.
416               But only consider the directory portion: don't eat
417               into the server name.  And only try if a directory
418               portion was found */
419
420            clen = slen - 1;
421
422            while ((slen - clen) == 1) {
423
424                if ((string_pos = strrchr(directory, '/'))) {
425                    *string_pos = '\0';
426                }
427                clen = strlen(directory);
428                if (clen == 0) {
429                    break;
430                }
431            }
432
433            value += 2;         /* jump over the '..' that we found in the
434                                   value */
435        }
436        else if (directory) {
437            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00678)
438                        "invalid directory name in map file: %s", r->uri);
439            return NULL;
440        }
441
442        if (!strncmp(value, "/../", 4) || !strcmp(value, "/..")) {
443            value++;            /* step over the '/' if there are more '..'
444                                   to do.  This way, we leave the starting
445                                   '/' on value after the last '..', but get
446                                   rid of it otherwise */
447        }
448
449    }                           /* by this point, value does not start
450                                   with '..' */
451
452    if (value && *value) {
453        return apr_pstrcat(r->pool, my_base, value, NULL);
454    }
455    return my_base;
456}
457
458static int imap_reply(request_rec *r, const char *redirect)
459{
460    if (!strcasecmp(redirect, "error")) {
461        /* they actually requested an error! */
462        return HTTP_INTERNAL_SERVER_ERROR;
463    }
464    if (!strcasecmp(redirect, "nocontent")) {
465        /* tell the client to keep the page it has */
466        return HTTP_NO_CONTENT;
467    }
468    if (redirect && *redirect) {
469        /* must be a URL, so redirect to it */
470        apr_table_setn(r->headers_out, "Location", redirect);
471        return HTTP_MOVED_TEMPORARILY;
472    }
473    return HTTP_INTERNAL_SERVER_ERROR;
474}
475
476static void menu_header(request_rec *r, char *menu)
477{
478    ap_set_content_type(r, "text/html; charset=ISO-8859-1");
479
480    ap_rvputs(r, DOCTYPE_HTML_3_2, "<html><head>\n<title>Menu for ",
481              ap_escape_html(r->pool, r->uri),
482              "</title>\n</head><body>\n", NULL);
483
484    if (!strcasecmp(menu, "formatted")) {
485        ap_rvputs(r, "<h1>Menu for ",
486                  ap_escape_html(r->pool, r->uri),
487                  "</h1>\n<hr />\n\n", NULL);
488    }
489
490    return;
491}
492
493static void menu_blank(request_rec *r, char *menu)
494{
495    if (!strcasecmp(menu, "formatted")) {
496        ap_rputs("\n", r);
497    }
498    else if (!strcasecmp(menu, "semiformatted")) {
499        ap_rputs("<br />\n", r);
500    }
501    else if (!strcasecmp(menu, "unformatted")) {
502        ap_rputs("\n", r);
503    }
504    return;
505}
506
507static void menu_comment(request_rec *r, char *menu, char *comment)
508{
509    if (!strcasecmp(menu, "formatted")) {
510        ap_rputs("\n", r);         /* print just a newline if 'formatted' */
511    }
512    else if (!strcasecmp(menu, "semiformatted") && *comment) {
513        ap_rvputs(r, comment, "\n", NULL);
514    }
515    else if (!strcasecmp(menu, "unformatted") && *comment) {
516        ap_rvputs(r, comment, "\n", NULL);
517    }
518    return;                     /* comments are ignored in the
519                                   'formatted' form */
520}
521
522static void menu_default(request_rec *r, const char *menu, const char *href, const char *text)
523{
524    char *ehref, *etext;
525    if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
526        return;                 /* don't print such lines, these aren't
527                                   really href's */
528    }
529
530    ehref = ap_escape_uri(r->pool, href);
531    etext = ap_escape_html(r->pool, text);
532
533    if (!strcasecmp(menu, "formatted")) {
534        ap_rvputs(r, "<pre>(Default) <a href=\"", ehref, "\">", etext,
535                     "</a></pre>\n", NULL);
536    }
537    else if (!strcasecmp(menu, "semiformatted")) {
538        ap_rvputs(r, "<pre>(Default) <a href=\"", ehref, "\">", etext,
539               "</a></pre>\n", NULL);
540    }
541    else if (!strcasecmp(menu, "unformatted")) {
542        ap_rvputs(r, "<a href=\"", ehref, "\">", etext, "</a>", NULL);
543    }
544    return;
545}
546
547static void menu_directive(request_rec *r, const char *menu, const char *href, const char *text)
548{
549    char *ehref, *etext;
550    if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
551        return;                 /* don't print such lines, as this isn't
552                                   really an href */
553    }
554
555    ehref = ap_escape_uri(r->pool, href);
556    etext = ap_escape_html(r->pool, text);
557
558    if (!strcasecmp(menu, "formatted")) {
559        ap_rvputs(r, "<pre>          <a href=\"", ehref, "\">", etext,
560               "</a></pre>\n", NULL);
561    }
562    else if (!strcasecmp(menu, "semiformatted")) {
563        ap_rvputs(r, "<pre>          <a href=\"", ehref, "\">", etext,
564               "</a></pre>\n", NULL);
565    }
566    else if (!strcasecmp(menu, "unformatted")) {
567        ap_rvputs(r, "<a href=\"", ehref, "\">", etext, "</a>", NULL);
568    }
569    return;
570}
571
572static void menu_footer(request_rec *r)
573{
574    ap_rputs("\n\n</body>\n</html>\n", r);         /* finish the menu */
575}
576
577static int imap_handler_internal(request_rec *r)
578{
579    char input[MAX_STRING_LEN];
580    char *directive;
581    char *value;
582    char *href_text;
583    const char *base;
584    const char *redirect;
585    const char *mapdflt;
586    char *closest = NULL;
587    double closest_yet = -1;
588    apr_status_t status;
589
590    double testpoint[2];
591    double pointarray[MAXVERTS + 1][2];
592    int vertex;
593
594    char *string_pos;
595    int showmenu = 0;
596
597    imap_conf_rec *icr;
598
599    char *imap_menu;
600    char *imap_default;
601    char *imap_base;
602
603    ap_configfile_t *imap;
604
605    icr = ap_get_module_config(r->per_dir_config, &imagemap_module);
606
607    imap_menu = icr->imap_menu ? icr->imap_menu : IMAP_MENU_DEFAULT;
608    imap_default = icr->imap_default
609      ?  icr->imap_default : IMAP_DEFAULT_DEFAULT;
610    imap_base = icr->imap_base ? icr->imap_base : IMAP_BASE_DEFAULT;
611
612    status = ap_pcfg_openfile(&imap, r->pool, r->filename);
613
614    if (status != APR_SUCCESS) {
615        return HTTP_NOT_FOUND;
616    }
617
618    base = imap_url(r, NULL, imap_base);         /* set base according
619                                                    to default */
620    if (!base) {
621        return HTTP_INTERNAL_SERVER_ERROR;
622    }
623    mapdflt = imap_url(r, NULL, imap_default);   /* and default to
624                                                    global default */
625    if (!mapdflt) {
626        return HTTP_INTERNAL_SERVER_ERROR;
627    }
628
629    testpoint[X] = get_x_coord(r->args);
630    testpoint[Y] = get_y_coord(r->args);
631
632    if ((testpoint[X] == -1 || testpoint[Y] == -1) ||
633        (testpoint[X] == 0 && testpoint[Y] == 0)) {
634        /* if either is -1 or if both are zero (new Lynx) */
635        /* we don't have valid coordinates */
636        testpoint[X] = -1;
637        testpoint[Y] = -1;
638        if (strncasecmp(imap_menu, "none", 2)) {
639            showmenu = 1;       /* show the menu _unless_ ImapMenu is
640                                   'none' or 'no' */
641        }
642    }
643
644    if (showmenu) {             /* send start of imagemap menu if
645                                   we're going to */
646        menu_header(r, imap_menu);
647    }
648
649    while (!ap_cfg_getline(input, sizeof(input), imap)) {
650        if (!input[0]) {
651            if (showmenu) {
652                menu_blank(r, imap_menu);
653            }
654            continue;
655        }
656
657        if (input[0] == '#') {
658            if (showmenu) {
659                menu_comment(r, imap_menu, input + 1);
660            }
661            continue;
662        }                       /* blank lines and comments are ignored
663                                   if we aren't printing a menu */
664
665        /* find the first two space delimited fields, recall that
666         * ap_cfg_getline has removed leading/trailing whitespace.
667         *
668         * note that we're tokenizing as we go... if we were to use the
669         * ap_getword() class of functions we would end up allocating extra
670         * memory for every line of the map file
671         */
672        string_pos = input;
673        if (!*string_pos) {   /* need at least two fields */
674            goto need_2_fields;
675        }
676
677        directive = string_pos;
678        while (*string_pos && !apr_isspace(*string_pos)) {   /* past directive */
679            ++string_pos;
680        }
681        if (!*string_pos) {   /* need at least two fields */
682            goto need_2_fields;
683        }
684        *string_pos++ = '\0';
685
686        if (!*string_pos) {   /* need at least two fields */
687            goto need_2_fields;
688        }
689        while (apr_isspace(*string_pos)) { /* past whitespace */
690            ++string_pos;
691        }
692
693        value = string_pos;
694        while (*string_pos && !apr_isspace(*string_pos)) {   /* past value */
695            ++string_pos;
696        }
697        if (apr_isspace(*string_pos)) {
698            *string_pos++ = '\0';
699        }
700        else {
701            /* end of input, don't advance past it */
702            *string_pos = '\0';
703        }
704
705        if (!strncasecmp(directive, "base", 4)) {       /* base, base_uri */
706            base = imap_url(r, NULL, value);
707            if (!base) {
708                goto menu_bail;
709            }
710            continue;           /* base is never printed to a menu */
711        }
712
713        read_quoted(&string_pos, &href_text);
714
715        if (!strcasecmp(directive, "default")) {        /* default */
716            mapdflt = imap_url(r, NULL, value);
717            if (!mapdflt) {
718                goto menu_bail;
719            }
720            if (showmenu) {     /* print the default if there's a menu */
721                redirect = imap_url(r, base, mapdflt);
722                if (!redirect) {
723                    goto menu_bail;
724                }
725                menu_default(r, imap_menu, redirect,
726                             href_text ? href_text : mapdflt);
727            }
728            continue;
729        }
730
731        vertex = 0;
732        while (vertex < MAXVERTS &&
733               sscanf(string_pos, "%lf%*[, ]%lf",
734                      &pointarray[vertex][X], &pointarray[vertex][Y]) == 2) {
735            /* Now skip what we just read... we can't use ANSIism %n */
736            while (apr_isspace(*string_pos)) {      /* past whitespace */
737                string_pos++;
738            }
739            while (apr_isdigit(*string_pos)) {      /* and the 1st number */
740                string_pos++;
741            }
742            string_pos++;       /* skip the ',' */
743            while (apr_isspace(*string_pos)) {      /* past any more whitespace */
744                string_pos++;
745            }
746            while (apr_isdigit(*string_pos)) {      /* 2nd number */
747                string_pos++;
748            }
749            vertex++;
750        }                       /* so long as there are more vertices to
751                                   read, and we have room, read them in.
752                                   We start where we left off of the last
753                                   sscanf, not at the beginning. */
754
755        pointarray[vertex][X] = -1;     /* signals the end of vertices */
756
757        if (showmenu) {
758            if (!href_text) {
759                read_quoted(&string_pos, &href_text);     /* href text could
760                                                             be here instead */
761            }
762            redirect = imap_url(r, base, value);
763            if (!redirect) {
764                goto menu_bail;
765            }
766            menu_directive(r, imap_menu, redirect,
767                           href_text ? href_text : value);
768            continue;
769        }
770        /* note that we don't make it past here if we are making a menu */
771
772        if (testpoint[X] == -1 || pointarray[0][X] == -1) {
773            continue;           /* don't try the following tests if testpoints
774                                   are invalid, or if there are no
775                                   coordinates */
776        }
777
778        if (!strcasecmp(directive, "poly")) {   /* poly */
779
780            if (pointinpoly(testpoint, pointarray)) {
781                ap_cfg_closefile(imap);
782                redirect = imap_url(r, base, value);
783                if (!redirect) {
784                    return HTTP_INTERNAL_SERVER_ERROR;
785                }
786                return (imap_reply(r, redirect));
787            }
788            continue;
789        }
790
791        if (!strcasecmp(directive, "circle")) {         /* circle */
792
793            if (pointincircle(testpoint, pointarray)) {
794                ap_cfg_closefile(imap);
795                redirect = imap_url(r, base, value);
796                if (!redirect) {
797                    return HTTP_INTERNAL_SERVER_ERROR;
798                }
799                return (imap_reply(r, redirect));
800            }
801            continue;
802        }
803
804        if (!strcasecmp(directive, "rect")) {   /* rect */
805
806            if (pointinrect(testpoint, pointarray)) {
807                ap_cfg_closefile(imap);
808                redirect = imap_url(r, base, value);
809                if (!redirect) {
810                    return HTTP_INTERNAL_SERVER_ERROR;
811                }
812                return (imap_reply(r, redirect));
813            }
814            continue;
815        }
816
817        if (!strcasecmp(directive, "point")) {  /* point */
818
819            if (is_closer(testpoint, pointarray, &closest_yet)) {
820                closest = apr_pstrdup(r->pool, value);
821            }
822
823            continue;
824        }                       /* move on to next line whether it's
825                                   closest or not */
826
827    }                           /* nothing matched, so we get another line! */
828
829    ap_cfg_closefile(imap);        /* we are done with the map file; close it */
830
831    if (showmenu) {
832        menu_footer(r);         /* finish the menu and we are done */
833        return OK;
834    }
835
836    if (closest) {             /* if a 'point' directive has been seen */
837        redirect = imap_url(r, base, closest);
838        if (!redirect) {
839            return HTTP_INTERNAL_SERVER_ERROR;
840        }
841        return (imap_reply(r, redirect));
842    }
843
844    if (mapdflt) {             /* a default should be defined, even if
845                                  only 'nocontent' */
846        redirect = imap_url(r, base, mapdflt);
847        if (!redirect) {
848            return HTTP_INTERNAL_SERVER_ERROR;
849        }
850        return (imap_reply(r, redirect));
851    }
852
853    return HTTP_INTERNAL_SERVER_ERROR;        /* If we make it this far,
854                                                 we failed. They lose! */
855
856need_2_fields:
857    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00679)
858                "map file %s, line %d syntax error: requires at "
859                "least two fields", r->uri, imap->line_number);
860    /* fall through */
861menu_bail:
862    ap_cfg_closefile(imap);
863    if (showmenu) {
864        /* There's not much else we can do ... we've already sent the headers
865         * to the client.
866         */
867        ap_rputs("\n\n[an internal server error occured]\n", r);
868        menu_footer(r);
869        return OK;
870    }
871    return HTTP_INTERNAL_SERVER_ERROR;
872}
873
874static int imap_handler(request_rec *r)
875{
876    /* Optimization: skip the allocation of large local variables on the
877     * stack (in imap_handler_internal()) on requests that aren't using
878     * imagemaps
879     */
880    if (r->method_number != M_GET || (strcmp(r->handler,IMAP_MAGIC_TYPE)
881                                      && strcmp(r->handler, "imap-file"))) {
882        return DECLINED;
883    }
884    else {
885        return imap_handler_internal(r);
886    }
887}
888
889static void register_hooks(apr_pool_t *p)
890{
891    ap_hook_handler(imap_handler,NULL,NULL,APR_HOOK_MIDDLE);
892}
893
894AP_DECLARE_MODULE(imagemap) =
895{
896    STANDARD20_MODULE_STUFF,
897    create_imap_dir_config,     /* dir config creater */
898    merge_imap_dir_configs,     /* dir merger --- default is to override */
899    NULL,                       /* server config */
900    NULL,                       /* merge server config */
901    imap_cmds,                  /* command apr_table_t */
902    register_hooks              /* register hooks */
903};
904