1 /* ====================================================================
2 * Copyright (c) 1995-1999 The Apache Group. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
16 * 3. All advertising materials mentioning features or use of this
17 * software must display the following acknowledgment:
18 * "This product includes software developed by the Apache Group
19 * for use in the Apache HTTP server project (http://www.apache.org/)."
21 * 4. The names "Apache Server" and "Apache Group" must not be used to
22 * endorse or promote products derived from this software without
23 * prior written permission. For written permission, please contact
26 * 5. Products derived from this software may not be called "Apache"
27 * nor may "Apache" appear in their names without prior written
28 * permission of the Apache Group.
30 * 6. Redistributions of any form whatsoever must retain the following
32 * "This product includes software developed by the Apache Group
33 * for use in the Apache HTTP server project (http://www.apache.org/)."
35 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
36 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
37 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
38 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
39 * IT'S CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
46 * OF THE POSSIBILITY OF SUCH DAMAGE.
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Group and was originally based
51 * on public domain software written at the National Center for
52 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
53 * For more information on the Apache Group and the Apache HTTP server
54 * project, please see <http://www.apache.org/>.
59 * This imagemap module started as a port of the original imagemap.c
60 * written by Rob McCool (11/13/93 robm@ncsa.uiuc.edu).
61 * This version includes the mapping algorithms found in version 1.3
64 * Contributors to this code include:
66 * Kevin Hughes, kevinh@pulua.hcc.hawaii.edu
68 * Eric Haines, erich@eye.com
69 * "macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com
71 * Randy Terbush, randy@zyzzyva.com
72 * port to Apache module format, "base_uri" and support for relative URLs
74 * James H. Cloos, Jr., cloos@jhcloos.com
75 * Added point datatype, using code in NCSA's version 1.8 imagemap.c
76 * program, as distributed with version 1.4.1 of their server.
77 * The point code is originally added by Craig Milo Rogers, Rogers@ISI.Edu
79 * Nathan Kurz, nate@tripod.com
80 * Rewrite/reorganization. New handling of default, base and relative URLs.
81 * New Configuration directives:
82 * ImapMenu {none, formatted, semiformatted, unformatted}
83 * ImapDefault {error, nocontent, referer, menu, URL}
84 * ImapBase {map, referer, URL}
85 * Support for creating non-graphical menu added. (backwards compatible):
86 * Old: directive URL [x,y ...]
87 * New: directive URL "Menu text" [x,y ...]
88 * or: directive URL x,y ... "Menu text"
89 * Map format and menu concept courtesy Joshua Bell, jsbell@acs.ucalgary.ca.
91 * Mark Cox, mark@ukweb.com, Allow relative URLs even when no base specified
95 #include "http_config.h"
96 #include "http_request.h"
97 #include "http_core.h"
98 #include "http_protocol.h"
99 #include "http_main.h"
100 #include "http_log.h"
101 #include "util_script.h"
104 #define IMAP_MAGIC_TYPE "application/x-httpd-imap"
109 #define IMAP_MENU_DEFAULT "formatted"
110 #define IMAP_DEFAULT_DEFAULT "nocontent"
111 #define IMAP_BASE_DEFAULT "map"
114 double strtod(); /* SunOS needed this */
117 module MODULE_VAR_EXPORT imap_module;
125 static void *create_imap_dir_config(ap_context_t *p, char *dummy)
128 (imap_conf_rec *) ap_palloc(p, sizeof(imap_conf_rec));
130 icr->imap_menu = NULL;
131 icr->imap_default = NULL;
132 icr->imap_base = NULL;
137 static void *merge_imap_dir_configs(ap_context_t *p, void *basev, void *addv)
139 imap_conf_rec *new = (imap_conf_rec *) ap_pcalloc(p, sizeof(imap_conf_rec));
140 imap_conf_rec *base = (imap_conf_rec *) basev;
141 imap_conf_rec *add = (imap_conf_rec *) addv;
143 new->imap_menu = add->imap_menu ? add->imap_menu : base->imap_menu;
144 new->imap_default = add->imap_default ? add->imap_default
145 : base->imap_default;
146 new->imap_base = add->imap_base ? add->imap_base : base->imap_base;
152 static const command_rec imap_cmds[] =
154 {"ImapMenu", ap_set_string_slot,
155 (void *) XtOffsetOf(imap_conf_rec, imap_menu), OR_INDEXES, TAKE1,
156 "the type of menu generated: none, formatted, semiformatted, unformatted"},
157 {"ImapDefault", ap_set_string_slot,
158 (void *) XtOffsetOf(imap_conf_rec, imap_default), OR_INDEXES, TAKE1,
159 "the action taken if no match: error, nocontent, referer, menu, URL"},
160 {"ImapBase", ap_set_string_slot,
161 (void *) XtOffsetOf(imap_conf_rec, imap_base), OR_INDEXES, TAKE1,
162 "the base for all URL's: map, referer, URL (or start of)"},
166 static int pointinrect(const double point[2], double coords[MAXVERTS][2])
168 double max[2], min[2];
169 if (coords[0][X] > coords[1][X]) {
170 max[0] = coords[0][X];
171 min[0] = coords[1][X];
174 max[0] = coords[1][X];
175 min[0] = coords[0][X];
178 if (coords[0][Y] > coords[1][Y]) {
179 max[1] = coords[0][Y];
180 min[1] = coords[1][Y];
183 max[1] = coords[1][Y];
184 min[1] = coords[0][Y];
187 return ((point[X] >= min[0] && point[X] <= max[0]) &&
188 (point[Y] >= min[1] && point[Y] <= max[1]));
191 static int pointincircle(const double point[2], double coords[MAXVERTS][2])
193 double radius1, radius2;
195 radius1 = ((coords[0][Y] - coords[1][Y]) * (coords[0][Y] - coords[1][Y]))
196 + ((coords[0][X] - coords[1][X]) * (coords[0][X] - coords[1][X]));
198 radius2 = ((coords[0][Y] - point[Y]) * (coords[0][Y] - point[Y]))
199 + ((coords[0][X] - point[X]) * (coords[0][X] - point[X]));
201 return (radius2 <= radius1);
204 #define fmin(a,b) (((a)>(b))?(b):(a))
205 #define fmax(a,b) (((a)>(b))?(a):(b))
207 static int pointinpoly(const double point[2], double pgon[MAXVERTS][2])
209 int i, numverts, crossings = 0;
210 double x = point[X], y = point[Y];
212 for (numverts = 0; pgon[numverts][X] != -1 && numverts < MAXVERTS;
214 /* just counting the vertexes */
217 for (i = 0; i < numverts; i++) {
218 double x1=pgon[i][X];
219 double y1=pgon[i][Y];
220 double x2=pgon[(i + 1) % numverts][X];
221 double y2=pgon[(i + 1) % numverts][Y];
222 double d=(y - y1) * (x2 - x1) - (x - x1) * (y2 - y1);
224 if ((y1 >= y) != (y2 >= y)) {
225 crossings +=y2 - y1 >= 0 ? d >= 0 : d <= 0;
227 if (!d && fmin(x1,x2) <= x && x <= fmax(x1,x2)
228 && fmin(y1,y2) <= y && y <= fmax(y1,y2)) {
232 return crossings & 0x01;
236 static int is_closer(const double point[2], double coords[MAXVERTS][2],
239 double dist_squared = ((point[X] - coords[0][X])
240 * (point[X] - coords[0][X]))
241 + ((point[Y] - coords[0][Y])
242 * (point[Y] - coords[0][Y]));
244 if (point[X] < 0 || point[Y] < 0) {
245 return (0); /* don't mess around with negative coordinates */
248 if (*closest < 0 || dist_squared < *closest) {
249 *closest = dist_squared;
250 return (1); /* if this is the first point or is the closest yet
251 set 'closest' equal to this distance^2 */
254 return (0); /* if it's not the first or closest */
258 static double get_x_coord(const char *args)
260 char *endptr; /* we want it non-null */
261 double x_coord = -1; /* -1 is returned if no coordinate is given */
264 return (-1); /* in case we aren't passed anything */
267 while (*args && !ap_isdigit(*args) && *args != ',') {
268 args++; /* jump to the first digit, but not past
272 x_coord = strtod(args, &endptr);
274 if (endptr > args) { /* if a conversion was made */
278 return (-1); /* else if no conversion was made,
279 or if no args was given */
282 static double get_y_coord(const char *args)
284 char *endptr; /* we want it non-null */
285 char *start_of_y = NULL;
286 double y_coord = -1; /* -1 is returned on error */
289 return (-1); /* in case we aren't passed anything */
292 start_of_y = strchr(args, ','); /* the comma */
296 start_of_y++; /* start looking at the character after
299 while (*start_of_y && !ap_isdigit(*start_of_y)) {
300 start_of_y++; /* jump to the first digit, but not
304 y_coord = strtod(start_of_y, &endptr);
306 if (endptr > start_of_y) {
311 return (-1); /* if no conversion was made, or
312 no comma was found in args */
316 /* See if string has a "quoted part", and if so set *quoted_part to
317 * the first character of the quoted part, then hammer a \0 onto the
318 * trailing quote, and set *string to point at the first character
319 * past the second quote.
321 * Otherwise set *quoted_part to NULL, and leave *string alone.
323 static void read_quoted(char **string, char **quoted_part)
325 char *strp = *string;
327 /* assume there's no quoted part */
330 while (ap_isspace(*strp)) {
331 strp++; /* go along string until non-whitespace */
334 if (*strp == '"') { /* if that character is a double quote */
335 strp++; /* step over it */
336 *quoted_part = strp; /* note where the quoted part begins */
338 while (*strp && *strp != '"') {
339 ++strp; /* skip the quoted portion */
342 *strp = '\0'; /* end the string with a NUL */
344 strp++; /* step over the last double quote */
350 * returns the mapped URL or NULL.
352 static char *imap_url(request_rec *r, const char *base, const char *value)
354 /* translates a value into a URL. */
356 char *string_pos = NULL;
357 const char *string_pos_const = NULL;
358 char *directory = NULL;
359 const char *referer = NULL;
362 if (!strcasecmp(value, "map") || !strcasecmp(value, "menu")) {
363 return ap_construct_url(r->pool, r->uri, r);
366 if (!strcasecmp(value, "nocontent") || !strcasecmp(value, "error")) {
367 return ap_pstrdup(r->pool, value); /* these are handled elsewhere,
371 if (!strcasecmp(value, "referer")) {
372 referer = ap_table_get(r->headers_in, "Referer");
373 if (referer && *referer) {
374 return ap_pstrdup(r->pool, referer);
377 /* XXX: This used to do *value = '\0'; ... which is totally bogus
378 * because it hammers the passed in value, which can be a string
379 * constant, or part of a config, or whatever. Total garbage.
380 * This works around that without changing the rest of this
383 value = ""; /* if 'referer' but no referring page,
388 string_pos_const = value;
389 while (ap_isalpha(*string_pos_const)) {
390 string_pos_const++; /* go along the URL from the map
391 until a non-letter */
393 if (*string_pos_const == ':') {
394 /* if letters and then a colon (like http:) */
395 /* it's an absolute URL, so use it! */
396 return ap_pstrdup(r->pool, value);
399 if (!base || !*base) {
400 if (value && *value) {
401 return ap_pstrdup(r->pool, value); /* no base: use what is given */
403 /* no base, no value: pick a simple default */
404 return ap_construct_url(r->pool, "/", r);
407 /* must be a relative URL to be combined with base */
408 if (strchr(base, '/') == NULL && (!strncmp(value, "../", 3)
409 || !strcmp(value, ".."))) {
410 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
411 "invalid base directive in map file: %s", r->uri);
414 my_base = ap_pstrdup(r->pool, base);
415 string_pos = my_base;
416 while (*string_pos) {
417 if (*string_pos == '/' && *(string_pos + 1) == '/') {
418 string_pos += 2; /* if there are two slashes, jump over them */
421 if (*string_pos == '/') { /* the first single slash */
422 if (value[0] == '/') {
424 } /* if the URL from the map starts from root,
425 end the base URL string at the first single
428 directory = string_pos; /* save the start of
429 the directory portion */
431 string_pos = strrchr(string_pos, '/'); /* now reuse
433 string_pos++; /* step over that last slash */
435 } /* but if the map url is relative, leave the
436 slash on the base (if there is one) */
439 string_pos++; /* until we get to the end of my_base without
440 finding a slash by itself */
443 while (!strncmp(value, "../", 3) || !strcmp(value, "..")) {
445 if (directory && (slen = strlen(directory))) {
447 /* for each '..', knock a directory off the end
448 by ending the string right at the last slash.
449 But only consider the directory portion: don't eat
450 into the server name. And only try if a directory
455 while ((slen - clen) == 1) {
457 if ((string_pos = strrchr(directory, '/'))) {
460 clen = strlen(directory);
466 value += 2; /* jump over the '..' that we found in the
469 else if (directory) {
470 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
471 "invalid directory name in map file: %s", r->uri);
475 if (!strncmp(value, "/../", 4) || !strcmp(value, "/..")) {
476 value++; /* step over the '/' if there are more '..'
477 to do. This way, we leave the starting
478 '/' on value after the last '..', but get
479 rid of it otherwise */
482 } /* by this point, value does not start
485 if (value && *value) {
486 return ap_pstrcat(r->pool, my_base, value, NULL);
491 static int imap_reply(request_rec *r, char *redirect)
493 if (!strcasecmp(redirect, "error")) {
494 return SERVER_ERROR; /* they actually requested an error! */
496 if (!strcasecmp(redirect, "nocontent")) {
497 return HTTP_NO_CONTENT; /* tell the client to keep the page it has */
499 if (redirect && *redirect) {
500 ap_table_setn(r->headers_out, "Location", redirect);
501 return REDIRECT; /* must be a URL, so redirect to it */
506 static void menu_header(request_rec *r, char *menu)
508 r->content_type = "text/html";
509 ap_send_http_header(r);
512 ap_rvputs(r, DOCTYPE_HTML_3_2, "<html><head>\n<title>Menu for ", r->uri,
513 "</title>\n</head><body>\n", NULL);
515 if (!strcasecmp(menu, "formatted")) {
516 ap_rvputs(r, "<h1>Menu for ", r->uri, "</h1>\n<hr>\n\n", NULL);
522 static void menu_blank(request_rec *r, char *menu)
524 if (!strcasecmp(menu, "formatted")) {
527 if (!strcasecmp(menu, "semiformatted")) {
528 ap_rputs("<br>\n", r);
530 if (!strcasecmp(menu, "unformatted")) {
536 static void menu_comment(request_rec *r, char *menu, char *comment)
538 if (!strcasecmp(menu, "formatted")) {
539 ap_rputs("\n", r); /* print just a newline if 'formatted' */
541 if (!strcasecmp(menu, "semiformatted") && *comment) {
542 ap_rvputs(r, comment, "\n", NULL);
544 if (!strcasecmp(menu, "unformatted") && *comment) {
545 ap_rvputs(r, comment, "\n", NULL);
547 return; /* comments are ignored in the
551 static void menu_default(request_rec *r, char *menu, char *href, char *text)
553 if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
554 return; /* don't print such lines, these aren't
557 if (!strcasecmp(menu, "formatted")) {
558 ap_rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text,
559 "</a></pre>\n", NULL);
561 if (!strcasecmp(menu, "semiformatted")) {
562 ap_rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text,
563 "</a></pre>\n", NULL);
565 if (!strcasecmp(menu, "unformatted")) {
566 ap_rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL);
571 static void menu_directive(request_rec *r, char *menu, char *href, char *text)
573 if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
574 return; /* don't print such lines, as this isn't
577 if (!strcasecmp(menu, "formatted")) {
578 ap_rvputs(r, "<pre> <a href=\"", href, "\">", text,
579 "</a></pre>\n", NULL);
581 if (!strcasecmp(menu, "semiformatted")) {
582 ap_rvputs(r, "<pre> <a href=\"", href, "\">", text,
583 "</a></pre>\n", NULL);
585 if (!strcasecmp(menu, "unformatted")) {
586 ap_rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL);
591 static void menu_footer(request_rec *r)
593 ap_rputs("\n\n</body>\n</html>\n", r); /* finish the menu */
596 static int imap_handler(request_rec *r)
598 char input[MAX_STRING_LEN];
605 char *closest = NULL;
606 double closest_yet = -1;
610 double pointarray[MAXVERTS + 1][2];
616 imap_conf_rec *icr = ap_get_module_config(r->per_dir_config, &imap_module);
618 char *imap_menu = icr->imap_menu ? icr->imap_menu : IMAP_MENU_DEFAULT;
619 char *imap_default = icr->imap_default
620 ? icr->imap_default : IMAP_DEFAULT_DEFAULT;
621 char *imap_base = icr->imap_base ? icr->imap_base : IMAP_BASE_DEFAULT;
625 if (r->method_number != M_GET) {
629 status = ap_pcfg_openfile(&imap, r->pool, r->filename);
631 if (status != APR_SUCCESS) {
635 base = imap_url(r, NULL, imap_base); /* set base according
638 return HTTP_INTERNAL_SERVER_ERROR;
640 mapdflt = imap_url(r, NULL, imap_default); /* and default to
643 return HTTP_INTERNAL_SERVER_ERROR;
646 testpoint[X] = get_x_coord(r->args);
647 testpoint[Y] = get_y_coord(r->args);
649 if ((testpoint[X] == -1 || testpoint[Y] == -1) ||
650 (testpoint[X] == 0 && testpoint[Y] == 0)) {
651 /* if either is -1 or if both are zero (new Lynx) */
652 /* we don't have valid coordinates */
655 if (strncasecmp(imap_menu, "none", 2)) {
656 showmenu = 1; /* show the menu _unless_ ImapMenu is
661 if (showmenu) { /* send start of imagemap menu if
663 menu_header(r, imap_menu);
666 while (!ap_cfg_getline(input, sizeof(input), imap)) {
669 menu_blank(r, imap_menu);
674 if (input[0] == '#') {
676 menu_comment(r, imap_menu, input + 1);
679 } /* blank lines and comments are ignored
680 if we aren't printing a menu */
682 /* find the first two space delimited fields, recall that
683 * ap_cfg_getline has removed leading/trailing whitespace.
685 * note that we're tokenizing as we go... if we were to use the
686 * ap_getword() class of functions we would end up allocating extra
687 * memory for every line of the map file
690 if (!*string_pos) { /* need at least two fields */
694 directive = string_pos;
695 while (*string_pos && !ap_isspace(*string_pos)) { /* past directive */
698 if (!*string_pos) { /* need at least two fields */
701 *string_pos++ = '\0';
703 if (!*string_pos) { /* need at least two fields */
706 while(*string_pos && ap_isspace(*string_pos)) { /* past whitespace */
711 while (*string_pos && !ap_isspace(*string_pos)) { /* past value */
714 if (ap_isspace(*string_pos)) {
715 *string_pos++ = '\0';
718 /* end of input, don't advance past it */
722 if (!strncasecmp(directive, "base", 4)) { /* base, base_uri */
723 base = imap_url(r, NULL, value);
727 continue; /* base is never printed to a menu */
730 read_quoted(&string_pos, &href_text);
732 if (!strcasecmp(directive, "default")) { /* default */
733 mapdflt = imap_url(r, NULL, value);
737 if (showmenu) { /* print the default if there's a menu */
738 redirect = imap_url(r, base, mapdflt);
742 menu_default(r, imap_menu, redirect,
743 href_text ? href_text : mapdflt);
749 while (vertex < MAXVERTS &&
750 sscanf(string_pos, "%lf%*[, ]%lf",
751 &pointarray[vertex][X], &pointarray[vertex][Y]) == 2) {
752 /* Now skip what we just read... we can't use ANSIism %n */
753 while (ap_isspace(*string_pos)) { /* past whitespace */
756 while (ap_isdigit(*string_pos)) { /* and the 1st number */
759 string_pos++; /* skip the ',' */
760 while (ap_isspace(*string_pos)) { /* past any more whitespace */
763 while (ap_isdigit(*string_pos)) { /* 2nd number */
767 } /* so long as there are more vertices to
768 read, and we have room, read them in.
769 We start where we left off of the last
770 sscanf, not at the beginning. */
772 pointarray[vertex][X] = -1; /* signals the end of vertices */
776 read_quoted(&string_pos, &href_text); /* href text could
779 redirect = imap_url(r, base, value);
783 menu_directive(r, imap_menu, redirect,
784 href_text ? href_text : value);
787 /* note that we don't make it past here if we are making a menu */
789 if (testpoint[X] == -1 || pointarray[0][X] == -1) {
790 continue; /* don't try the following tests if testpoints
791 are invalid, or if there are no
795 if (!strcasecmp(directive, "poly")) { /* poly */
797 if (pointinpoly(testpoint, pointarray)) {
798 ap_cfg_closefile(imap);
799 redirect = imap_url(r, base, value);
801 return HTTP_INTERNAL_SERVER_ERROR;
803 return (imap_reply(r, redirect));
808 if (!strcasecmp(directive, "circle")) { /* circle */
810 if (pointincircle(testpoint, pointarray)) {
811 ap_cfg_closefile(imap);
812 redirect = imap_url(r, base, value);
814 return HTTP_INTERNAL_SERVER_ERROR;
816 return (imap_reply(r, redirect));
821 if (!strcasecmp(directive, "rect")) { /* rect */
823 if (pointinrect(testpoint, pointarray)) {
824 ap_cfg_closefile(imap);
825 redirect = imap_url(r, base, value);
827 return HTTP_INTERNAL_SERVER_ERROR;
829 return (imap_reply(r, redirect));
834 if (!strcasecmp(directive, "point")) { /* point */
836 if (is_closer(testpoint, pointarray, &closest_yet)) {
837 closest = ap_pstrdup(r->pool, value);
841 } /* move on to next line whether it's
844 } /* nothing matched, so we get another line! */
846 ap_cfg_closefile(imap); /* we are done with the map file; close it */
849 menu_footer(r); /* finish the menu and we are done */
853 if (closest) { /* if a 'point' directive has been seen */
854 redirect = imap_url(r, base, closest);
856 return HTTP_INTERNAL_SERVER_ERROR;
858 return (imap_reply(r, redirect));
861 if (mapdflt) { /* a default should be defined, even if
863 redirect = imap_url(r, base, mapdflt);
865 return HTTP_INTERNAL_SERVER_ERROR;
867 return (imap_reply(r, redirect));
870 return HTTP_INTERNAL_SERVER_ERROR; /* If we make it this far,
871 we failed. They lose! */
874 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
875 "map file %s, line %d syntax error: requires at "
876 "least two fields", r->uri, imap->line_number);
879 ap_cfg_closefile(imap);
881 /* There's not much else we can do ... we've already sent the headers
884 ap_rputs("\n\n[an internal server error occured]\n", r);
888 return HTTP_INTERNAL_SERVER_ERROR;
892 static const handler_rec imap_handlers[] =
894 {IMAP_MAGIC_TYPE, imap_handler},
895 {"imap-file", imap_handler},
899 module MODULE_VAR_EXPORT imap_module =
901 STANDARD20_MODULE_STUFF,
902 create_imap_dir_config, /* dir config creater */
903 merge_imap_dir_configs, /* dir merger --- default is to override */
904 NULL, /* server config */
905 NULL, /* merge server config */
906 imap_cmds, /* command ap_table_t */
907 imap_handlers, /* handlers */
908 NULL /* register hooks */