]> granicus.if.org Git - apache/blob - modules/mappers/mod_imap.c
Make handlers use hooks.
[apache] / modules / mappers / mod_imap.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  *
54  * Portions of this software are based upon public domain software
55  * originally written at the National Center for Supercomputing Applications,
56  * University of Illinois, Urbana-Champaign.
57  */
58
59 /*
60  * This imagemap module started as a port of the original imagemap.c
61  * written by Rob McCool (11/13/93 robm@ncsa.uiuc.edu).
62  * This version includes the mapping algorithms found in version 1.3
63  * of imagemap.c.
64  *
65  * Contributors to this code include:
66  *
67  * Kevin Hughes, kevinh@pulua.hcc.hawaii.edu
68  *
69  * Eric Haines, erich@eye.com
70  * "macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com
71  *
72  * Randy Terbush, randy@zyzzyva.com
73  * port to Apache module format, "base_uri" and support for relative URLs
74  * 
75  * James H. Cloos, Jr., cloos@jhcloos.com
76  * Added point datatype, using code in NCSA's version 1.8 imagemap.c
77  * program, as distributed with version 1.4.1 of their server.
78  * The point code is originally added by Craig Milo Rogers, Rogers@ISI.Edu
79  *
80  * Nathan Kurz, nate@tripod.com
81  * Rewrite/reorganization.  New handling of default, base and relative URLs.  
82  * New Configuration directives:
83  *    ImapMenu {none, formatted, semiformatted, unformatted}
84  *    ImapDefault {error, nocontent, referer, menu, URL}
85  *    ImapBase {map, referer, URL}
86  * Support for creating non-graphical menu added.  (backwards compatible):
87  *    Old:  directive URL [x,y ...]
88  *    New:  directive URL "Menu text" [x,y ...]
89  *     or:  directive URL x,y ... "Menu text"
90  * Map format and menu concept courtesy Joshua Bell, jsbell@acs.ucalgary.ca.
91  *
92  * Mark Cox, mark@ukweb.com, Allow relative URLs even when no base specified
93  */
94
95 #include "apr.h"
96 #include "apr_strings.h"
97
98 #if APR_HAVE_STDIO_H
99 #include <stdio.h>              /* for sscanf() */
100 #endif
101
102 #include "ap_config.h"
103 #include "httpd.h"
104 #include "http_config.h"
105 #include "http_request.h"
106 #include "http_core.h"
107 #include "http_protocol.h"
108 #include "http_main.h"
109 #include "http_log.h"
110 #include "util_script.h"
111 #ifdef HAVE_STRING_H
112 #include <string.h>
113 #endif
114 #ifdef HAVE_STRINGS_H
115 #include <strings.h>
116 #endif
117
118 #define IMAP_MAGIC_TYPE "application/x-httpd-imap"
119 #define MAXVERTS 100
120 #define X 0
121 #define Y 1
122
123 #define IMAP_MENU_DEFAULT "formatted"
124 #define IMAP_DEFAULT_DEFAULT "nocontent"
125 #define IMAP_BASE_DEFAULT "map"
126
127 #ifdef SUNOS4
128 double strtod();                /* SunOS needed this */
129 #endif
130
131 module AP_MODULE_DECLARE_DATA imap_module;
132
133 typedef struct {
134     char *imap_menu;
135     char *imap_default;
136     char *imap_base;
137 } imap_conf_rec;
138
139 static void *create_imap_dir_config(apr_pool_t *p, char *dummy)
140 {
141     imap_conf_rec *icr =
142     (imap_conf_rec *) apr_palloc(p, sizeof(imap_conf_rec));
143
144     icr->imap_menu = NULL;
145     icr->imap_default = NULL;
146     icr->imap_base = NULL;
147
148     return icr;
149 }
150
151 static void *merge_imap_dir_configs(apr_pool_t *p, void *basev, void *addv)
152 {
153     imap_conf_rec *new = (imap_conf_rec *) apr_pcalloc(p, sizeof(imap_conf_rec));
154     imap_conf_rec *base = (imap_conf_rec *) basev;
155     imap_conf_rec *add = (imap_conf_rec *) addv;
156
157     new->imap_menu = add->imap_menu ? add->imap_menu : base->imap_menu;
158     new->imap_default = add->imap_default ? add->imap_default
159                                           : base->imap_default;
160     new->imap_base = add->imap_base ? add->imap_base : base->imap_base;
161
162     return new;
163 }
164
165
166 static const command_rec imap_cmds[] =
167 {
168     AP_INIT_TAKE1("ImapMenu", ap_set_string_slot,
169                   (void *) XtOffsetOf(imap_conf_rec, imap_menu), OR_INDEXES,
170                   "the type of menu generated: none, formatted, semiformatted, "
171                   "unformatted"),
172     AP_INIT_TAKE1("ImapDefault", ap_set_string_slot,
173                   (void *) XtOffsetOf(imap_conf_rec, imap_default), OR_INDEXES,
174                   "the action taken if no match: error, nocontent, referer, "
175                   "menu, URL"),
176     AP_INIT_TAKE1("ImapBase", ap_set_string_slot,
177                   (void *) XtOffsetOf(imap_conf_rec, imap_base), OR_INDEXES,
178                   "the base for all URL's: map, referer, URL (or start of)"),
179     {NULL}
180 };
181
182 static int pointinrect(const double point[2], double coords[MAXVERTS][2])
183 {
184     double max[2], min[2];
185     if (coords[0][X] > coords[1][X]) {
186         max[0] = coords[0][X];
187         min[0] = coords[1][X];
188     }
189     else {
190         max[0] = coords[1][X];
191         min[0] = coords[0][X];
192     }
193
194     if (coords[0][Y] > coords[1][Y]) {
195         max[1] = coords[0][Y];
196         min[1] = coords[1][Y];
197     }
198     else {
199         max[1] = coords[1][Y];
200         min[1] = coords[0][Y];
201     }
202
203     return ((point[X] >= min[0] && point[X] <= max[0]) &&
204             (point[Y] >= min[1] && point[Y] <= max[1]));
205 }
206
207 static int pointincircle(const double point[2], double coords[MAXVERTS][2])
208 {
209     double radius1, radius2;
210
211     radius1 = ((coords[0][Y] - coords[1][Y]) * (coords[0][Y] - coords[1][Y]))
212         + ((coords[0][X] - coords[1][X]) * (coords[0][X] - coords[1][X]));
213
214     radius2 = ((coords[0][Y] - point[Y]) * (coords[0][Y] - point[Y]))
215         + ((coords[0][X] - point[X]) * (coords[0][X] - point[X]));
216
217     return (radius2 <= radius1);
218 }
219
220 #define fmin(a,b) (((a)>(b))?(b):(a))
221 #define fmax(a,b) (((a)>(b))?(a):(b))
222
223 static int pointinpoly(const double point[2], double pgon[MAXVERTS][2])
224 {
225     int i, numverts, crossings = 0;
226     double x = point[X], y = point[Y];
227
228     for (numverts = 0; pgon[numverts][X] != -1 && numverts < MAXVERTS;
229         numverts++) {
230         /* just counting the vertexes */
231     }
232
233     for (i = 0; i < numverts; i++) {
234         double x1=pgon[i][X];
235         double y1=pgon[i][Y];
236         double x2=pgon[(i + 1) % numverts][X];
237         double y2=pgon[(i + 1) % numverts][Y];
238         double d=(y - y1) * (x2 - x1) - (x - x1) * (y2 - y1);
239
240         if ((y1 >= y) != (y2 >= y)) {
241             crossings +=y2 - y1 >= 0 ? d >= 0 : d <= 0;
242         }
243         if (!d && fmin(x1,x2) <= x && x <= fmax(x1,x2)
244             && fmin(y1,y2) <= y && y <= fmax(y1,y2)) {
245             return 1;
246         }
247     }
248     return crossings & 0x01;
249 }
250
251
252 static int is_closer(const double point[2], double coords[MAXVERTS][2],
253                      double *closest)
254 {
255     double dist_squared = ((point[X] - coords[0][X])
256                            * (point[X] - coords[0][X]))
257                           + ((point[Y] - coords[0][Y])
258                              * (point[Y] - coords[0][Y]));
259
260     if (point[X] < 0 || point[Y] < 0) {
261         return (0);          /* don't mess around with negative coordinates */
262     }
263
264     if (*closest < 0 || dist_squared < *closest) {
265         *closest = dist_squared;
266         return (1);          /* if this is the first point or is the closest yet
267                                 set 'closest' equal to this distance^2 */
268     }
269
270     return (0);              /* if it's not the first or closest */
271
272 }
273
274 static double get_x_coord(const char *args)
275 {
276     char *endptr;               /* we want it non-null */
277     double x_coord = -1;        /* -1 is returned if no coordinate is given */
278
279     if (args == NULL) {
280         return (-1);            /* in case we aren't passed anything */
281     }
282
283     while (*args && !apr_isdigit(*args) && *args != ',') {
284         args++;                 /* jump to the first digit, but not past
285                                    a comma or end */
286     }
287
288     x_coord = strtod(args, &endptr);
289
290     if (endptr > args) {        /* if a conversion was made */
291         return (x_coord);
292     }
293
294     return (-1);                /* else if no conversion was made,
295                                    or if no args was given */
296 }
297
298 static double get_y_coord(const char *args)
299 {
300     char *endptr;               /* we want it non-null */
301     const char *start_of_y = NULL;
302     double y_coord = -1;        /* -1 is returned on error */
303
304     if (args == NULL) {
305         return (-1);            /* in case we aren't passed anything */
306     }
307
308     start_of_y = ap_strchr_c(args, ',');     /* the comma */
309
310     if (start_of_y) {
311
312         start_of_y++;           /* start looking at the character after
313                                    the comma */
314
315         while (*start_of_y && !apr_isdigit(*start_of_y)) {
316             start_of_y++;       /* jump to the first digit, but not
317                                    past the end */
318         }
319
320         y_coord = strtod(start_of_y, &endptr);
321
322         if (endptr > start_of_y) {
323             return (y_coord);
324         }
325     }
326
327     return (-1);                /* if no conversion was made, or
328                                    no comma was found in args */
329 }
330
331
332 /* See if string has a "quoted part", and if so set *quoted_part to
333  * the first character of the quoted part, then hammer a \0 onto the
334  * trailing quote, and set *string to point at the first character
335  * past the second quote.
336  *
337  * Otherwise set *quoted_part to NULL, and leave *string alone.
338  */
339 static void read_quoted(char **string, char **quoted_part)
340 {
341     char *strp = *string;
342
343     /* assume there's no quoted part */
344     *quoted_part = NULL;
345
346     while (apr_isspace(*strp)) {
347         strp++;                 /* go along string until non-whitespace */
348     }
349
350     if (*strp == '"') {         /* if that character is a double quote */
351         strp++;                 /* step over it */
352         *quoted_part = strp;    /* note where the quoted part begins */
353
354         while (*strp && *strp != '"') {
355             ++strp;             /* skip the quoted portion */
356         }
357
358         *strp = '\0';           /* end the string with a NUL */
359
360         strp++;                 /* step over the last double quote */
361         *string = strp;
362     }
363 }
364
365 /*
366  * returns the mapped URL or NULL.
367  */
368 static char *imap_url(request_rec *r, const char *base, const char *value)
369 {
370 /* translates a value into a URL. */
371     int slen, clen;
372     char *string_pos = NULL;
373     const char *string_pos_const = NULL;
374     char *directory = NULL;
375     const char *referer = NULL;
376     char *my_base;
377
378     if (!strcasecmp(value, "map") || !strcasecmp(value, "menu")) {
379         return ap_construct_url(r->pool, r->uri, r);
380     }
381
382     if (!strcasecmp(value, "nocontent") || !strcasecmp(value, "error")) {
383         return apr_pstrdup(r->pool, value);      /* these are handled elsewhere,
384                                                 so just copy them */
385     }
386
387     if (!strcasecmp(value, "referer")) {
388         referer = apr_table_get(r->headers_in, "Referer");
389         if (referer && *referer) {
390             return apr_pstrdup(r->pool, referer);
391         }
392         else {
393             /* XXX:  This used to do *value = '\0'; ... which is totally bogus
394              * because it hammers the passed in value, which can be a string
395              * constant, or part of a config, or whatever.  Total garbage.
396              * This works around that without changing the rest of this
397              * code much
398              */
399             value = "";      /* if 'referer' but no referring page,
400                                 null the value */
401         }
402     }
403
404     string_pos_const = value;
405     while (apr_isalpha(*string_pos_const)) {
406         string_pos_const++;           /* go along the URL from the map
407                                          until a non-letter */
408     }
409     if (*string_pos_const == ':') {
410         /* if letters and then a colon (like http:) */
411         /* it's an absolute URL, so use it! */
412         return apr_pstrdup(r->pool, value);
413     }
414
415     if (!base || !*base) {
416         if (value && *value) {
417             return apr_pstrdup(r->pool, value); /* no base: use what is given */
418         }
419         /* no base, no value: pick a simple default */
420         return ap_construct_url(r->pool, "/", r);
421     }
422
423     /* must be a relative URL to be combined with base */
424     if (ap_strchr_c(base, '/') == NULL && (!strncmp(value, "../", 3)
425         || !strcmp(value, ".."))) {
426         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
427                     "invalid base directive in map file: %s", r->uri);
428         return NULL;
429     }
430     my_base = apr_pstrdup(r->pool, base);
431     string_pos = my_base;
432     while (*string_pos) {
433         if (*string_pos == '/' && *(string_pos + 1) == '/') {
434             string_pos += 2;    /* if there are two slashes, jump over them */
435             continue;
436         }
437         if (*string_pos == '/') {       /* the first single slash */
438             if (value[0] == '/') {
439                 *string_pos = '\0';
440             }                   /* if the URL from the map starts from root,
441                                    end the base URL string at the first single
442                                    slash */
443             else {
444                 directory = string_pos;         /* save the start of
445                                                    the directory portion */
446
447                 string_pos = strrchr(string_pos, '/');  /* now reuse
448                                                            string_pos */
449                 string_pos++;   /* step over that last slash */
450                 *string_pos = '\0';
451             }                   /* but if the map url is relative, leave the
452                                    slash on the base (if there is one) */
453             break;
454         }
455         string_pos++;           /* until we get to the end of my_base without
456                                    finding a slash by itself */
457     }
458
459     while (!strncmp(value, "../", 3) || !strcmp(value, "..")) {
460
461         if (directory && (slen = strlen(directory))) {
462
463             /* for each '..',  knock a directory off the end 
464                by ending the string right at the last slash.
465                But only consider the directory portion: don't eat
466                into the server name.  And only try if a directory
467                portion was found */
468
469             clen = slen - 1;
470
471             while ((slen - clen) == 1) {
472
473                 if ((string_pos = strrchr(directory, '/'))) {
474                     *string_pos = '\0';
475                 }
476                 clen = strlen(directory);
477                 if (clen == 0) {
478                     break;
479                 }
480             }
481
482             value += 2;         /* jump over the '..' that we found in the
483                                    value */
484         }
485         else if (directory) {
486             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
487                         "invalid directory name in map file: %s", r->uri);
488             return NULL;
489         }
490
491         if (!strncmp(value, "/../", 4) || !strcmp(value, "/..")) {
492             value++;            /* step over the '/' if there are more '..'
493                                    to do.  This way, we leave the starting
494                                    '/' on value after the last '..', but get
495                                    rid of it otherwise */
496         }
497
498     }                           /* by this point, value does not start
499                                    with '..' */
500
501     if (value && *value) {
502         return apr_pstrcat(r->pool, my_base, value, NULL);
503     }
504     return my_base;
505 }
506
507 static int imap_reply(request_rec *r, char *redirect)
508 {
509     if (!strcasecmp(redirect, "error")) {
510         /* they actually requested an error! */
511         return HTTP_INTERNAL_SERVER_ERROR;
512     }
513     if (!strcasecmp(redirect, "nocontent")) {
514         /* tell the client to keep the page it has */
515         return HTTP_NO_CONTENT;
516     }
517     if (redirect && *redirect) {
518         /* must be a URL, so redirect to it */
519         apr_table_setn(r->headers_out, "Location", redirect);
520         return HTTP_MOVED_TEMPORARILY;
521     }
522     return HTTP_INTERNAL_SERVER_ERROR;
523 }
524
525 static void menu_header(request_rec *r, char *menu)
526 {
527     r->content_type = "text/html";
528     ap_send_http_header(r);
529
530
531     ap_rvputs(r, DOCTYPE_HTML_3_2, "<html><head>\n<title>Menu for ", r->uri,
532            "</title>\n</head><body>\n", NULL);
533
534     if (!strcasecmp(menu, "formatted")) {
535         ap_rvputs(r, "<h1>Menu for ", r->uri, "</h1>\n<hr>\n\n", NULL);
536     }
537
538     return;
539 }
540
541 static void menu_blank(request_rec *r, char *menu)
542 {
543     if (!strcasecmp(menu, "formatted")) {
544         ap_rputs("\n", r);
545     }
546     if (!strcasecmp(menu, "semiformatted")) {
547         ap_rputs("<br>\n", r);
548     }
549     if (!strcasecmp(menu, "unformatted")) {
550         ap_rputs("\n", r);
551     }
552     return;
553 }
554
555 static void menu_comment(request_rec *r, char *menu, char *comment)
556 {
557     if (!strcasecmp(menu, "formatted")) {
558         ap_rputs("\n", r);         /* print just a newline if 'formatted' */
559     }
560     if (!strcasecmp(menu, "semiformatted") && *comment) {
561         ap_rvputs(r, comment, "\n", NULL);
562     }
563     if (!strcasecmp(menu, "unformatted") && *comment) {
564         ap_rvputs(r, comment, "\n", NULL);
565     }
566     return;                     /* comments are ignored in the
567                                    'formatted' form */
568 }
569
570 static void menu_default(request_rec *r, char *menu, char *href, char *text)
571 {
572     if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
573         return;                 /* don't print such lines, these aren't
574                                    really href's */
575     }
576     if (!strcasecmp(menu, "formatted")) {
577         ap_rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text,
578                "</a></pre>\n", NULL);
579     }
580     if (!strcasecmp(menu, "semiformatted")) {
581         ap_rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text,
582                "</a></pre>\n", NULL);
583     }
584     if (!strcasecmp(menu, "unformatted")) {
585         ap_rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL);
586     }
587     return;
588 }
589
590 static void menu_directive(request_rec *r, char *menu, char *href, char *text)
591 {
592     if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
593         return;                 /* don't print such lines, as this isn't
594                                    really an href */
595     }
596     if (!strcasecmp(menu, "formatted")) {
597         ap_rvputs(r, "<pre>          <a href=\"", href, "\">", text,
598                "</a></pre>\n", NULL);
599     }
600     if (!strcasecmp(menu, "semiformatted")) {
601         ap_rvputs(r, "<pre>          <a href=\"", href, "\">", text,
602                "</a></pre>\n", NULL);
603     }
604     if (!strcasecmp(menu, "unformatted")) {
605         ap_rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL);
606     }
607     return;
608 }
609
610 static void menu_footer(request_rec *r)
611 {
612     ap_rputs("\n\n</body>\n</html>\n", r);         /* finish the menu */
613 }
614
615 static int imap_handler(const char *handler,request_rec *r)
616 {
617     char input[MAX_STRING_LEN];
618     char *directive;
619     char *value;
620     char *href_text;
621     char *base;
622     char *redirect;
623     char *mapdflt;
624     char *closest = NULL;
625     double closest_yet = -1;
626     apr_status_t status;
627
628     double testpoint[2];
629     double pointarray[MAXVERTS + 1][2];
630     int vertex;
631
632     char *string_pos;
633     int showmenu = 0;
634
635     imap_conf_rec *icr;
636
637     char *imap_menu;
638     char *imap_default;
639     char *imap_base;
640
641     configfile_t *imap; 
642
643     if (r->method_number != M_GET || (strcmp(handler,IMAP_MAGIC_TYPE)
644                                       && strcmp(handler, "imap-file")))
645         return DECLINED;
646
647     icr = ap_get_module_config(r->per_dir_config, &imap_module);
648
649     imap_menu = icr->imap_menu ? icr->imap_menu : IMAP_MENU_DEFAULT;
650     imap_default = icr->imap_default
651       ?  icr->imap_default : IMAP_DEFAULT_DEFAULT;
652     imap_base = icr->imap_base ? icr->imap_base : IMAP_BASE_DEFAULT;
653
654     status = ap_pcfg_openfile(&imap, r->pool, r->filename);
655
656     if (status != APR_SUCCESS) {
657         return HTTP_NOT_FOUND;
658     }
659
660     base = imap_url(r, NULL, imap_base);         /* set base according
661                                                     to default */
662     if (!base) {
663         return HTTP_INTERNAL_SERVER_ERROR;
664     }
665     mapdflt = imap_url(r, NULL, imap_default);   /* and default to
666                                                     global default */
667     if (!mapdflt) {
668         return HTTP_INTERNAL_SERVER_ERROR;
669     }
670
671     testpoint[X] = get_x_coord(r->args);
672     testpoint[Y] = get_y_coord(r->args);
673
674     if ((testpoint[X] == -1 || testpoint[Y] == -1) ||
675         (testpoint[X] == 0 && testpoint[Y] == 0)) {
676         /* if either is -1 or if both are zero (new Lynx) */
677         /* we don't have valid coordinates */
678         testpoint[X] = -1;
679         testpoint[Y] = -1;
680         if (strncasecmp(imap_menu, "none", 2)) {
681             showmenu = 1;       /* show the menu _unless_ ImapMenu is
682                                    'none' or 'no' */
683         }
684     }
685
686     if (showmenu) {             /* send start of imagemap menu if
687                                    we're going to */
688         menu_header(r, imap_menu);
689     }
690
691     while (!ap_cfg_getline(input, sizeof(input), imap)) {
692         if (!input[0]) {
693             if (showmenu) {
694                 menu_blank(r, imap_menu);
695             }
696             continue;
697         }
698
699         if (input[0] == '#') {
700             if (showmenu) {
701                 menu_comment(r, imap_menu, input + 1);
702             }
703             continue;
704         }                       /* blank lines and comments are ignored
705                                    if we aren't printing a menu */
706
707         /* find the first two space delimited fields, recall that
708          * ap_cfg_getline has removed leading/trailing whitespace.
709          *
710          * note that we're tokenizing as we go... if we were to use the
711          * ap_getword() class of functions we would end up allocating extra
712          * memory for every line of the map file
713          */
714         string_pos = input;
715         if (!*string_pos) {             /* need at least two fields */
716             goto need_2_fields;
717         }
718
719         directive = string_pos;
720         while (*string_pos && !apr_isspace(*string_pos)) {      /* past directive */
721             ++string_pos;
722         }
723         if (!*string_pos) {             /* need at least two fields */
724             goto need_2_fields;
725         }
726         *string_pos++ = '\0';
727
728         if (!*string_pos) {             /* need at least two fields */
729             goto need_2_fields;
730         }
731         while(*string_pos && apr_isspace(*string_pos)) { /* past whitespace */
732             ++string_pos;
733         }
734
735         value = string_pos;
736         while (*string_pos && !apr_isspace(*string_pos)) {      /* past value */
737             ++string_pos;
738         }
739         if (apr_isspace(*string_pos)) {
740             *string_pos++ = '\0';
741         }
742         else {
743             /* end of input, don't advance past it */
744             *string_pos = '\0';
745         }
746
747         if (!strncasecmp(directive, "base", 4)) {       /* base, base_uri */
748             base = imap_url(r, NULL, value);
749             if (!base) {
750                 goto menu_bail;
751             }
752             continue;           /* base is never printed to a menu */
753         }
754
755         read_quoted(&string_pos, &href_text);
756
757         if (!strcasecmp(directive, "default")) {        /* default */
758             mapdflt = imap_url(r, NULL, value);
759             if (!mapdflt) {
760                 goto menu_bail;
761             }
762             if (showmenu) {     /* print the default if there's a menu */
763                 redirect = imap_url(r, base, mapdflt);
764                 if (!redirect) {
765                     goto menu_bail;
766                 }
767                 menu_default(r, imap_menu, redirect,
768                              href_text ? href_text : mapdflt);
769             }
770             continue;
771         }
772
773         vertex = 0;
774         while (vertex < MAXVERTS &&
775                sscanf(string_pos, "%lf%*[, ]%lf",
776                       &pointarray[vertex][X], &pointarray[vertex][Y]) == 2) {
777             /* Now skip what we just read... we can't use ANSIism %n */
778             while (apr_isspace(*string_pos)) {      /* past whitespace */
779                 string_pos++;
780             }
781             while (apr_isdigit(*string_pos)) {      /* and the 1st number */
782                 string_pos++;
783             }
784             string_pos++;       /* skip the ',' */
785             while (apr_isspace(*string_pos)) {      /* past any more whitespace */
786                 string_pos++;
787             }
788             while (apr_isdigit(*string_pos)) {      /* 2nd number */
789                 string_pos++;
790             }
791             vertex++;
792         }                       /* so long as there are more vertices to
793                                    read, and we have room, read them in.
794                                    We start where we left off of the last
795                                    sscanf, not at the beginning. */
796
797         pointarray[vertex][X] = -1;     /* signals the end of vertices */
798
799         if (showmenu) {
800             if (!href_text) {
801                 read_quoted(&string_pos, &href_text);     /* href text could
802                                                              be here instead */
803             }
804             redirect = imap_url(r, base, value);
805             if (!redirect) {
806                 goto menu_bail;
807             }
808             menu_directive(r, imap_menu, redirect,
809                            href_text ? href_text : value);
810             continue;
811         }
812         /* note that we don't make it past here if we are making a menu */
813
814         if (testpoint[X] == -1 || pointarray[0][X] == -1) {
815             continue;           /* don't try the following tests if testpoints
816                                    are invalid, or if there are no
817                                    coordinates */
818         }
819
820         if (!strcasecmp(directive, "poly")) {   /* poly */
821
822             if (pointinpoly(testpoint, pointarray)) {
823                 ap_cfg_closefile(imap);
824                 redirect = imap_url(r, base, value);
825                 if (!redirect) {
826                     return HTTP_INTERNAL_SERVER_ERROR;
827                 }
828                 return (imap_reply(r, redirect));
829             }
830             continue;
831         }
832
833         if (!strcasecmp(directive, "circle")) {         /* circle */
834
835             if (pointincircle(testpoint, pointarray)) {
836                 ap_cfg_closefile(imap);
837                 redirect = imap_url(r, base, value);
838                 if (!redirect) {
839                     return HTTP_INTERNAL_SERVER_ERROR;
840                 }
841                 return (imap_reply(r, redirect));
842             }
843             continue;
844         }
845
846         if (!strcasecmp(directive, "rect")) {   /* rect */
847
848             if (pointinrect(testpoint, pointarray)) {
849                 ap_cfg_closefile(imap);
850                 redirect = imap_url(r, base, value);
851                 if (!redirect) {
852                     return HTTP_INTERNAL_SERVER_ERROR;
853                 }
854                 return (imap_reply(r, redirect));
855             }
856             continue;
857         }
858
859         if (!strcasecmp(directive, "point")) {  /* point */
860
861             if (is_closer(testpoint, pointarray, &closest_yet)) {
862                 closest = apr_pstrdup(r->pool, value);
863             }
864
865             continue;
866         }                       /* move on to next line whether it's
867                                    closest or not */
868
869     }                           /* nothing matched, so we get another line! */
870
871     ap_cfg_closefile(imap);        /* we are done with the map file; close it */
872
873     if (showmenu) {
874         menu_footer(r);         /* finish the menu and we are done */
875         return OK;
876     }
877
878     if (closest) {             /* if a 'point' directive has been seen */
879         redirect = imap_url(r, base, closest);
880         if (!redirect) {
881             return HTTP_INTERNAL_SERVER_ERROR;
882         }
883         return (imap_reply(r, redirect));
884     }
885
886     if (mapdflt) {             /* a default should be defined, even if
887                                   only 'nocontent' */
888         redirect = imap_url(r, base, mapdflt);
889         if (!redirect) {
890             return HTTP_INTERNAL_SERVER_ERROR;
891         }
892         return (imap_reply(r, redirect));
893     }
894
895     return HTTP_INTERNAL_SERVER_ERROR;        /* If we make it this far,
896                                                  we failed. They lose! */
897
898 need_2_fields:
899     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
900                 "map file %s, line %d syntax error: requires at "
901                 "least two fields", r->uri, imap->line_number);
902     /* fall through */
903 menu_bail:
904     ap_cfg_closefile(imap);
905     if (showmenu) {
906         /* There's not much else we can do ... we've already sent the headers
907          * to the client.
908          */
909         ap_rputs("\n\n[an internal server error occured]\n", r);
910         menu_footer(r);
911         return OK;
912     }
913     return HTTP_INTERNAL_SERVER_ERROR;
914 }
915
916 static void register_hooks(void)
917 {
918     ap_hook_handler(imap_handler,NULL,NULL,AP_HOOK_MIDDLE);
919 }
920
921 module AP_MODULE_DECLARE_DATA imap_module =
922 {
923     STANDARD20_MODULE_STUFF,
924     create_imap_dir_config,     /* dir config creater */
925     merge_imap_dir_configs,     /* dir merger --- default is to override */
926     NULL,                       /* server config */
927     NULL,                       /* merge server config */
928     imap_cmds,                  /* command apr_table_t */
929     register_hooks              /* register hooks */
930 };