directives for directories that are beneath one for which a
+ * configuration record was already created. The routine has the
+ * responsibility of creating a new record and merging the contents of the
+ * other two into it appropriately. If the module doesn't declare a merge
+ * routine, the record for the closest ancestor location (that has one) is
+ * used exclusively.
+ *
+ * The routine MUST NOT modify any of its arguments!
+ *
+ * The return value is a pointer to the created module-specific structure
+ * containing the merged values.
+ */
+static void *example_merge_dir_config(pool *p, void *parent_conf,
+ void *newloc_conf)
+{
+
+ excfg *merged_config = (excfg *) ap_pcalloc(p, sizeof(excfg));
+ excfg *pconf = (excfg *) parent_conf;
+ excfg *nconf = (excfg *) newloc_conf;
+ char *note;
+
+ /*
+ * Some things get copied directly from the more-specific record, rather
+ * than getting merged.
+ */
+ merged_config->local = nconf->local;
+ merged_config->loc = ap_pstrdup(p, nconf->loc);
+ /*
+ * Others, like the setting of the `congenital' flag, get ORed in. The
+ * setting of that particular flag, for instance, is TRUE if it was ever
+ * true anywhere in the upstream configuration.
+ */
+ merged_config->congenital = (pconf->congenital | pconf->local);
+ /*
+ * If we're merging records for two different types of environment (server
+ * and directory), mark the new record appropriately. Otherwise, inherit
+ * the current value.
+ */
+ merged_config->cmode =
+ (pconf->cmode == nconf->cmode) ? pconf->cmode : CONFIG_MODE_COMBO;
+ /*
+ * Now just record our being called in the trace list. Include the
+ * locations we were asked to merge.
+ */
+ note = ap_pstrcat(p, "example_merge_dir_config(\"", pconf->loc, "\",\"",
+ nconf->loc, "\")", NULL);
+ trace_add(NULL, NULL, merged_config, note);
+ return (void *) merged_config;
+}
+
+/*
+ * This function gets called to create a per-server configuration
+ * record. It will always be called for the "default" server.
+ *
+ * The return value is a pointer to the created module-specific
+ * structure.
+ */
+static void *example_create_server_config(pool *p, server_rec *s)
+{
+
+ excfg *cfg;
+ char *sname = s->server_hostname;
+
+ /*
+ * As with the example_create_dir_config() reoutine, we allocate and fill
+ * in an empty record.
+ */
+ cfg = (excfg *) ap_pcalloc(p, sizeof(excfg));
+ cfg->local = 0;
+ cfg->congenital = 0;
+ cfg->cmode = CONFIG_MODE_SERVER;
+ /*
+ * Note that we were called in the trace list.
+ */
+ sname = (sname != NULL) ? sname : "";
+ cfg->loc = ap_pstrcat(p, "SVR(", sname, ")", NULL);
+ trace_add(s, NULL, cfg, "example_create_server_config()");
+ return (void *) cfg;
+}
+
+/*
+ * This function gets called to merge two per-server configuration
+ * records. This is typically done to cope with things like virtual hosts and
+ * the default server configuration The routine has the responsibility of
+ * creating a new record and merging the contents of the other two into it
+ * appropriately. If the module doesn't declare a merge routine, the more
+ * specific existing record is used exclusively.
+ *
+ * The routine MUST NOT modify any of its arguments!
+ *
+ * The return value is a pointer to the created module-specific structure
+ * containing the merged values.
+ */
+static void *example_merge_server_config(pool *p, void *server1_conf,
+ void *server2_conf)
+{
+
+ excfg *merged_config = (excfg *) ap_pcalloc(p, sizeof(excfg));
+ excfg *s1conf = (excfg *) server1_conf;
+ excfg *s2conf = (excfg *) server2_conf;
+ char *note;
+
+ /*
+ * Our inheritance rules are our own, and part of our module's semantics.
+ * Basically, just note whence we came.
+ */
+ merged_config->cmode =
+ (s1conf->cmode == s2conf->cmode) ? s1conf->cmode : CONFIG_MODE_COMBO;
+ merged_config->local = s2conf->local;
+ merged_config->congenital = (s1conf->congenital | s1conf->local);
+ merged_config->loc = ap_pstrdup(p, s2conf->loc);
+ /*
+ * Trace our call, including what we were asked to merge.
+ */
+ note = ap_pstrcat(p, "example_merge_server_config(\"", s1conf->loc, "\",\"",
+ s2conf->loc, "\")", NULL);
+ trace_add(NULL, NULL, merged_config, note);
+ return (void *) merged_config;
+}
+
+/*
+ * This routine is called after the request has been read but before any other
+ * phases have been processed. This allows us to make decisions based upon
+ * the input header fields.
+ *
+ * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, no
+ * further modules are called for this phase.
+ */
+static int example_post_read_request(request_rec *r)
+{
+
+ excfg *cfg;
+
+ cfg = our_dconfig(r);
+ /*
+ * We don't actually *do* anything here, except note the fact that we were
+ * called.
+ */
+ trace_add(r->server, r, cfg, "example_post_read_request()");
+ return DECLINED;
+}
+
+/*
+ * This routine gives our module an opportunity to translate the URI into an
+ * actual filename. If we don't do anything special, the server's default
+ * rules (Alias directives and the like) will continue to be followed.
+ *
+ * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, no
+ * further modules are called for this phase.
+ */
+static int example_translate_handler(request_rec *r)
+{
+
+ excfg *cfg;
+
+ cfg = our_dconfig(r);
+ /*
+ * We don't actually *do* anything here, except note the fact that we were
+ * called.
+ */
+ trace_add(r->server, r, cfg, "example_translate_handler()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called to check the authentication information sent with
+ * the request (such as looking up the user in a database and verifying that
+ * the [encrypted] password sent matches the one in the database).
+ *
+ * The return value is OK, DECLINED, or some HTTP_mumble error (typically
+ * HTTP_UNAUTHORIZED). If we return OK, no other modules are given a chance
+ * at the request during this phase.
+ */
+static int example_check_user_id(request_rec *r)
+{
+
+ excfg *cfg;
+
+ cfg = our_dconfig(r);
+ /*
+ * Don't do anything except log the call.
+ */
+ trace_add(r->server, r, cfg, "example_check_user_id()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called to check to see if the resource being requested
+ * requires authorisation.
+ *
+ * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, no
+ * other modules are called during this phase.
+ *
+ * If *all* modules return DECLINED, the request is aborted with a server
+ * error.
+ */
+static int example_auth_checker(request_rec *r)
+{
+
+ excfg *cfg;
+
+ cfg = our_dconfig(r);
+ /*
+ * Log the call and return OK, or access will be denied (even though we
+ * didn't actually do anything).
+ */
+ trace_add(r->server, r, cfg, "example_auth_checker()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called to check for any module-specific restrictions placed
+ * upon the requested resource. (See the mod_access module for an example.)
+ *
+ * The return value is OK, DECLINED, or HTTP_mumble. All modules with an
+ * handler for this phase are called regardless of whether their predecessors
+ * return OK or DECLINED. The first one to return any other status, however,
+ * will abort the sequence (and the request) as usual.
+ */
+static int example_access_checker(request_rec *r)
+{
+
+ excfg *cfg;
+
+ cfg = our_dconfig(r);
+ trace_add(r->server, r, cfg, "example_access_checker()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called to determine and/or set the various document type
+ * information bits, like Content-type (via r->content_type), language, et
+ * cetera.
+ *
+ * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, no
+ * further modules are given a chance at the request for this phase.
+ */
+static int example_type_checker(request_rec *r)
+{
+
+ excfg *cfg;
+
+ cfg = our_dconfig(r);
+ /*
+ * Log the call, but don't do anything else - and report truthfully that
+ * we didn't do anything.
+ */
+ trace_add(r->server, r, cfg, "example_type_checker()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called to perform any module-specific fixing of header
+ * fields, et cetera. It is invoked just before any content-handler.
+ *
+ * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, the
+ * server will still call any remaining modules with an handler for this
+ * phase.
+ */
+static int example_fixer_upper(request_rec *r)
+{
+
+ excfg *cfg;
+
+ cfg = our_dconfig(r);
+ /*
+ * Log the call and exit.
+ */
+ trace_add(r->server, r, cfg, "example_fixer_upper()");
+ return OK;
+}
+
+/*
+ * This routine is called to perform any module-specific logging activities
+ * over and above the normal server things.
+ *
+ * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, any
+ * remaining modules with an handler for this phase will still be called.
+ */
+static int example_logger(request_rec *r)
+{
+
+ excfg *cfg;
+
+ cfg = our_dconfig(r);
+ trace_add(r->server, r, cfg, "example_logger()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called to give the module a chance to look at the request
+ * headers and take any appropriate specific actions early in the processing
+ * sequence.
+ *
+ * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, any
+ * remaining modules with handlers for this phase will still be called.
+ */
+static int example_header_parser(request_rec *r)
+{
+
+ excfg *cfg;
+
+ cfg = our_dconfig(r);
+ trace_add(r->server, r, cfg, "example_header_parser()");
+ return DECLINED;
+}
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* All of the routines have been declared now. Here's the list of */
+/* directives specific to our module, and information about where they */
+/* may appear and how the command parser should pass them to us for */
+/* processing. Note that care must be taken to ensure that there are NO */
+/* collisions of directive names between modules. */
+/* */
+/*--------------------------------------------------------------------------*/
+/*
+ * List of directives specific to our module.
+ */
+static const command_rec example_cmds[] =
+{
+ {
+ "Example", /* directive name */
+ cmd_example, /* config action routine */
+ NULL, /* argument to include in call */
+ OR_OPTIONS, /* where available */
+ NO_ARGS, /* arguments */
+ "Example directive - no arguments"
+ /* directive description */
+ },
+ {NULL}
+};
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* Now the list of content handlers available from this module. */
+/* */
+/*--------------------------------------------------------------------------*/
+/*
+ * List of content handlers our module supplies. Each handler is defined by
+ * two parts: a name by which it can be referenced (such as by
+ * {Add,Set}Handler), and the actual routine name. The list is terminated by
+ * a NULL block, since it can be of variable length.
+ *
+ * Note that content-handlers are invoked on a most-specific to least-specific
+ * basis; that is, a handler that is declared for "text/plain" will be
+ * invoked before one that was declared for "text / *". Note also that
+ * if a content-handler returns anything except DECLINED, no other
+ * content-handlers will be called.
+ */
+static const handler_rec example_handlers[] =
+{
+ {"example-handler", example_handler},
+ {NULL}
+};
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* Finally, the list of callback routines and data structures that */
+/* provide the hooks into our module from the other parts of the server. */
+/* */
+/*--------------------------------------------------------------------------*/
+/*
+ * Module definition for configuration. If a particular callback is not
+ * needed, replace its routine name below with the word NULL.
+ *
+ * The number in brackets indicates the order in which the routine is called
+ * during request processing. Note that not all routines are necessarily
+ * called (such as if a resource doesn't have access restrictions).
+ */
+module example_module =
+{
+ STANDARD_MODULE_STUFF,
+ example_init, /* module initializer */
+ example_create_dir_config, /* per-directory config creator */
+ example_merge_dir_config, /* dir config merger */
+ example_create_server_config, /* server config creator */
+ example_merge_server_config, /* server config merger */
+ example_cmds, /* command table */
+ example_handlers, /* [7] list of handlers */
+ example_translate_handler, /* [2] filename-to-URI translation */
+ example_check_user_id, /* [5] check/validate user_id */
+ example_auth_checker, /* [6] check user_id is valid *here* */
+ example_access_checker, /* [4] check access by host address */
+ example_type_checker, /* [7] MIME type checker/setter */
+ example_fixer_upper, /* [8] fixups */
+ example_logger, /* [10] logger */
+#if MODULE_MAGIC_NUMBER >= 19970103
+ example_header_parser, /* [3] header parser */
+#endif
+#if MODULE_MAGIC_NUMBER >= 19970719
+ example_child_init, /* process initializer */
+#endif
+#if MODULE_MAGIC_NUMBER >= 19970728
+ example_child_exit, /* process exit/cleanup */
+#endif
+#if MODULE_MAGIC_NUMBER >= 19970902
+ example_post_read_request /* [1] post read_request handling */
+#endif
+};
diff --git a/modules/filters/mod_include.c b/modules/filters/mod_include.c
new file mode 100644
index 0000000000..3ee96db83b
--- /dev/null
+++ b/modules/filters/mod_include.c
@@ -0,0 +1,2486 @@
+/* ====================================================================
+ * Copyright (c) 1995-1999 The Apache Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ * software must display the following acknowledgment:
+ * "This product includes software developed by the Apache Group
+ * for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * 4. The names "Apache Server" and "Apache Group" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For written permission, please contact
+ * apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ * acknowledgment:
+ * "This product includes software developed by the Apache Group
+ * for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Group and was originally based
+ * on public domain software written at the National Center for
+ * Supercomputing Applications, University of Illinois, Urbana-Champaign.
+ * For more information on the Apache Group and the Apache HTTP server
+ * project, please see .
+ *
+ */
+
+/*
+ * http_include.c: Handles the server-parsed HTML documents
+ *
+ * Original by Rob McCool; substantial fixups by David Robinson;
+ * incorporated into the Apache module framework by rst.
+ *
+ */
+/*
+ * sub key may be anything a Perl*Handler can be:
+ * subroutine name, package name (defaults to package::handler),
+ * Class->method call or anoymous sub {}
+ *
+ * Child accessed
+ * times.
+ *
+ *
+ *
+ * -Doug MacEachern
+ */
+
+#ifdef USE_PERL_SSI
+#include "config.h"
+#undef VOIDUSED
+#ifdef USE_SFIO
+#undef USE_SFIO
+#define USE_STDIO
+#endif
+#include "modules/perl/mod_perl.h"
+#else
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "util_script.h"
+#endif
+
+#define STARTING_SEQUENCE ""
+#define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
+#define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
+#define SIZEFMT_BYTES 0
+#define SIZEFMT_KMG 1
+#ifdef CHARSET_EBCDIC
+#define RAW_ASCII_CHAR(ch) os_toebcdic[(unsigned char)ch]
+#else /*CHARSET_EBCDIC*/
+#define RAW_ASCII_CHAR(ch) (ch)
+#endif /*CHARSET_EBCDIC*/
+
+module MODULE_VAR_EXPORT includes_module;
+
+/* just need some arbitrary non-NULL pointer which can't also be a request_rec */
+#define NESTED_INCLUDE_MAGIC (&includes_module)
+
+/* ------------------------ Environment function -------------------------- */
+
+/* XXX: could use ap_table_overlap here */
+static void add_include_vars(request_rec *r, char *timefmt)
+{
+#ifndef WIN32
+ struct passwd *pw;
+#endif /* ndef WIN32 */
+ table *e = r->subprocess_env;
+ char *t;
+ time_t date = r->request_time;
+
+ ap_table_setn(e, "DATE_LOCAL", ap_ht_time(r->pool, date, timefmt, 0));
+ ap_table_setn(e, "DATE_GMT", ap_ht_time(r->pool, date, timefmt, 1));
+ ap_table_setn(e, "LAST_MODIFIED",
+ ap_ht_time(r->pool, r->finfo.st_mtime, timefmt, 0));
+ ap_table_setn(e, "DOCUMENT_URI", r->uri);
+ ap_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
+#ifndef WIN32
+ pw = getpwuid(r->finfo.st_uid);
+ if (pw) {
+ ap_table_setn(e, "USER_NAME", ap_pstrdup(r->pool, pw->pw_name));
+ }
+ else {
+ ap_table_setn(e, "USER_NAME", ap_psprintf(r->pool, "user#%lu",
+ (unsigned long) r->finfo.st_uid));
+ }
+#endif /* ndef WIN32 */
+
+ if ((t = strrchr(r->filename, '/'))) {
+ ap_table_setn(e, "DOCUMENT_NAME", ++t);
+ }
+ else {
+ ap_table_setn(e, "DOCUMENT_NAME", r->uri);
+ }
+ if (r->args) {
+ char *arg_copy = ap_pstrdup(r->pool, r->args);
+
+ ap_unescape_url(arg_copy);
+ ap_table_setn(e, "QUERY_STRING_UNESCAPED",
+ ap_escape_shell_cmd(r->pool, arg_copy));
+ }
+}
+
+
+
+/* --------------------------- Parser functions --------------------------- */
+
+#define OUTBUFSIZE 4096
+/* PUT_CHAR and FLUSH_BUF currently only work within the scope of
+ * find_string(); they are hacks to avoid calling rputc for each and
+ * every character output. A common set of buffering calls for this
+ * type of output SHOULD be implemented.
+ */
+#define PUT_CHAR(c,r) \
+ { \
+ outbuf[outind++] = c; \
+ if (outind == OUTBUFSIZE) { \
+ FLUSH_BUF(r) \
+ }; \
+ }
+
+/* there SHOULD be some error checking on the return value of
+ * rwrite, however it is unclear what the API for rwrite returning
+ * errors is and little can really be done to help the error in
+ * any case.
+ */
+#define FLUSH_BUF(r) \
+ { \
+ ap_rwrite(outbuf, outind, r); \
+ outind = 0; \
+ }
+
+/*
+ * f: file handle being read from
+ * c: character to read into
+ * ret: return value to use if input fails
+ * r: current request_rec
+ *
+ * This macro is redefined after find_string() for historical reasons
+ * to avoid too many code changes. This is one of the many things
+ * that should be fixed.
+ */
+#define GET_CHAR(f,c,ret,r) \
+ { \
+ int i = getc(f); \
+ if (i == EOF) { /* either EOF or error -- needs error handling if latter */ \
+ if (ferror(f)) { \
+ fprintf(stderr, "encountered error in GET_CHAR macro, " \
+ "mod_include.\n"); \
+ } \
+ FLUSH_BUF(r); \
+ ap_pfclose(r->pool, f); \
+ return ret; \
+ } \
+ c = (char)i; \
+ }
+
+static int find_string(FILE *in, const char *str, request_rec *r, int printing)
+{
+ int x, l = strlen(str), p;
+ char outbuf[OUTBUFSIZE];
+ int outind = 0;
+ char c;
+
+ p = 0;
+ while (1) {
+ GET_CHAR(in, c, 1, r);
+ if (c == str[p]) {
+ if ((++p) == l) {
+ FLUSH_BUF(r);
+ return 0;
+ }
+ }
+ else {
+ if (printing) {
+ for (x = 0; x < p; x++) {
+ PUT_CHAR(str[x], r);
+ }
+ PUT_CHAR(c, r);
+ }
+ p = 0;
+ }
+ }
+}
+
+#undef FLUSH_BUF
+#undef PUT_CHAR
+#undef GET_CHAR
+#define GET_CHAR(f,c,r,p) \
+ { \
+ int i = getc(f); \
+ if (i == EOF) { /* either EOF or error -- needs error handling if latter */ \
+ if (ferror(f)) { \
+ fprintf(stderr, "encountered error in GET_CHAR macro, " \
+ "mod_include.\n"); \
+ } \
+ ap_pfclose(p, f); \
+ return r; \
+ } \
+ c = (char)i; \
+ }
+
+/*
+ * decodes a string containing html entities or numeric character references.
+ * 's' is overwritten with the decoded string.
+ * If 's' is syntatically incorrect, then the followed fixups will be made:
+ * unknown entities will be left undecoded;
+ * references to unused numeric characters will be deleted.
+ * In particular, will not be decoded, but will be deleted.
+ *
+ * drtr
+ */
+
+/* maximum length of any ISO-LATIN-1 HTML entity name. */
+#define MAXENTLEN (6)
+
+/* The following is a shrinking transformation, therefore safe. */
+
+static void decodehtml(char *s)
+{
+ int val, i, j;
+ char *p = s;
+ const char *ents;
+ static const char * const entlist[MAXENTLEN + 1] =
+ {
+ NULL, /* 0 */
+ NULL, /* 1 */
+ "lt\074gt\076", /* 2 */
+ "amp\046ETH\320eth\360", /* 3 */
+ "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml\353\
+iuml\357ouml\366uuml\374yuml\377", /* 4 */
+ "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc\333\
+THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352icirc\356ocirc\364\
+ucirc\373thorn\376", /* 5 */
+ "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311\
+Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde\325Oslash\330\
+Ugrave\331Uacute\332Yacute\335agrave\340aacute\341atilde\343ccedil\347\
+egrave\350eacute\351igrave\354iacute\355ntilde\361ograve\362oacute\363\
+otilde\365oslash\370ugrave\371uacute\372yacute\375" /* 6 */
+ };
+
+ for (; *s != '\0'; s++, p++) {
+ if (*s != '&') {
+ *p = *s;
+ continue;
+ }
+ /* find end of entity */
+ for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
+ continue;
+ }
+
+ if (s[i] == '\0') { /* treat as normal data */
+ *p = *s;
+ continue;
+ }
+
+ /* is it numeric ? */
+ if (s[1] == '#') {
+ for (j = 2, val = 0; j < i && ap_isdigit(s[j]); j++) {
+ val = val * 10 + s[j] - '0';
+ }
+ s += i;
+ if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
+ (val >= 127 && val <= 160) || val >= 256) {
+ p--; /* no data to output */
+ }
+ else {
+ *p = RAW_ASCII_CHAR(val);
+ }
+ }
+ else {
+ j = i - 1;
+ if (j > MAXENTLEN || entlist[j] == NULL) {
+ /* wrong length */
+ *p = '&';
+ continue; /* skip it */
+ }
+ for (ents = entlist[j]; *ents != '\0'; ents += i) {
+ if (strncmp(s + 1, ents, j) == 0) {
+ break;
+ }
+ }
+
+ if (*ents == '\0') {
+ *p = '&'; /* unknown */
+ }
+ else {
+ *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
+ s += i;
+ }
+ }
+ }
+
+ *p = '\0';
+}
+
+/*
+ * extract the next tag name and value.
+ * if there are no more tags, set the tag name to 'done'
+ * the tag value is html decoded if dodecode is non-zero
+ */
+
+static char *get_tag(pool *p, FILE *in, char *tag, int tagbuf_len, int dodecode)
+{
+ char *t = tag, *tag_val, c, term;
+
+ /* makes code below a little less cluttered */
+ --tagbuf_len;
+
+ do { /* skip whitespace */
+ GET_CHAR(in, c, NULL, p);
+ } while (ap_isspace(c));
+
+ /* tags can't start with - */
+ if (c == '-') {
+ GET_CHAR(in, c, NULL, p);
+ if (c == '-') {
+ do {
+ GET_CHAR(in, c, NULL, p);
+ } while (ap_isspace(c));
+ if (c == '>') {
+ ap_cpystrn(tag, "done", tagbuf_len);
+ return tag;
+ }
+ }
+ return NULL; /* failed */
+ }
+
+ /* find end of tag name */
+ while (1) {
+ if (t - tag == tagbuf_len) {
+ *t = '\0';
+ return NULL;
+ }
+ if (c == '=' || ap_isspace(c)) {
+ break;
+ }
+ *(t++) = ap_tolower(c);
+ GET_CHAR(in, c, NULL, p);
+ }
+
+ *t++ = '\0';
+ tag_val = t;
+
+ while (ap_isspace(c)) {
+ GET_CHAR(in, c, NULL, p); /* space before = */
+ }
+ if (c != '=') {
+ ungetc(c, in);
+ return NULL;
+ }
+
+ do {
+ GET_CHAR(in, c, NULL, p); /* space after = */
+ } while (ap_isspace(c));
+
+ /* we should allow a 'name' as a value */
+
+ if (c != '"' && c != '\'') {
+ return NULL;
+ }
+ term = c;
+ while (1) {
+ GET_CHAR(in, c, NULL, p);
+ if (t - tag == tagbuf_len) {
+ *t = '\0';
+ return NULL;
+ }
+/* Want to accept \" as a valid character within a string. */
+ if (c == '\\') {
+ *(t++) = c; /* Add backslash */
+ GET_CHAR(in, c, NULL, p);
+ if (c == term) { /* Only if */
+ *(--t) = c; /* Replace backslash ONLY for terminator */
+ }
+ }
+ else if (c == term) {
+ break;
+ }
+ *(t++) = c;
+ }
+ *t = '\0';
+ if (dodecode) {
+ decodehtml(tag_val);
+ }
+ return ap_pstrdup(p, tag_val);
+}
+
+static int get_directive(FILE *in, char *dest, size_t len, pool *p)
+{
+ char *d = dest;
+ char c;
+
+ /* make room for nul terminator */
+ --len;
+
+ /* skip initial whitespace */
+ while (1) {
+ GET_CHAR(in, c, 1, p);
+ if (!ap_isspace(c)) {
+ break;
+ }
+ }
+ /* now get directive */
+ while (1) {
+ if (d - dest == len) {
+ return 1;
+ }
+ *d++ = ap_tolower(c);
+ GET_CHAR(in, c, 1, p);
+ if (ap_isspace(c)) {
+ break;
+ }
+ }
+ *d = '\0';
+ return 0;
+}
+
+/*
+ * Do variable substitution on strings
+ */
+static void parse_string(request_rec *r, const char *in, char *out,
+ size_t length, int leave_name)
+{
+ char ch;
+ char *next = out;
+ char *end_out;
+
+ /* leave room for nul terminator */
+ end_out = out + length - 1;
+
+ while ((ch = *in++) != '\0') {
+ switch (ch) {
+ case '\\':
+ if (next == end_out) {
+ /* truncated */
+ *next = '\0';
+ return;
+ }
+ if (*in == '$') {
+ *next++ = *in++;
+ }
+ else {
+ *next++ = ch;
+ }
+ break;
+ case '$':
+ {
+ char var[MAX_STRING_LEN];
+ const char *start_of_var_name;
+ const char *end_of_var_name; /* end of var name + 1 */
+ const char *expansion;
+ const char *val;
+ size_t l;
+
+ /* guess that the expansion won't happen */
+ expansion = in - 1;
+ if (*in == '{') {
+ ++in;
+ start_of_var_name = in;
+ in = strchr(in, '}');
+ if (in == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
+ r, "Missing '}' on variable \"%s\"",
+ expansion);
+ *next = '\0';
+ return;
+ }
+ end_of_var_name = in;
+ ++in;
+ }
+ else {
+ start_of_var_name = in;
+ while (ap_isalnum(*in) || *in == '_') {
+ ++in;
+ }
+ end_of_var_name = in;
+ }
+ /* what a pain, too bad there's no table_getn where you can
+ * pass a non-nul terminated string */
+ l = end_of_var_name - start_of_var_name;
+ if (l != 0) {
+ l = (l > sizeof(var) - 1) ? (sizeof(var) - 1) : l;
+ memcpy(var, start_of_var_name, l);
+ var[l] = '\0';
+
+ val = ap_table_get(r->subprocess_env, var);
+ if (val) {
+ expansion = val;
+ l = strlen(expansion);
+ }
+ else if (leave_name) {
+ l = in - expansion;
+ }
+ else {
+ break; /* no expansion to be done */
+ }
+ }
+ else {
+ /* zero-length variable name causes just the $ to be copied */
+ l = 1;
+ }
+ l = (l > end_out - next) ? (end_out - next) : l;
+ memcpy(next, expansion, l);
+ next += l;
+ break;
+ }
+ default:
+ if (next == end_out) {
+ /* truncated */
+ *next = '\0';
+ return;
+ }
+ *next++ = ch;
+ break;
+ }
+ }
+ *next = '\0';
+ return;
+}
+
+/* --------------------------- Action handlers ---------------------------- */
+
+static int include_cgi(char *s, request_rec *r)
+{
+ request_rec *rr = ap_sub_req_lookup_uri(s, r);
+ int rr_status;
+
+ if (rr->status != HTTP_OK) {
+ return -1;
+ }
+
+ /* No hardwired path info or query allowed */
+
+ if ((rr->path_info && rr->path_info[0]) || rr->args) {
+ return -1;
+ }
+ if (rr->finfo.st_mode == 0) {
+ return -1;
+ }
+
+ /* Script gets parameters of the *document*, for back compatibility */
+
+ rr->path_info = r->path_info; /* hard to get right; see mod_cgi.c */
+ rr->args = r->args;
+
+ /* Force sub_req to be treated as a CGI request, even if ordinary
+ * typing rules would have called it something else.
+ */
+
+ rr->content_type = CGI_MAGIC_TYPE;
+
+ /* Run it. */
+
+ rr_status = ap_run_sub_req(rr);
+ if (ap_is_HTTP_REDIRECT(rr_status)) {
+ const char *location = ap_table_get(rr->headers_out, "Location");
+ location = ap_escape_html(rr->pool, location);
+ ap_rvputs(r, "", location, "", NULL);
+ }
+
+ ap_destroy_sub_req(rr);
+#ifndef WIN32
+ ap_chdir_file(r->filename);
+#endif
+
+ return 0;
+}
+
+/* ensure that path is relative, and does not contain ".." elements
+ * ensentially ensure that it does not match the regex:
+ * (^/|(^|/)\.\.(/|$))
+ * XXX: this needs os abstraction... consider c:..\foo in win32
+ */
+static int is_only_below(const char *path)
+{
+#ifdef HAVE_DRIVE_LETTERS
+ if (path[1] == ':')
+ return 0;
+#endif
+ if (path[0] == '/') {
+ return 0;
+ }
+ if (path[0] == '.' && path[1] == '.'
+ && (path[2] == '\0' || path[2] == '/')) {
+ return 0;
+ }
+ while (*path) {
+ if (*path == '/' && path[1] == '.' && path[2] == '.'
+ && (path[3] == '\0' || path[3] == '/')) {
+ return 0;
+ }
+ ++path;
+ }
+ return 1;
+}
+
+static int handle_include(FILE *in, request_rec *r, const char *error, int noexec)
+{
+ char tag[MAX_STRING_LEN];
+ char parsed_string[MAX_STRING_LEN];
+ char *tag_val;
+
+ while (1) {
+ if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
+ return 1;
+ }
+ if (!strcmp(tag, "file") || !strcmp(tag, "virtual")) {
+ request_rec *rr = NULL;
+ char *error_fmt = NULL;
+
+ parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+ if (tag[0] == 'f') {
+ /* be safe; only files in this directory or below allowed */
+ if (!is_only_below(parsed_string)) {
+ error_fmt = "unable to include file \"%s\" "
+ "in parsed file %s";
+ }
+ else {
+ rr = ap_sub_req_lookup_file(parsed_string, r);
+ }
+ }
+ else {
+ rr = ap_sub_req_lookup_uri(parsed_string, r);
+ }
+
+ if (!error_fmt && rr->status != HTTP_OK) {
+ error_fmt = "unable to include \"%s\" in parsed file %s";
+ }
+
+ if (!error_fmt && noexec && rr->content_type
+ && (strncmp(rr->content_type, "text/", 5))) {
+ error_fmt = "unable to include potential exec \"%s\" "
+ "in parsed file %s";
+ }
+ if (error_fmt == NULL) {
+ /* try to avoid recursive includes. We do this by walking
+ * up the r->main list of subrequests, and at each level
+ * walking back through any internal redirects. At each
+ * step, we compare the filenames and the URIs.
+ *
+ * The filename comparison catches a recursive include
+ * with an ever-changing URL, eg.
+ *
+ * which, although they would eventually be caught because
+ * we have a limit on the length of files, etc., can
+ * recurse for a while.
+ *
+ * The URI comparison catches the case where the filename
+ * is changed while processing the request, so the
+ * current name is never the same as any previous one.
+ * This can happen with "DocumentRoot /foo" when you
+ * request "/" on the server and it includes "/".
+ * This only applies to modules such as mod_dir that
+ * (somewhat improperly) mess with r->filename outside
+ * of a filename translation phase.
+ */
+ int founddupe = 0;
+ request_rec *p;
+ for (p = r; p != NULL && !founddupe; p = p->main) {
+ request_rec *q;
+ for (q = p; q != NULL; q = q->prev) {
+ if ( (strcmp(q->filename, rr->filename) == 0) ||
+ (strcmp(q->uri, rr->uri) == 0) ){
+ founddupe = 1;
+ break;
+ }
+ }
+ }
+
+ if (p != NULL) {
+ error_fmt = "Recursive include of \"%s\" "
+ "in parsed file %s";
+ }
+ }
+
+ /* see the Kludge in send_parsed_file for why */
+ if (rr)
+ ap_set_module_config(rr->request_config, &includes_module, r);
+
+ if (!error_fmt && ap_run_sub_req(rr)) {
+ error_fmt = "unable to include \"%s\" in parsed file %s";
+ }
+#ifndef WIN32
+ ap_chdir_file(r->filename);
+#endif
+ if (error_fmt) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
+ r, error_fmt, tag_val, r->filename);
+ ap_rputs(error, r);
+ }
+
+ /* destroy the sub request if it's not a nested include */
+ if (rr != NULL
+ && ap_get_module_config(rr->request_config, &includes_module)
+ != NESTED_INCLUDE_MAGIC) {
+ ap_destroy_sub_req(rr);
+ }
+ }
+ else if (!strcmp(tag, "done")) {
+ return 0;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "unknown parameter \"%s\" to tag include in %s",
+ tag, r->filename);
+ ap_rputs(error, r);
+ }
+ }
+}
+
+typedef struct {
+#ifdef TPF
+ TPF_FORK_CHILD t;
+#endif
+ request_rec *r;
+ char *s;
+} include_cmd_arg;
+
+static int include_cmd_child(void *arg, child_info *pinfo)
+{
+ request_rec *r = ((include_cmd_arg *) arg)->r;
+ char *s = ((include_cmd_arg *) arg)->s;
+ table *env = r->subprocess_env;
+ int child_pid = 0;
+#ifdef DEBUG_INCLUDE_CMD
+#ifdef OS2
+ /* under OS/2 /dev/tty is referenced as con */
+ FILE *dbg = fopen("con", "w");
+#else
+ FILE *dbg = fopen("/dev/tty", "w");
+#endif
+#endif
+#ifndef WIN32
+ char err_string[MAX_STRING_LEN];
+#endif
+
+#ifdef DEBUG_INCLUDE_CMD
+ fprintf(dbg, "Attempting to include command '%s'\n", s);
+#endif
+
+ if (r->path_info && r->path_info[0] != '\0') {
+ request_rec *pa_req;
+
+ ap_table_setn(env, "PATH_INFO", ap_escape_shell_cmd(r->pool, r->path_info));
+
+ pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info), r);
+ if (pa_req->filename) {
+ ap_table_setn(env, "PATH_TRANSLATED",
+ ap_pstrcat(r->pool, pa_req->filename, pa_req->path_info,
+ NULL));
+ }
+ }
+
+ if (r->args) {
+ char *arg_copy = ap_pstrdup(r->pool, r->args);
+
+ ap_table_setn(env, "QUERY_STRING", r->args);
+ ap_unescape_url(arg_copy);
+ ap_table_setn(env, "QUERY_STRING_UNESCAPED",
+ ap_escape_shell_cmd(r->pool, arg_copy));
+ }
+
+ ap_error_log2stderr(r->server);
+
+#ifdef DEBUG_INCLUDE_CMD
+ fprintf(dbg, "Attempting to exec '%s'\n", s);
+#endif
+#ifdef TPF
+ return (0);
+#else
+ ap_cleanup_for_exec();
+ /* set shellcmd flag to pass arg to SHELL_PATH */
+ child_pid = ap_call_exec(r, pinfo, s, ap_create_environment(r->pool, env),
+ 1);
+#if defined(WIN32) || defined(OS2)
+ return (child_pid);
+#else
+ /* Oh, drat. We're still here. The log file descriptors are closed,
+ * so we have to whimper a complaint onto stderr...
+ */
+
+#ifdef DEBUG_INCLUDE_CMD
+ fprintf(dbg, "Exec failed\n");
+#endif
+ ap_snprintf(err_string, sizeof(err_string),
+ "exec of %s failed, reason: %s (errno = %d)\n",
+ SHELL_PATH, strerror(errno), errno);
+ write(STDERR_FILENO, err_string, strlen(err_string));
+ exit(0);
+ /* NOT REACHED */
+ return (child_pid);
+#endif /* WIN32 */
+#endif /* TPF */
+}
+
+static int include_cmd(char *s, request_rec *r)
+{
+ include_cmd_arg arg;
+ BUFF *script_in;
+
+ arg.r = r;
+ arg.s = s;
+#ifdef TPF
+ arg.t.filename = r->filename;
+ arg.t.subprocess_env = r->subprocess_env;
+ arg.t.prog_type = FORK_FILE;
+#endif
+
+ if (!ap_bspawn_child(r->pool, include_cmd_child, &arg,
+ kill_after_timeout, NULL, &script_in, NULL)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
+ "couldn't spawn include command");
+ return -1;
+ }
+
+ ap_send_fb(script_in, r);
+ ap_bclose(script_in);
+ return 0;
+}
+
+static int handle_exec(FILE *in, request_rec *r, const char *error)
+{
+ char tag[MAX_STRING_LEN];
+ char *tag_val;
+ char *file = r->filename;
+ char parsed_string[MAX_STRING_LEN];
+
+ while (1) {
+ if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
+ return 1;
+ }
+ if (!strcmp(tag, "cmd")) {
+ parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 1);
+ if (include_cmd(parsed_string, r) == -1) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "execution failure for parameter \"%s\" "
+ "to tag exec in file %s",
+ tag, r->filename);
+ ap_rputs(error, r);
+ }
+ /* just in case some stooge changed directories */
+#ifndef WIN32
+ ap_chdir_file(r->filename);
+#endif
+ }
+ else if (!strcmp(tag, "cgi")) {
+ parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+ if (include_cgi(parsed_string, r) == -1) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "invalid CGI ref \"%s\" in %s", tag_val, file);
+ ap_rputs(error, r);
+ }
+ /* grumble groan */
+#ifndef WIN32
+ ap_chdir_file(r->filename);
+#endif
+ }
+ else if (!strcmp(tag, "done")) {
+ return 0;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "unknown parameter \"%s\" to tag exec in %s",
+ tag, file);
+ ap_rputs(error, r);
+ }
+ }
+
+}
+
+static int handle_echo(FILE *in, request_rec *r, const char *error)
+{
+ char tag[MAX_STRING_LEN];
+ char *tag_val;
+
+ while (1) {
+ if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
+ return 1;
+ }
+ if (!strcmp(tag, "var")) {
+ const char *val = ap_table_get(r->subprocess_env, tag_val);
+
+ if (val) {
+ ap_rputs(val, r);
+ }
+ else {
+ ap_rputs("(none)", r);
+ }
+ }
+ else if (!strcmp(tag, "done")) {
+ return 0;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "unknown parameter \"%s\" to tag echo in %s",
+ tag, r->filename);
+ ap_rputs(error, r);
+ }
+ }
+}
+
+#ifdef USE_PERL_SSI
+static int handle_perl(FILE *in, request_rec *r, const char *error)
+{
+ char tag[MAX_STRING_LEN];
+ char parsed_string[MAX_STRING_LEN];
+ char *tag_val;
+ SV *sub = Nullsv;
+ AV *av = newAV();
+
+ if (ap_allow_options(r) & OPT_INCNOEXEC) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "#perl SSI disallowed by IncludesNoExec in %s",
+ r->filename);
+ return DECLINED;
+ }
+ while (1) {
+ if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
+ break;
+ }
+ if (strnEQ(tag, "sub", 3)) {
+ sub = newSVpv(tag_val, 0);
+ }
+ else if (strnEQ(tag, "arg", 3)) {
+ parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+ av_push(av, newSVpv(parsed_string, 0));
+ }
+ else if (strnEQ(tag, "done", 4)) {
+ break;
+ }
+ }
+ perl_stdout2client(r);
+ perl_setup_env(r);
+ perl_call_handler(sub, r, av);
+ return OK;
+}
+#endif
+
+/* error and tf must point to a string with room for at
+ * least MAX_STRING_LEN characters
+ */
+static int handle_config(FILE *in, request_rec *r, char *error, char *tf,
+ int *sizefmt)
+{
+ char tag[MAX_STRING_LEN];
+ char *tag_val;
+ char parsed_string[MAX_STRING_LEN];
+ table *env = r->subprocess_env;
+
+ while (1) {
+ if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0))) {
+ return 1;
+ }
+ if (!strcmp(tag, "errmsg")) {
+ parse_string(r, tag_val, error, MAX_STRING_LEN, 0);
+ }
+ else if (!strcmp(tag, "timefmt")) {
+ time_t date = r->request_time;
+
+ parse_string(r, tag_val, tf, MAX_STRING_LEN, 0);
+ ap_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, tf, 0));
+ ap_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, tf, 1));
+ ap_table_setn(env, "LAST_MODIFIED",
+ ap_ht_time(r->pool, r->finfo.st_mtime, tf, 0));
+ }
+ else if (!strcmp(tag, "sizefmt")) {
+ parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+ decodehtml(parsed_string);
+ if (!strcmp(parsed_string, "bytes")) {
+ *sizefmt = SIZEFMT_BYTES;
+ }
+ else if (!strcmp(parsed_string, "abbrev")) {
+ *sizefmt = SIZEFMT_KMG;
+ }
+ }
+ else if (!strcmp(tag, "done")) {
+ return 0;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "unknown parameter \"%s\" to tag config in %s",
+ tag, r->filename);
+ ap_rputs(error, r);
+ }
+ }
+}
+
+
+static int find_file(request_rec *r, const char *directive, const char *tag,
+ char *tag_val, struct stat *finfo, const char *error)
+{
+ char *to_send = tag_val;
+ request_rec *rr = NULL;
+ int ret=0;
+ char *error_fmt = NULL;
+
+ if (!strcmp(tag, "file")) {
+ /* be safe; only files in this directory or below allowed */
+ if (!is_only_below(tag_val)) {
+ error_fmt = "unable to access file \"%s\" "
+ "in parsed file %s";
+ }
+ else {
+ ap_getparents(tag_val); /* get rid of any nasties */
+ rr = ap_sub_req_lookup_file(tag_val, r);
+
+ if (rr->status == HTTP_OK && rr->finfo.st_mode != 0) {
+ to_send = rr->filename;
+ if (stat(to_send, finfo)) {
+ error_fmt = "unable to get information about \"%s\" "
+ "in parsed file %s";
+ }
+ }
+ else {
+ error_fmt = "unable to lookup information about \"%s\" "
+ "in parsed file %s";
+ }
+ }
+
+ if (error_fmt) {
+ ret = -1;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, r, error_fmt, to_send, r->filename);
+ ap_rputs(error, r);
+ }
+
+ if (rr) ap_destroy_sub_req(rr);
+
+ return ret;
+ }
+ else if (!strcmp(tag, "virtual")) {
+ rr = ap_sub_req_lookup_uri(tag_val, r);
+
+ if (rr->status == HTTP_OK && rr->finfo.st_mode != 0) {
+ memcpy((char *) finfo, (const char *) &rr->finfo,
+ sizeof(struct stat));
+ ap_destroy_sub_req(rr);
+ return 0;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "unable to get information about \"%s\" "
+ "in parsed file %s",
+ tag_val, r->filename);
+ ap_rputs(error, r);
+ ap_destroy_sub_req(rr);
+ return -1;
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "unknown parameter \"%s\" to tag %s in %s",
+ tag, directive, r->filename);
+ ap_rputs(error, r);
+ return -1;
+ }
+}
+
+
+static int handle_fsize(FILE *in, request_rec *r, const char *error, int sizefmt)
+{
+ char tag[MAX_STRING_LEN];
+ char *tag_val;
+ struct stat finfo;
+ char parsed_string[MAX_STRING_LEN];
+
+ while (1) {
+ if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
+ return 1;
+ }
+ else if (!strcmp(tag, "done")) {
+ return 0;
+ }
+ else {
+ parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+ if (!find_file(r, "fsize", tag, parsed_string, &finfo, error)) {
+ if (sizefmt == SIZEFMT_KMG) {
+ ap_send_size(finfo.st_size, r);
+ }
+ else {
+ int l, x;
+#if defined(AP_OFF_T_IS_QUAD)
+ ap_snprintf(tag, sizeof(tag), "%qd", finfo.st_size);
+#else
+ ap_snprintf(tag, sizeof(tag), "%ld", finfo.st_size);
+#endif
+ l = strlen(tag); /* grrr */
+ for (x = 0; x < l; x++) {
+ if (x && (!((l - x) % 3))) {
+ ap_rputc(',', r);
+ }
+ ap_rputc(tag[x], r);
+ }
+ }
+ }
+ }
+ }
+}
+
+static int handle_flastmod(FILE *in, request_rec *r, const char *error, const char *tf)
+{
+ char tag[MAX_STRING_LEN];
+ char *tag_val;
+ struct stat finfo;
+ char parsed_string[MAX_STRING_LEN];
+
+ while (1) {
+ if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
+ return 1;
+ }
+ else if (!strcmp(tag, "done")) {
+ return 0;
+ }
+ else {
+ parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+ if (!find_file(r, "flastmod", tag, parsed_string, &finfo, error)) {
+ ap_rputs(ap_ht_time(r->pool, finfo.st_mtime, tf, 0), r);
+ }
+ }
+ }
+}
+
+static int re_check(request_rec *r, char *string, char *rexp)
+{
+ regex_t *compiled;
+ int regex_error;
+
+ compiled = ap_pregcomp(r->pool, rexp, REG_EXTENDED | REG_NOSUB);
+ if (compiled == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "unable to compile pattern \"%s\"", rexp);
+ return -1;
+ }
+ regex_error = ap_regexec(compiled, string, 0, (regmatch_t *) NULL, 0);
+ ap_pregfree(r->pool, compiled);
+ return (!regex_error);
+}
+
+enum token_type {
+ token_string,
+ token_and, token_or, token_not, token_eq, token_ne,
+ token_rbrace, token_lbrace, token_group,
+ token_ge, token_le, token_gt, token_lt
+};
+struct token {
+ enum token_type type;
+ char value[MAX_STRING_LEN];
+};
+
+/* there is an implicit assumption here that string is at most MAX_STRING_LEN-1
+ * characters long...
+ */
+static const char *get_ptoken(request_rec *r, const char *string, struct token *token)
+{
+ char ch;
+ int next = 0;
+ int qs = 0;
+
+ /* Skip leading white space */
+ if (string == (char *) NULL) {
+ return (char *) NULL;
+ }
+ while ((ch = *string++)) {
+ if (!ap_isspace(ch)) {
+ break;
+ }
+ }
+ if (ch == '\0') {
+ return (char *) NULL;
+ }
+
+ token->type = token_string; /* the default type */
+ switch (ch) {
+ case '(':
+ token->type = token_lbrace;
+ return (string);
+ case ')':
+ token->type = token_rbrace;
+ return (string);
+ case '=':
+ token->type = token_eq;
+ return (string);
+ case '!':
+ if (*string == '=') {
+ token->type = token_ne;
+ return (string + 1);
+ }
+ else {
+ token->type = token_not;
+ return (string);
+ }
+ case '\'':
+ token->type = token_string;
+ qs = 1;
+ break;
+ case '|':
+ if (*string == '|') {
+ token->type = token_or;
+ return (string + 1);
+ }
+ break;
+ case '&':
+ if (*string == '&') {
+ token->type = token_and;
+ return (string + 1);
+ }
+ break;
+ case '>':
+ if (*string == '=') {
+ token->type = token_ge;
+ return (string + 1);
+ }
+ else {
+ token->type = token_gt;
+ return (string);
+ }
+ case '<':
+ if (*string == '=') {
+ token->type = token_le;
+ return (string + 1);
+ }
+ else {
+ token->type = token_lt;
+ return (string);
+ }
+ default:
+ token->type = token_string;
+ break;
+ }
+ /* We should only be here if we are in a string */
+ if (!qs) {
+ token->value[next++] = ch;
+ }
+
+ /*
+ * Yes I know that goto's are BAD. But, c doesn't allow me to
+ * exit a loop from a switch statement. Yes, I could use a flag,
+ * but that is (IMHO) even less readable/maintainable than the goto.
+ */
+ /*
+ * I used the ++string throughout this section so that string
+ * ends up pointing to the next token and I can just return it
+ */
+ for (ch = *string; ch != '\0'; ch = *++string) {
+ if (ch == '\\') {
+ if ((ch = *++string) == '\0') {
+ goto TOKEN_DONE;
+ }
+ token->value[next++] = ch;
+ continue;
+ }
+ if (!qs) {
+ if (ap_isspace(ch)) {
+ goto TOKEN_DONE;
+ }
+ switch (ch) {
+ case '(':
+ goto TOKEN_DONE;
+ case ')':
+ goto TOKEN_DONE;
+ case '=':
+ goto TOKEN_DONE;
+ case '!':
+ goto TOKEN_DONE;
+ case '|':
+ if (*(string + 1) == '|') {
+ goto TOKEN_DONE;
+ }
+ break;
+ case '&':
+ if (*(string + 1) == '&') {
+ goto TOKEN_DONE;
+ }
+ break;
+ case '<':
+ goto TOKEN_DONE;
+ case '>':
+ goto TOKEN_DONE;
+ }
+ token->value[next++] = ch;
+ }
+ else {
+ if (ch == '\'') {
+ qs = 0;
+ ++string;
+ goto TOKEN_DONE;
+ }
+ token->value[next++] = ch;
+ }
+ }
+ TOKEN_DONE:
+ /* If qs is still set, I have an unmatched ' */
+ if (qs) {
+ ap_rputs("\nUnmatched '\n", r);
+ next = 0;
+ }
+ token->value[next] = '\0';
+ return (string);
+}
+
+
+/*
+ * Hey I still know that goto's are BAD. I don't think that I've ever
+ * used two in the same project, let alone the same file before. But,
+ * I absolutely want to make sure that I clean up the memory in all
+ * cases. And, without rewriting this completely, the easiest way
+ * is to just branch to the return code which cleans it up.
+ */
+/* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1
+ * characters long...
+ */
+static int parse_expr(request_rec *r, const char *expr, const char *error)
+{
+ struct parse_node {
+ struct parse_node *left, *right, *parent;
+ struct token token;
+ int value, done;
+ } *root, *current, *new;
+ const char *parse;
+ char buffer[MAX_STRING_LEN];
+ pool *expr_pool;
+ int retval = 0;
+
+ if ((parse = expr) == (char *) NULL) {
+ return (0);
+ }
+ root = current = (struct parse_node *) NULL;
+ expr_pool = ap_make_sub_pool(r->pool);
+
+ /* Create Parse Tree */
+ while (1) {
+ new = (struct parse_node *) ap_palloc(expr_pool,
+ sizeof(struct parse_node));
+ new->parent = new->left = new->right = (struct parse_node *) NULL;
+ new->done = 0;
+ if ((parse = get_ptoken(r, parse, &new->token)) == (char *) NULL) {
+ break;
+ }
+ switch (new->token.type) {
+
+ case token_string:
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, " Token: string (", new->token.value, ")\n", NULL);
+#endif
+ if (current == (struct parse_node *) NULL) {
+ root = current = new;
+ break;
+ }
+ switch (current->token.type) {
+ case token_string:
+ if (current->token.value[0] != '\0') {
+ strncat(current->token.value, " ",
+ sizeof(current->token.value)
+ - strlen(current->token.value) - 1);
+ }
+ strncat(current->token.value, new->token.value,
+ sizeof(current->token.value)
+ - strlen(current->token.value) - 1);
+ current->token.value[sizeof(current->token.value) - 1] = '\0';
+ break;
+ case token_eq:
+ case token_ne:
+ case token_and:
+ case token_or:
+ case token_lbrace:
+ case token_not:
+ case token_ge:
+ case token_gt:
+ case token_le:
+ case token_lt:
+ new->parent = current;
+ current = current->right = new;
+ break;
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+ break;
+
+ case token_and:
+ case token_or:
+#ifdef DEBUG_INCLUDE
+ ap_rputs(" Token: and/or\n", r);
+#endif
+ if (current == (struct parse_node *) NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+ /* Percolate upwards */
+ while (current != (struct parse_node *) NULL) {
+ switch (current->token.type) {
+ case token_string:
+ case token_group:
+ case token_not:
+ case token_eq:
+ case token_ne:
+ case token_and:
+ case token_or:
+ case token_ge:
+ case token_gt:
+ case token_le:
+ case token_lt:
+ current = current->parent;
+ continue;
+ case token_lbrace:
+ break;
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+ break;
+ }
+ if (current == (struct parse_node *) NULL) {
+ new->left = root;
+ new->left->parent = new;
+ new->parent = (struct parse_node *) NULL;
+ root = new;
+ }
+ else {
+ new->left = current->right;
+ current->right = new;
+ new->parent = current;
+ }
+ current = new;
+ break;
+
+ case token_not:
+#ifdef DEBUG_INCLUDE
+ ap_rputs(" Token: not\n", r);
+#endif
+ if (current == (struct parse_node *) NULL) {
+ root = current = new;
+ break;
+ }
+ /* Percolate upwards */
+ while (current != (struct parse_node *) NULL) {
+ switch (current->token.type) {
+ case token_not:
+ case token_eq:
+ case token_ne:
+ case token_and:
+ case token_or:
+ case token_lbrace:
+ case token_ge:
+ case token_gt:
+ case token_le:
+ case token_lt:
+ break;
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+ break;
+ }
+ if (current == (struct parse_node *) NULL) {
+ new->left = root;
+ new->left->parent = new;
+ new->parent = (struct parse_node *) NULL;
+ root = new;
+ }
+ else {
+ new->left = current->right;
+ current->right = new;
+ new->parent = current;
+ }
+ current = new;
+ break;
+
+ case token_eq:
+ case token_ne:
+ case token_ge:
+ case token_gt:
+ case token_le:
+ case token_lt:
+#ifdef DEBUG_INCLUDE
+ ap_rputs(" Token: eq/ne/ge/gt/le/lt\n", r);
+#endif
+ if (current == (struct parse_node *) NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+ /* Percolate upwards */
+ while (current != (struct parse_node *) NULL) {
+ switch (current->token.type) {
+ case token_string:
+ case token_group:
+ current = current->parent;
+ continue;
+ case token_lbrace:
+ case token_and:
+ case token_or:
+ break;
+ case token_not:
+ case token_eq:
+ case token_ne:
+ case token_ge:
+ case token_gt:
+ case token_le:
+ case token_lt:
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+ break;
+ }
+ if (current == (struct parse_node *) NULL) {
+ new->left = root;
+ new->left->parent = new;
+ new->parent = (struct parse_node *) NULL;
+ root = new;
+ }
+ else {
+ new->left = current->right;
+ current->right = new;
+ new->parent = current;
+ }
+ current = new;
+ break;
+
+ case token_rbrace:
+#ifdef DEBUG_INCLUDE
+ ap_rputs(" Token: rbrace\n", r);
+#endif
+ while (current != (struct parse_node *) NULL) {
+ if (current->token.type == token_lbrace) {
+ current->token.type = token_group;
+ break;
+ }
+ current = current->parent;
+ }
+ if (current == (struct parse_node *) NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Unmatched ')' in \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+ break;
+
+ case token_lbrace:
+#ifdef DEBUG_INCLUDE
+ ap_rputs(" Token: lbrace\n", r);
+#endif
+ if (current == (struct parse_node *) NULL) {
+ root = current = new;
+ break;
+ }
+ /* Percolate upwards */
+ while (current != (struct parse_node *) NULL) {
+ switch (current->token.type) {
+ case token_not:
+ case token_eq:
+ case token_ne:
+ case token_and:
+ case token_or:
+ case token_lbrace:
+ case token_ge:
+ case token_gt:
+ case token_le:
+ case token_lt:
+ break;
+ case token_string:
+ case token_group:
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+ break;
+ }
+ if (current == (struct parse_node *) NULL) {
+ new->left = root;
+ new->left->parent = new;
+ new->parent = (struct parse_node *) NULL;
+ root = new;
+ }
+ else {
+ new->left = current->right;
+ current->right = new;
+ new->parent = current;
+ }
+ current = new;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* Evaluate Parse Tree */
+ current = root;
+ while (current != (struct parse_node *) NULL) {
+ switch (current->token.type) {
+ case token_string:
+#ifdef DEBUG_INCLUDE
+ ap_rputs(" Evaluate string\n", r);
+#endif
+ parse_string(r, current->token.value, buffer, sizeof(buffer), 0);
+ ap_cpystrn(current->token.value, buffer, sizeof(current->token.value));
+ current->value = (current->token.value[0] != '\0');
+ current->done = 1;
+ current = current->parent;
+ break;
+
+ case token_and:
+ case token_or:
+#ifdef DEBUG_INCLUDE
+ ap_rputs(" Evaluate and/or\n", r);
+#endif
+ if (current->left == (struct parse_node *) NULL ||
+ current->right == (struct parse_node *) NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+ if (!current->left->done) {
+ switch (current->left->token.type) {
+ case token_string:
+ parse_string(r, current->left->token.value,
+ buffer, sizeof(buffer), 0);
+ ap_cpystrn(current->left->token.value, buffer,
+ sizeof(current->left->token.value));
+ current->left->value = (current->left->token.value[0] != '\0');
+ current->left->done = 1;
+ break;
+ default:
+ current = current->left;
+ continue;
+ }
+ }
+ if (!current->right->done) {
+ switch (current->right->token.type) {
+ case token_string:
+ parse_string(r, current->right->token.value,
+ buffer, sizeof(buffer), 0);
+ ap_cpystrn(current->right->token.value, buffer,
+ sizeof(current->right->token.value));
+ current->right->value = (current->right->token.value[0] != '\0');
+ current->right->done = 1;
+ break;
+ default:
+ current = current->right;
+ continue;
+ }
+ }
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, " Left: ", current->left->value ? "1" : "0",
+ "\n", NULL);
+ ap_rvputs(r, " Right: ", current->right->value ? "1" : "0",
+ "\n", NULL);
+#endif
+ if (current->token.type == token_and) {
+ current->value = current->left->value && current->right->value;
+ }
+ else {
+ current->value = current->left->value || current->right->value;
+ }
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, " Returning ", current->value ? "1" : "0",
+ "\n", NULL);
+#endif
+ current->done = 1;
+ current = current->parent;
+ break;
+
+ case token_eq:
+ case token_ne:
+#ifdef DEBUG_INCLUDE
+ ap_rputs(" Evaluate eq/ne\n", r);
+#endif
+ if ((current->left == (struct parse_node *) NULL) ||
+ (current->right == (struct parse_node *) NULL) ||
+ (current->left->token.type != token_string) ||
+ (current->right->token.type != token_string)) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+ parse_string(r, current->left->token.value,
+ buffer, sizeof(buffer), 0);
+ ap_cpystrn(current->left->token.value, buffer,
+ sizeof(current->left->token.value));
+ parse_string(r, current->right->token.value,
+ buffer, sizeof(buffer), 0);
+ ap_cpystrn(current->right->token.value, buffer,
+ sizeof(current->right->token.value));
+ if (current->right->token.value[0] == '/') {
+ int len;
+ len = strlen(current->right->token.value);
+ if (current->right->token.value[len - 1] == '/') {
+ current->right->token.value[len - 1] = '\0';
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid rexp \"%s\" in file %s",
+ current->right->token.value, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, " Re Compare (", current->left->token.value,
+ ") with /", ¤t->right->token.value[1], "/\n", NULL);
+#endif
+ current->value =
+ re_check(r, current->left->token.value,
+ ¤t->right->token.value[1]);
+ }
+ else {
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, " Compare (", current->left->token.value,
+ ") with (", current->right->token.value, ")\n", NULL);
+#endif
+ current->value =
+ (strcmp(current->left->token.value,
+ current->right->token.value) == 0);
+ }
+ if (current->token.type == token_ne) {
+ current->value = !current->value;
+ }
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, " Returning ", current->value ? "1" : "0",
+ "\n", NULL);
+#endif
+ current->done = 1;
+ current = current->parent;
+ break;
+ case token_ge:
+ case token_gt:
+ case token_le:
+ case token_lt:
+#ifdef DEBUG_INCLUDE
+ ap_rputs(" Evaluate ge/gt/le/lt\n", r);
+#endif
+ if ((current->left == (struct parse_node *) NULL) ||
+ (current->right == (struct parse_node *) NULL) ||
+ (current->left->token.type != token_string) ||
+ (current->right->token.type != token_string)) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+ parse_string(r, current->left->token.value,
+ buffer, sizeof(buffer), 0);
+ ap_cpystrn(current->left->token.value, buffer,
+ sizeof(current->left->token.value));
+ parse_string(r, current->right->token.value,
+ buffer, sizeof(buffer), 0);
+ ap_cpystrn(current->right->token.value, buffer,
+ sizeof(current->right->token.value));
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, " Compare (", current->left->token.value,
+ ") with (", current->right->token.value, ")\n", NULL);
+#endif
+ current->value =
+ strcmp(current->left->token.value,
+ current->right->token.value);
+ if (current->token.type == token_ge) {
+ current->value = current->value >= 0;
+ }
+ else if (current->token.type == token_gt) {
+ current->value = current->value > 0;
+ }
+ else if (current->token.type == token_le) {
+ current->value = current->value <= 0;
+ }
+ else if (current->token.type == token_lt) {
+ current->value = current->value < 0;
+ }
+ else {
+ current->value = 0; /* Don't return -1 if unknown token */
+ }
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, " Returning ", current->value ? "1" : "0",
+ "\n", NULL);
+#endif
+ current->done = 1;
+ current = current->parent;
+ break;
+
+ case token_not:
+ if (current->right != (struct parse_node *) NULL) {
+ if (!current->right->done) {
+ current = current->right;
+ continue;
+ }
+ current->value = !current->right->value;
+ }
+ else {
+ current->value = 0;
+ }
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, " Evaluate !: ", current->value ? "1" : "0",
+ "\n", NULL);
+#endif
+ current->done = 1;
+ current = current->parent;
+ break;
+
+ case token_group:
+ if (current->right != (struct parse_node *) NULL) {
+ if (!current->right->done) {
+ current = current->right;
+ continue;
+ }
+ current->value = current->right->value;
+ }
+ else {
+ current->value = 1;
+ }
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, " Evaluate (): ", current->value ? "1" : "0",
+ "\n", NULL);
+#endif
+ current->done = 1;
+ current = current->parent;
+ break;
+
+ case token_lbrace:
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Unmatched '(' in \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+
+ case token_rbrace:
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Unmatched ')' in \"%s\" in file %s",
+ expr, r->filename);
+ ap_rputs(error, r);
+ goto RETURN;
+
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "bad token type");
+ ap_rputs(error, r);
+ goto RETURN;
+ }
+ }
+
+ retval = (root == (struct parse_node *) NULL) ? 0 : root->value;
+ RETURN:
+ ap_destroy_pool(expr_pool);
+ return (retval);
+}
+
+static int handle_if(FILE *in, request_rec *r, const char *error,
+ int *conditional_status, int *printing)
+{
+ char tag[MAX_STRING_LEN];
+ char *tag_val;
+ char *expr;
+
+ expr = NULL;
+ while (1) {
+ tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0);
+ if (*tag == '\0') {
+ return 1;
+ }
+ else if (!strcmp(tag, "done")) {
+ if (expr == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "missing expr in if statement: %s",
+ r->filename);
+ ap_rputs(error, r);
+ return 1;
+ }
+ *printing = *conditional_status = parse_expr(r, expr, error);
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, "**** if conditional_status=\"",
+ *conditional_status ? "1" : "0", "\"\n", NULL);
+#endif
+ return 0;
+ }
+ else if (!strcmp(tag, "expr")) {
+ expr = tag_val;
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, "**** if expr=\"", expr, "\"\n", NULL);
+#endif
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "unknown parameter \"%s\" to tag if in %s",
+ tag, r->filename);
+ ap_rputs(error, r);
+ }
+ }
+}
+
+static int handle_elif(FILE *in, request_rec *r, const char *error,
+ int *conditional_status, int *printing)
+{
+ char tag[MAX_STRING_LEN];
+ char *tag_val;
+ char *expr;
+
+ expr = NULL;
+ while (1) {
+ tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0);
+ if (*tag == '\0') {
+ return 1;
+ }
+ else if (!strcmp(tag, "done")) {
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, "**** elif conditional_status=\"",
+ *conditional_status ? "1" : "0", "\"\n", NULL);
+#endif
+ if (*conditional_status) {
+ *printing = 0;
+ return (0);
+ }
+ if (expr == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "missing expr in elif statement: %s",
+ r->filename);
+ ap_rputs(error, r);
+ return 1;
+ }
+ *printing = *conditional_status = parse_expr(r, expr, error);
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, "**** elif conditional_status=\"",
+ *conditional_status ? "1" : "0", "\"\n", NULL);
+#endif
+ return 0;
+ }
+ else if (!strcmp(tag, "expr")) {
+ expr = tag_val;
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, "**** if expr=\"", expr, "\"\n", NULL);
+#endif
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "unknown parameter \"%s\" to tag if in %s",
+ tag, r->filename);
+ ap_rputs(error, r);
+ }
+ }
+}
+
+static int handle_else(FILE *in, request_rec *r, const char *error,
+ int *conditional_status, int *printing)
+{
+ char tag[MAX_STRING_LEN];
+
+ if (!get_tag(r->pool, in, tag, sizeof(tag), 1)) {
+ return 1;
+ }
+ else if (!strcmp(tag, "done")) {
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, "**** else conditional_status=\"",
+ *conditional_status ? "1" : "0", "\"\n", NULL);
+#endif
+ *printing = !(*conditional_status);
+ *conditional_status = 1;
+ return 0;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "else directive does not take tags in %s",
+ r->filename);
+ if (*printing) {
+ ap_rputs(error, r);
+ }
+ return -1;
+ }
+}
+
+static int handle_endif(FILE *in, request_rec *r, const char *error,
+ int *conditional_status, int *printing)
+{
+ char tag[MAX_STRING_LEN];
+
+ if (!get_tag(r->pool, in, tag, sizeof(tag), 1)) {
+ return 1;
+ }
+ else if (!strcmp(tag, "done")) {
+#ifdef DEBUG_INCLUDE
+ ap_rvputs(r, "**** endif conditional_status=\"",
+ *conditional_status ? "1" : "0", "\"\n", NULL);
+#endif
+ *printing = 1;
+ *conditional_status = 1;
+ return 0;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "endif directive does not take tags in %s",
+ r->filename);
+ ap_rputs(error, r);
+ return -1;
+ }
+}
+
+static int handle_set(FILE *in, request_rec *r, const char *error)
+{
+ char tag[MAX_STRING_LEN];
+ char parsed_string[MAX_STRING_LEN];
+ char *tag_val;
+ char *var;
+
+ var = (char *) NULL;
+ while (1) {
+ if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
+ return 1;
+ }
+ else if (!strcmp(tag, "done")) {
+ return 0;
+ }
+ else if (!strcmp(tag, "var")) {
+ var = tag_val;
+ }
+ else if (!strcmp(tag, "value")) {
+ if (var == (char *) NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "variable must precede value in set directive in %s",
+ r->filename);
+ ap_rputs(error, r);
+ return -1;
+ }
+ parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+ ap_table_setn(r->subprocess_env, var, ap_pstrdup(r->pool, parsed_string));
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid tag for set directive in %s", r->filename);
+ ap_rputs(error, r);
+ return -1;
+ }
+ }
+}
+
+static int handle_printenv(FILE *in, request_rec *r, const char *error)
+{
+ char tag[MAX_STRING_LEN];
+ char *tag_val;
+ array_header *arr = ap_table_elts(r->subprocess_env);
+ table_entry *elts = (table_entry *) arr->elts;
+ int i;
+
+ if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
+ return 1;
+ }
+ else if (!strcmp(tag, "done")) {
+ for (i = 0; i < arr->nelts; ++i) {
+ ap_rvputs(r, elts[i].key, "=", elts[i].val, "\n", NULL);
+ }
+ return 0;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "printenv directive does not take tags in %s",
+ r->filename);
+ ap_rputs(error, r);
+ return -1;
+ }
+}
+
+
+
+/* -------------------------- The main function --------------------------- */
+
+/* This is a stub which parses a file descriptor. */
+
+static void send_parsed_content(FILE *f, request_rec *r)
+{
+ char directive[MAX_STRING_LEN], error[MAX_STRING_LEN];
+ char timefmt[MAX_STRING_LEN];
+ int noexec = ap_allow_options(r) & OPT_INCNOEXEC;
+ int ret, sizefmt;
+ int if_nesting;
+ int printing;
+ int conditional_status;
+
+ ap_cpystrn(error, DEFAULT_ERROR_MSG, sizeof(error));
+ ap_cpystrn(timefmt, DEFAULT_TIME_FORMAT, sizeof(timefmt));
+ sizefmt = SIZEFMT_KMG;
+
+/* Turn printing on */
+ printing = conditional_status = 1;
+ if_nesting = 0;
+
+#ifndef WIN32
+ ap_chdir_file(r->filename);
+#endif
+ if (r->args) { /* add QUERY stuff to env cause it ain't yet */
+ char *arg_copy = ap_pstrdup(r->pool, r->args);
+
+ ap_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
+ ap_unescape_url(arg_copy);
+ ap_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
+ ap_escape_shell_cmd(r->pool, arg_copy));
+ }
+
+ while (1) {
+ if (!find_string(f, STARTING_SEQUENCE, r, printing)) {
+ if (get_directive(f, directive, sizeof(directive), r->pool)) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "mod_include: error reading directive in %s",
+ r->filename);
+ ap_rputs(error, r);
+ return;
+ }
+ if (!strcmp(directive, "if")) {
+ if (!printing) {
+ if_nesting++;
+ }
+ else {
+ ret = handle_if(f, r, error, &conditional_status,
+ &printing);
+ if_nesting = 0;
+ }
+ continue;
+ }
+ else if (!strcmp(directive, "else")) {
+ if (!if_nesting) {
+ ret = handle_else(f, r, error, &conditional_status,
+ &printing);
+ }
+ continue;
+ }
+ else if (!strcmp(directive, "elif")) {
+ if (!if_nesting) {
+ ret = handle_elif(f, r, error, &conditional_status,
+ &printing);
+ }
+ continue;
+ }
+ else if (!strcmp(directive, "endif")) {
+ if (!if_nesting) {
+ ret = handle_endif(f, r, error, &conditional_status,
+ &printing);
+ }
+ else {
+ if_nesting--;
+ }
+ continue;
+ }
+ if (!printing) {
+ continue;
+ }
+ if (!strcmp(directive, "exec")) {
+ if (noexec) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "exec used but not allowed in %s",
+ r->filename);
+ if (printing) {
+ ap_rputs(error, r);
+ }
+ ret = find_string(f, ENDING_SEQUENCE, r, 0);
+ }
+ else {
+ ret = handle_exec(f, r, error);
+ }
+ }
+ else if (!strcmp(directive, "config")) {
+ ret = handle_config(f, r, error, timefmt, &sizefmt);
+ }
+ else if (!strcmp(directive, "set")) {
+ ret = handle_set(f, r, error);
+ }
+ else if (!strcmp(directive, "include")) {
+ ret = handle_include(f, r, error, noexec);
+ }
+ else if (!strcmp(directive, "echo")) {
+ ret = handle_echo(f, r, error);
+ }
+ else if (!strcmp(directive, "fsize")) {
+ ret = handle_fsize(f, r, error, sizefmt);
+ }
+ else if (!strcmp(directive, "flastmod")) {
+ ret = handle_flastmod(f, r, error, timefmt);
+ }
+ else if (!strcmp(directive, "printenv")) {
+ ret = handle_printenv(f, r, error);
+ }
+#ifdef USE_PERL_SSI
+ else if (!strcmp(directive, "perl")) {
+ ret = handle_perl(f, r, error);
+ }
+#endif
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "unknown directive \"%s\" "
+ "in parsed doc %s",
+ directive, r->filename);
+ if (printing) {
+ ap_rputs(error, r);
+ }
+ ret = find_string(f, ENDING_SEQUENCE, r, 0);
+ }
+ if (ret) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "premature EOF in parsed file %s",
+ r->filename);
+ return;
+ }
+ }
+ else {
+ return;
+ }
+ }
+}
+
+/*****************************************************************
+ *
+ * XBITHACK. Sigh... NB it's configurable per-directory; the compile-time
+ * option only changes the default.
+ */
+
+module includes_module;
+enum xbithack {
+ xbithack_off, xbithack_on, xbithack_full
+};
+
+#ifdef XBITHACK
+#define DEFAULT_XBITHACK xbithack_full
+#else
+#define DEFAULT_XBITHACK xbithack_off
+#endif
+
+static void *create_includes_dir_config(pool *p, char *dummy)
+{
+ enum xbithack *result = (enum xbithack *) ap_palloc(p, sizeof(enum xbithack));
+ *result = DEFAULT_XBITHACK;
+ return result;
+}
+
+static const char *set_xbithack(cmd_parms *cmd, void *xbp, char *arg)
+{
+ enum xbithack *state = (enum xbithack *) xbp;
+
+ if (!strcasecmp(arg, "off")) {
+ *state = xbithack_off;
+ }
+ else if (!strcasecmp(arg, "on")) {
+ *state = xbithack_on;
+ }
+ else if (!strcasecmp(arg, "full")) {
+ *state = xbithack_full;
+ }
+ else {
+ return "XBitHack must be set to Off, On, or Full";
+ }
+
+ return NULL;
+}
+
+static int send_parsed_file(request_rec *r)
+{
+ FILE *f;
+ enum xbithack *state =
+ (enum xbithack *) ap_get_module_config(r->per_dir_config, &includes_module);
+ int errstatus;
+ request_rec *parent;
+
+ if (!(ap_allow_options(r) & OPT_INCLUDES)) {
+ return DECLINED;
+ }
+ r->allowed |= (1 << M_GET);
+ if (r->method_number != M_GET) {
+ return DECLINED;
+ }
+ if (r->finfo.st_mode == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "File does not exist: %s",
+ (r->path_info
+ ? ap_pstrcat(r->pool, r->filename, r->path_info, NULL)
+ : r->filename));
+ return HTTP_NOT_FOUND;
+ }
+
+ if (!(f = ap_pfopen(r->pool, r->filename, "r"))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
+ "file permissions deny server access: %s", r->filename);
+ return HTTP_FORBIDDEN;
+ }
+
+ if ((*state == xbithack_full)
+#if !defined(OS2) && !defined(WIN32)
+ /* OS/2 dosen't support Groups. */
+ && (r->finfo.st_mode & S_IXGRP)
+#endif
+ ) {
+ ap_update_mtime(r, r->finfo.st_mtime);
+ ap_set_last_modified(r);
+ }
+ if ((errstatus = ap_meets_conditions(r)) != OK) {
+ return errstatus;
+ }
+
+ ap_send_http_header(r);
+
+ if (r->header_only) {
+ ap_pfclose(r->pool, f);
+ return OK;
+ }
+
+ if ((parent = ap_get_module_config(r->request_config, &includes_module))) {
+ /* Kludge --- for nested includes, we want to keep the subprocess
+ * environment of the base document (for compatibility); that means
+ * torquing our own last_modified date as well so that the
+ * LAST_MODIFIED variable gets reset to the proper value if the
+ * nested document resets .
+ * We also insist that the memory for this subrequest not be
+ * destroyed, that's dealt with in handle_include().
+ */
+ r->subprocess_env = parent->subprocess_env;
+ ap_pool_join(parent->pool, r->pool);
+ r->finfo.st_mtime = parent->finfo.st_mtime;
+ }
+ else {
+ /* we're not a nested include, so we create an initial
+ * environment */
+ ap_add_common_vars(r);
+ ap_add_cgi_vars(r);
+ add_include_vars(r, DEFAULT_TIME_FORMAT);
+ }
+ /* XXX: this is bogus, at some point we're going to do a subrequest,
+ * and when we do it we're going to be subjecting code that doesn't
+ * expect to be signal-ready to SIGALRM. There is no clean way to
+ * fix this, except to put alarm support into BUFF. -djg
+ */
+ ap_hard_timeout("send SSI", r);
+
+#ifdef CHARSET_EBCDIC
+ /* XXX:@@@ Is the generated/included output ALWAYS in text/ebcdic format? */
+ ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, 1);
+#endif
+
+ send_parsed_content(f, r);
+
+ if (parent) {
+ /* signify that the sub request should not be killed */
+ ap_set_module_config(r->request_config, &includes_module,
+ NESTED_INCLUDE_MAGIC);
+ }
+
+ ap_kill_timeout(r);
+ return OK;
+}
+
+static int send_shtml_file(request_rec *r)
+{
+ r->content_type = "text/html";
+ return send_parsed_file(r);
+}
+
+static int xbithack_handler(request_rec *r)
+{
+#if defined(OS2) || defined(WIN32)
+ /* OS/2 dosen't currently support the xbithack. This is being worked on. */
+ return DECLINED;
+#else
+ enum xbithack *state;
+
+ if (!(r->finfo.st_mode & S_IXUSR)) {
+ return DECLINED;
+ }
+
+ state = (enum xbithack *) ap_get_module_config(r->per_dir_config,
+ &includes_module);
+
+ if (*state == xbithack_off) {
+ return DECLINED;
+ }
+ return send_parsed_file(r);
+#endif
+}
+
+static const command_rec includes_cmds[] =
+{
+ {"XBitHack", set_xbithack, NULL, OR_OPTIONS, TAKE1, "Off, On, or Full"},
+ {NULL}
+};
+
+static const handler_rec includes_handlers[] =
+{
+ {INCLUDES_MAGIC_TYPE, send_shtml_file},
+ {INCLUDES_MAGIC_TYPE3, send_shtml_file},
+ {"server-parsed", send_parsed_file},
+ {"text/html", xbithack_handler},
+ {NULL}
+};
+
+module MODULE_VAR_EXPORT includes_module =
+{
+ STANDARD_MODULE_STUFF,
+ NULL, /* initializer */
+ create_includes_dir_config, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ includes_cmds, /* command table */
+ includes_handlers, /* handlers */
+ NULL, /* filename translation */
+ NULL, /* check_user_id */
+ NULL, /* check auth */
+ NULL, /* check access */
+ NULL, /* type_checker */
+ NULL, /* fixups */
+ NULL, /* logger */
+ NULL, /* header parser */
+ NULL, /* child_init */
+ NULL, /* child_exit */
+ NULL /* post read-request */
+};
diff --git a/modules/generators/mod_asis.c b/modules/generators/mod_asis.c
new file mode 100644
index 0000000000..5e299df926
--- /dev/null
+++ b/modules/generators/mod_asis.c
@@ -0,0 +1,145 @@
+/* ====================================================================
+ * Copyright (c) 1995-1999 The Apache Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ * software must display the following acknowledgment:
+ * "This product includes software developed by the Apache Group
+ * for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * 4. The names "Apache Server" and "Apache Group" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For written permission, please contact
+ * apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ * acknowledgment:
+ * "This product includes software developed by the Apache Group
+ * for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Group and was originally based
+ * on public domain software written at the National Center for
+ * Supercomputing Applications, University of Illinois, Urbana-Champaign.
+ * For more information on the Apache Group and the Apache HTTP server
+ * project, please see .
+ *
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "util_script.h"
+#include "http_main.h"
+#include "http_request.h"
+
+static int asis_handler(request_rec *r)
+{
+ FILE *f;
+ const char *location;
+
+ r->allowed |= (1 << M_GET);
+ if (r->method_number != M_GET)
+ return DECLINED;
+ if (r->finfo.st_mode == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "File does not exist: %s", r->filename);
+ return NOT_FOUND;
+ }
+
+ f = ap_pfopen(r->pool, r->filename, "r");
+
+ if (f == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
+ "file permissions deny server access: %s", r->filename);
+ return FORBIDDEN;
+ }
+
+ ap_scan_script_header_err(r, f, NULL);
+ location = ap_table_get(r->headers_out, "Location");
+
+ if (location && location[0] == '/' &&
+ ((r->status == HTTP_OK) || ap_is_HTTP_REDIRECT(r->status))) {
+
+ ap_pfclose(r->pool, f);
+
+ /* Internal redirect -- fake-up a pseudo-request */
+ r->status = HTTP_OK;
+
+ /* This redirect needs to be a GET no matter what the original
+ * method was.
+ */
+ r->method = ap_pstrdup(r->pool, "GET");
+ r->method_number = M_GET;
+
+ ap_internal_redirect_handler(location, r);
+ return OK;
+ }
+
+ ap_send_http_header(r);
+ if (!r->header_only)
+ ap_send_fd(f, r);
+
+ ap_pfclose(r->pool, f);
+ return OK;
+}
+
+static const handler_rec asis_handlers[] =
+{
+ {ASIS_MAGIC_TYPE, asis_handler},
+ {"send-as-is", asis_handler},
+ {NULL}
+};
+
+module MODULE_VAR_EXPORT asis_module =
+{
+ STANDARD_MODULE_STUFF,
+ NULL, /* initializer */
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command table */
+ asis_handlers, /* handlers */
+ NULL, /* translate_handler */
+ NULL, /* check_user_id */
+ NULL, /* check auth */
+ NULL, /* check access */
+ NULL, /* type_checker */
+ NULL, /* pre-run fixups */
+ NULL, /* logger */
+ NULL, /* header parser */
+ NULL, /* child_init */
+ NULL, /* child_exit */
+ NULL /* post read-request */
+};
diff --git a/modules/generators/mod_autoindex.c b/modules/generators/mod_autoindex.c
new file mode 100644
index 0000000000..9c28dc4086
--- /dev/null
+++ b/modules/generators/mod_autoindex.c
@@ -0,0 +1,1673 @@
+/* ====================================================================
+ * Copyright (c) 1995-1999 The Apache Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ * software must display the following acknowledgment:
+ * "This product includes software developed by the Apache Group
+ * for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * 4. The names "Apache Server" and "Apache Group" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For written permission, please contact
+ * apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ * acknowledgment:
+ * "This product includes software developed by the Apache Group
+ * for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Group and was originally based
+ * on public domain software written at the National Center for
+ * Supercomputing Applications, University of Illinois, Urbana-Champaign.
+ * For more information on the Apache Group and the Apache HTTP server
+ * project, please see .
+ *
+ */
+
+/*
+ * mod_autoindex.c: Handles the on-the-fly html index generation
+ *
+ * Rob McCool
+ * 3/23/93
+ *
+ * Adapted to Apache by rst.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "util_script.h"
+#include "fnmatch.h"
+
+module MODULE_VAR_EXPORT autoindex_module;
+
+/****************************************************************
+ *
+ * Handling configuration directives...
+ */
+
+#define HRULE 1
+#define NO_HRULE 0
+#define FRONT_MATTER 1
+#define END_MATTER 0
+
+#define FANCY_INDEXING 1 /* Indexing options */
+#define ICONS_ARE_LINKS 2
+#define SCAN_HTML_TITLES 4
+#define SUPPRESS_LAST_MOD 8
+#define SUPPRESS_SIZE 16
+#define SUPPRESS_DESC 32
+#define SUPPRESS_PREAMBLE 64
+#define SUPPRESS_COLSORT 128
+#define NO_OPTIONS 256
+
+#define K_PAD 1
+#define K_NOPAD 0
+
+#define K_NOADJUST 0
+#define K_ADJUST 1
+#define K_UNSET 2
+
+/*
+ * Define keys for sorting.
+ */
+#define K_NAME 'N' /* Sort by file name (default) */
+#define K_LAST_MOD 'M' /* Last modification date */
+#define K_SIZE 'S' /* Size (absolute, not as displayed) */
+#define K_DESC 'D' /* Description */
+
+#define D_ASCENDING 'A'
+#define D_DESCENDING 'D'
+
+/*
+ * These are the dimensions of the default icons supplied with Apache.
+ */
+#define DEFAULT_ICON_WIDTH 20
+#define DEFAULT_ICON_HEIGHT 22
+
+/*
+ * Other default dimensions.
+ */
+#define DEFAULT_NAME_WIDTH 23
+
+struct item {
+ char *type;
+ char *apply_to;
+ char *apply_path;
+ char *data;
+};
+
+typedef struct ai_desc_t {
+ char *pattern;
+ char *description;
+ int full_path;
+ int wildcards;
+} ai_desc_t;
+
+typedef struct autoindex_config_struct {
+
+ char *default_icon;
+ int opts;
+ int incremented_opts;
+ int decremented_opts;
+ int name_width;
+ int name_adjust;
+ int icon_width;
+ int icon_height;
+ char *default_order;
+
+ array_header *icon_list;
+ array_header *alt_list;
+ array_header *desc_list;
+ array_header *ign_list;
+ array_header *hdr_list;
+ array_header *rdme_list;
+
+} autoindex_config_rec;
+
+static char c_by_encoding, c_by_type, c_by_path;
+
+#define BY_ENCODING &c_by_encoding
+#define BY_TYPE &c_by_type
+#define BY_PATH &c_by_path
+
+/*
+ * Return true if the specified string refers to the parent directory (i.e.,
+ * matches ".." or "../"). Hopefully this one call is significantly less
+ * expensive than multiple strcmp() calls.
+ */
+static ap_inline int is_parent(const char *name)
+{
+ /*
+ * Now, IFF the first two bytes are dots, and the third byte is either
+ * EOS (\0) or a slash followed by EOS, we have a match.
+ */
+ if (((name[0] == '.') && (name[1] == '.'))
+ && ((name[2] == '\0')
+ || ((name[2] == '/') && (name[3] == '\0')))) {
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * This routine puts the standard HTML header at the top of the index page.
+ * We include the DOCTYPE because we may be using features therefrom (i.e.,
+ * HEIGHT and WIDTH attributes on the icons if we're FancyIndexing).
+ */
+static void emit_preamble(request_rec *r, char *title)
+{
+ ap_rvputs(r, DOCTYPE_HTML_3_2,
+ "\n \n Index of ", title,
+ "\n \n \n", NULL);
+}
+
+static void push_item(array_header *arr, char *type, char *to, char *path,
+ char *data)
+{
+ struct item *p = (struct item *) ap_push_array(arr);
+
+ if (!to) {
+ to = "";
+ }
+ if (!path) {
+ path = "";
+ }
+
+ p->type = type;
+ p->data = data ? ap_pstrdup(arr->pool, data) : NULL;
+ p->apply_path = ap_pstrcat(arr->pool, path, "*", NULL);
+
+ if ((type == BY_PATH) && (!ap_is_matchexp(to))) {
+ p->apply_to = ap_pstrcat(arr->pool, "*", to, NULL);
+ }
+ else if (to) {
+ p->apply_to = ap_pstrdup(arr->pool, to);
+ }
+ else {
+ p->apply_to = NULL;
+ }
+}
+
+static const char *add_alt(cmd_parms *cmd, void *d, char *alt, char *to)
+{
+ if (cmd->info == BY_PATH) {
+ if (!strcmp(to, "**DIRECTORY**")) {
+ to = "^^DIRECTORY^^";
+ }
+ }
+ if (cmd->info == BY_ENCODING) {
+ ap_str_tolower(to);
+ }
+
+ push_item(((autoindex_config_rec *) d)->alt_list, cmd->info, to,
+ cmd->path, alt);
+ return NULL;
+}
+
+static const char *add_icon(cmd_parms *cmd, void *d, char *icon, char *to)
+{
+ char *iconbak = ap_pstrdup(cmd->pool, icon);
+
+ if (icon[0] == '(') {
+ char *alt;
+ char *cl = strchr(iconbak, ')');
+
+ if (cl == NULL) {
+ return "missing closing paren";
+ }
+ alt = ap_getword_nc(cmd->pool, &iconbak, ',');
+ *cl = '\0'; /* Lose closing paren */
+ add_alt(cmd, d, &alt[1], to);
+ }
+ if (cmd->info == BY_PATH) {
+ if (!strcmp(to, "**DIRECTORY**")) {
+ to = "^^DIRECTORY^^";
+ }
+ }
+ if (cmd->info == BY_ENCODING) {
+ ap_str_tolower(to);
+ }
+
+ push_item(((autoindex_config_rec *) d)->icon_list, cmd->info, to,
+ cmd->path, iconbak);
+ return NULL;
+}
+
+/*
+ * Add description text for a filename pattern. If the pattern has
+ * wildcards already (or we need to add them), add leading and
+ * trailing wildcards to it to ensure substring processing. If the
+ * pattern contains a '/' anywhere, force wildcard matching mode,
+ * add a slash to the prefix so that "bar/bletch" won't be matched
+ * by "foobar/bletch", and make a note that there's a delimiter;
+ * the matching routine simplifies to just the actual filename
+ * whenever it can. This allows definitions in parent directories
+ * to be made for files in subordinate ones using relative paths.
+ */
+
+/*
+ * Absent a strcasestr() function, we have to force wildcards on
+ * systems for which "AAA" and "aaa" mean the same file.
+ */
+#ifdef CASE_BLIND_FILESYSTEM
+#define WILDCARDS_REQUIRED 1
+#else
+#define WILDCARDS_REQUIRED 0
+#endif
+
+static const char *add_desc(cmd_parms *cmd, void *d, char *desc, char *to)
+{
+ autoindex_config_rec *dcfg = (autoindex_config_rec *) d;
+ ai_desc_t *desc_entry;
+ char *prefix = "";
+
+ desc_entry = (ai_desc_t *) ap_push_array(dcfg->desc_list);
+ desc_entry->full_path = (strchr(to, '/') == NULL) ? 0 : 1;
+ desc_entry->wildcards = (WILDCARDS_REQUIRED
+ || desc_entry->full_path
+ || ap_is_fnmatch(to));
+ if (desc_entry->wildcards) {
+ prefix = desc_entry->full_path ? "*/" : "*";
+ desc_entry->pattern = ap_pstrcat(dcfg->desc_list->pool,
+ prefix, to, "*", NULL);
+ }
+ else {
+ desc_entry->pattern = ap_pstrdup(dcfg->desc_list->pool, to);
+ }
+ desc_entry->description = ap_pstrdup(dcfg->desc_list->pool, desc);
+ return NULL;
+}
+
+static const char *add_ignore(cmd_parms *cmd, void *d, char *ext)
+{
+ push_item(((autoindex_config_rec *) d)->ign_list, 0, ext, cmd->path, NULL);
+ return NULL;
+}
+
+static const char *add_header(cmd_parms *cmd, void *d, char *name)
+{
+ push_item(((autoindex_config_rec *) d)->hdr_list, 0, NULL, cmd->path,
+ name);
+ return NULL;
+}
+
+static const char *add_readme(cmd_parms *cmd, void *d, char *name)
+{
+ push_item(((autoindex_config_rec *) d)->rdme_list, 0, NULL, cmd->path,
+ name);
+ return NULL;
+}
+
+/* A legacy directive, FancyIndexing is superseded by the IndexOptions
+ * keyword. But for compatibility..
+ */
+static const char *fancy_indexing(cmd_parms *cmd, void *d, int arg)
+{
+ int curopts;
+ int newopts;
+ autoindex_config_rec *cfg;
+
+ cfg = (autoindex_config_rec *) d;
+ curopts = cfg->opts;
+ if (curopts & NO_OPTIONS) {
+ return "FancyIndexing directive conflicts with existing "
+ "IndexOptions None";
+ }
+ newopts = (arg ? (curopts | FANCY_INDEXING) : (curopts & ~FANCY_INDEXING));
+ cfg->opts = newopts;
+ return NULL;
+}
+
+static const char *add_opts(cmd_parms *cmd, void *d, const char *optstr)
+{
+ char *w;
+ int opts;
+ int opts_add;
+ int opts_remove;
+ char action;
+ autoindex_config_rec *d_cfg = (autoindex_config_rec *) d;
+
+ opts = d_cfg->opts;
+ opts_add = d_cfg->incremented_opts;
+ opts_remove = d_cfg->decremented_opts;
+ while (optstr[0]) {
+ int option = 0;
+
+ w = ap_getword_conf(cmd->pool, &optstr);
+ if ((*w == '+') || (*w == '-')) {
+ action = *(w++);
+ }
+ else {
+ action = '\0';
+ }
+ if (!strcasecmp(w, "FancyIndexing")) {
+ option = FANCY_INDEXING;
+ }
+ else if (!strcasecmp(w, "IconsAreLinks")) {
+ option = ICONS_ARE_LINKS;
+ }
+ else if (!strcasecmp(w, "ScanHTMLTitles")) {
+ option = SCAN_HTML_TITLES;
+ }
+ else if (!strcasecmp(w, "SuppressLastModified")) {
+ option = SUPPRESS_LAST_MOD;
+ }
+ else if (!strcasecmp(w, "SuppressSize")) {
+ option = SUPPRESS_SIZE;
+ }
+ else if (!strcasecmp(w, "SuppressDescription")) {
+ option = SUPPRESS_DESC;
+ }
+ else if (!strcasecmp(w, "SuppressHTMLPreamble")) {
+ option = SUPPRESS_PREAMBLE;
+ }
+ else if (!strcasecmp(w, "SuppressColumnSorting")) {
+ option = SUPPRESS_COLSORT;
+ }
+ else if (!strcasecmp(w, "None")) {
+ if (action != '\0') {
+ return "Cannot combine '+' or '-' with 'None' keyword";
+ }
+ opts = NO_OPTIONS;
+ opts_add = 0;
+ opts_remove = 0;
+ }
+ else if (!strcasecmp(w, "IconWidth")) {
+ if (action != '-') {
+ d_cfg->icon_width = DEFAULT_ICON_WIDTH;
+ }
+ else {
+ d_cfg->icon_width = 0;
+ }
+ }
+ else if (!strncasecmp(w, "IconWidth=", 10)) {
+ if (action == '-') {
+ return "Cannot combine '-' with IconWidth=n";
+ }
+ d_cfg->icon_width = atoi(&w[10]);
+ }
+ else if (!strcasecmp(w, "IconHeight")) {
+ if (action != '-') {
+ d_cfg->icon_height = DEFAULT_ICON_HEIGHT;
+ }
+ else {
+ d_cfg->icon_height = 0;
+ }
+ }
+ else if (!strncasecmp(w, "IconHeight=", 11)) {
+ if (action == '-') {
+ return "Cannot combine '-' with IconHeight=n";
+ }
+ d_cfg->icon_height = atoi(&w[11]);
+ }
+ else if (!strcasecmp(w, "NameWidth")) {
+ if (action != '-') {
+ return "NameWidth with no value may only appear as "
+ "'-NameWidth'";
+ }
+ d_cfg->name_width = DEFAULT_NAME_WIDTH;
+ d_cfg->name_adjust = K_NOADJUST;
+ }
+ else if (!strncasecmp(w, "NameWidth=", 10)) {
+ if (action == '-') {
+ return "Cannot combine '-' with NameWidth=n";
+ }
+ if (w[10] == '*') {
+ d_cfg->name_adjust = K_ADJUST;
+ }
+ else {
+ int width = atoi(&w[10]);
+
+ if (width < 5) {
+ return "NameWidth value must be greater than 5";
+ }
+ d_cfg->name_width = width;
+ d_cfg->name_adjust = K_NOADJUST;
+ }
+ }
+ else {
+ return "Invalid directory indexing option";
+ }
+ if (action == '\0') {
+ opts |= option;
+ opts_add = 0;
+ opts_remove = 0;
+ }
+ else if (action == '+') {
+ opts_add |= option;
+ opts_remove &= ~option;
+ }
+ else {
+ opts_remove |= option;
+ opts_add &= ~option;
+ }
+ }
+ if ((opts & NO_OPTIONS) && (opts & ~NO_OPTIONS)) {
+ return "Cannot combine other IndexOptions keywords with 'None'";
+ }
+ d_cfg->incremented_opts = opts_add;
+ d_cfg->decremented_opts = opts_remove;
+ d_cfg->opts = opts;
+ return NULL;
+}
+
+static const char *set_default_order(cmd_parms *cmd, void *m, char *direction,
+ char *key)
+{
+ char temp[4];
+ autoindex_config_rec *d_cfg = (autoindex_config_rec *) m;
+
+ ap_cpystrn(temp, "k=d", sizeof(temp));
+ if (!strcasecmp(direction, "Ascending")) {
+ temp[2] = D_ASCENDING;
+ }
+ else if (!strcasecmp(direction, "Descending")) {
+ temp[2] = D_DESCENDING;
+ }
+ else {
+ return "First keyword must be 'Ascending' or 'Descending'";
+ }
+
+ if (!strcasecmp(key, "Name")) {
+ temp[0] = K_NAME;
+ }
+ else if (!strcasecmp(key, "Date")) {
+ temp[0] = K_LAST_MOD;
+ }
+ else if (!strcasecmp(key, "Size")) {
+ temp[0] = K_SIZE;
+ }
+ else if (!strcasecmp(key, "Description")) {
+ temp[0] = K_DESC;
+ }
+ else {
+ return "Second keyword must be 'Name', 'Date', 'Size', or "
+ "'Description'";
+ }
+
+ if (d_cfg->default_order == NULL) {
+ d_cfg->default_order = ap_palloc(cmd->pool, 4);
+ d_cfg->default_order[3] = '\0';
+ }
+ ap_cpystrn(d_cfg->default_order, temp, sizeof(temp));
+ return NULL;
+}
+
+#define DIR_CMD_PERMS OR_INDEXES
+
+static const command_rec autoindex_cmds[] =
+{
+ {"AddIcon", add_icon, BY_PATH, DIR_CMD_PERMS, ITERATE2,
+ "an icon URL followed by one or more filenames"},
+ {"AddIconByType", add_icon, BY_TYPE, DIR_CMD_PERMS, ITERATE2,
+ "an icon URL followed by one or more MIME types"},
+ {"AddIconByEncoding", add_icon, BY_ENCODING, DIR_CMD_PERMS, ITERATE2,
+ "an icon URL followed by one or more content encodings"},
+ {"AddAlt", add_alt, BY_PATH, DIR_CMD_PERMS, ITERATE2,
+ "alternate descriptive text followed by one or more filenames"},
+ {"AddAltByType", add_alt, BY_TYPE, DIR_CMD_PERMS, ITERATE2,
+ "alternate descriptive text followed by one or more MIME types"},
+ {"AddAltByEncoding", add_alt, BY_ENCODING, DIR_CMD_PERMS, ITERATE2,
+ "alternate descriptive text followed by one or more content encodings"},
+ {"IndexOptions", add_opts, NULL, DIR_CMD_PERMS, RAW_ARGS,
+ "one or more index options"},
+ {"IndexOrderDefault", set_default_order, NULL, DIR_CMD_PERMS, TAKE2,
+ "{Ascending,Descending} {Name,Size,Description,Date}"},
+ {"IndexIgnore", add_ignore, NULL, DIR_CMD_PERMS, ITERATE,
+ "one or more file extensions"},
+ {"AddDescription", add_desc, BY_PATH, DIR_CMD_PERMS, ITERATE2,
+ "Descriptive text followed by one or more filenames"},
+ {"HeaderName", add_header, NULL, DIR_CMD_PERMS, TAKE1, "a filename"},
+ {"ReadmeName", add_readme, NULL, DIR_CMD_PERMS, TAKE1, "a filename"},
+ {"FancyIndexing", fancy_indexing, NULL, DIR_CMD_PERMS, FLAG,
+ "Limited to 'on' or 'off' (superseded by IndexOptions FancyIndexing)"},
+ {"DefaultIcon", ap_set_string_slot,
+ (void *) XtOffsetOf(autoindex_config_rec, default_icon),
+ DIR_CMD_PERMS, TAKE1, "an icon URL"},
+ {NULL}
+};
+
+static void *create_autoindex_config(pool *p, char *dummy)
+{
+ autoindex_config_rec *new =
+ (autoindex_config_rec *) ap_pcalloc(p, sizeof(autoindex_config_rec));
+
+ new->icon_width = 0;
+ new->icon_height = 0;
+ new->name_width = DEFAULT_NAME_WIDTH;
+ new->name_adjust = K_UNSET;
+ new->icon_list = ap_make_array(p, 4, sizeof(struct item));
+ new->alt_list = ap_make_array(p, 4, sizeof(struct item));
+ new->desc_list = ap_make_array(p, 4, sizeof(ai_desc_t));
+ new->ign_list = ap_make_array(p, 4, sizeof(struct item));
+ new->hdr_list = ap_make_array(p, 4, sizeof(struct item));
+ new->rdme_list = ap_make_array(p, 4, sizeof(struct item));
+ new->opts = 0;
+ new->incremented_opts = 0;
+ new->decremented_opts = 0;
+ new->default_order = NULL;
+
+ return (void *) new;
+}
+
+static void *merge_autoindex_configs(pool *p, void *basev, void *addv)
+{
+ autoindex_config_rec *new;
+ autoindex_config_rec *base = (autoindex_config_rec *) basev;
+ autoindex_config_rec *add = (autoindex_config_rec *) addv;
+
+ new = (autoindex_config_rec *) ap_pcalloc(p, sizeof(autoindex_config_rec));
+ new->default_icon = add->default_icon ? add->default_icon
+ : base->default_icon;
+ new->icon_height = add->icon_height ? add->icon_height : base->icon_height;
+ new->icon_width = add->icon_width ? add->icon_width : base->icon_width;
+
+ new->alt_list = ap_append_arrays(p, add->alt_list, base->alt_list);
+ new->ign_list = ap_append_arrays(p, add->ign_list, base->ign_list);
+ new->hdr_list = ap_append_arrays(p, add->hdr_list, base->hdr_list);
+ new->desc_list = ap_append_arrays(p, add->desc_list, base->desc_list);
+ new->icon_list = ap_append_arrays(p, add->icon_list, base->icon_list);
+ new->rdme_list = ap_append_arrays(p, add->rdme_list, base->rdme_list);
+ if (add->opts & NO_OPTIONS) {
+ /*
+ * If the current directory says 'no options' then we also
+ * clear any incremental mods from being inheritable further down.
+ */
+ new->opts = NO_OPTIONS;
+ new->incremented_opts = 0;
+ new->decremented_opts = 0;
+ }
+ else {
+ /*
+ * If there were any non-incremental options selected for
+ * this directory, they dominate and we don't inherit *anything.*
+ * Contrariwise, we *do* inherit if the only settings here are
+ * incremental ones.
+ */
+ if (add->opts == 0) {
+ new->incremented_opts = (base->incremented_opts
+ | add->incremented_opts)
+ & ~add->decremented_opts;
+ new->decremented_opts = (base->decremented_opts
+ | add->decremented_opts);
+ /*
+ * We may have incremental settings, so make sure we don't
+ * inadvertently inherit an IndexOptions None from above.
+ */
+ new->opts = (base->opts & ~NO_OPTIONS);
+ }
+ else {
+ /*
+ * There are local non-incremental settings, which clear
+ * all inheritance from above. They *are* the new base settings.
+ */
+ new->opts = add->opts;;
+ }
+ /*
+ * We're guaranteed that there'll be no overlap between
+ * the add-options and the remove-options.
+ */
+ new->opts |= new->incremented_opts;
+ new->opts &= ~new->decremented_opts;
+ }
+ /*
+ * Inherit the NameWidth settings if there aren't any specific to
+ * the new location; otherwise we'll end up using the defaults set in the
+ * config-rec creation routine.
+ */
+ if (add->name_adjust == K_UNSET) {
+ new->name_width = base->name_width;
+ new->name_adjust = base->name_adjust;
+ }
+ else {
+ new->name_width = add->name_width;
+ new->name_adjust = add->name_adjust;
+ }
+
+ new->default_order = (add->default_order != NULL)
+ ? add->default_order : base->default_order;
+ return new;
+}
+
+/****************************************************************
+ *
+ * Looking things up in config entries...
+ */
+
+/* Structure used to hold entries when we're actually building an index */
+
+struct ent {
+ char *name;
+ char *icon;
+ char *alt;
+ char *desc;
+ off_t size;
+ time_t lm;
+ struct ent *next;
+ int ascending;
+ char key;
+};
+
+static char *find_item(request_rec *r, array_header *list, int path_only)
+{
+ const char *content_type = r->content_type;
+ const char *content_encoding = r->content_encoding;
+ char *path = r->filename;
+
+ struct item *items = (struct item *) list->elts;
+ int i;
+
+ for (i = 0; i < list->nelts; ++i) {
+ struct item *p = &items[i];
+
+ /* Special cased for ^^DIRECTORY^^ and ^^BLANKICON^^ */
+ if ((path[0] == '^') || (!ap_strcmp_match(path, p->apply_path))) {
+ if (!*(p->apply_to)) {
+ return p->data;
+ }
+ else if (p->type == BY_PATH || path[0] == '^') {
+ if (!ap_strcmp_match(path, p->apply_to)) {
+ return p->data;
+ }
+ }
+ else if (!path_only) {
+ if (!content_encoding) {
+ if (p->type == BY_TYPE) {
+ if (content_type
+ && !ap_strcasecmp_match(content_type,
+ p->apply_to)) {
+ return p->data;
+ }
+ }
+ }
+ else {
+ if (p->type == BY_ENCODING) {
+ if (!ap_strcasecmp_match(content_encoding,
+ p->apply_to)) {
+ return p->data;
+ }
+ }
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+#define find_icon(d,p,t) find_item(p,d->icon_list,t)
+#define find_alt(d,p,t) find_item(p,d->alt_list,t)
+#define find_header(d,p) find_item(p,d->hdr_list,0)
+#define find_readme(d,p) find_item(p,d->rdme_list,0)
+
+static char *find_default_icon(autoindex_config_rec *d, char *bogus_name)
+{
+ request_rec r;
+
+ /* Bleah. I tried to clean up find_item, and it lead to this bit
+ * of ugliness. Note that the fields initialized are precisely
+ * those that find_item looks at...
+ */
+
+ r.filename = bogus_name;
+ r.content_type = r.content_encoding = NULL;
+
+ return find_item(&r, d->icon_list, 1);
+}
+
+/*
+ * Look through the list of pattern/description pairs and return the first one
+ * if any) that matches the filename in the request. If multiple patterns
+ * match, only the first one is used; since the order in the array is the
+ * same as the order in which directives were processed, earlier matching
+ * directives will dominate.
+ */
+
+#ifdef CASE_BLIND_FILESYSTEM
+#define MATCH_FLAGS FNM_CASE_BLIND
+#else
+#define MATCH_FLAGS 0
+#endif
+
+static char *find_desc(autoindex_config_rec *dcfg, request_rec *r)
+{
+ int i;
+ ai_desc_t *list = (ai_desc_t *) dcfg->desc_list->elts;
+ const char *filename_full = r->filename;
+ const char *filename_only;
+ const char *filename;
+
+ /*
+ * If the filename includes a path, extract just the name itself
+ * for the simple matches.
+ */
+ if ((filename_only = strrchr(filename_full, '/')) == NULL) {
+ filename_only = filename_full;
+ }
+ else {
+ filename_only++;
+ }
+ for (i = 0; i < dcfg->desc_list->nelts; ++i) {
+ ai_desc_t *tuple = &list[i];
+ int found;
+
+ /*
+ * Only use the full-path filename if the pattern contains '/'s.
+ */
+ filename = (tuple->full_path) ? filename_full : filename_only;
+ /*
+ * Make the comparison using the cheapest method; only do
+ * wildcard checking if we must.
+ */
+ if (tuple->wildcards) {
+ found = (ap_fnmatch(tuple->pattern, filename, MATCH_FLAGS) == 0);
+ }
+ else {
+ found = (strstr(filename, tuple->pattern) != NULL);
+ }
+ if (found) {
+ return tuple->description;
+ }
+ }
+ return NULL;
+}
+
+static int ignore_entry(autoindex_config_rec *d, char *path)
+{
+ array_header *list = d->ign_list;
+ struct item *items = (struct item *) list->elts;
+ char *tt;
+ int i;
+
+ if ((tt = strrchr(path, '/')) == NULL) {
+ tt = path;
+ }
+ else {
+ tt++;
+ }
+
+ for (i = 0; i < list->nelts; ++i) {
+ struct item *p = &items[i];
+ char *ap;
+
+ if ((ap = strrchr(p->apply_to, '/')) == NULL) {
+ ap = p->apply_to;
+ }
+ else {
+ ap++;
+ }
+
+#ifndef CASE_BLIND_FILESYSTEM
+ if (!ap_strcmp_match(path, p->apply_path)
+ && !ap_strcmp_match(tt, ap)) {
+ return 1;
+ }
+#else /* !CASE_BLIND_FILESYSTEM */
+ /*
+ * On some platforms, the match must be case-blind. This is really
+ * a factor of the filesystem involved, but we can't detect that
+ * reliably - so we have to granularise at the OS level.
+ */
+ if (!ap_strcasecmp_match(path, p->apply_path)
+ && !ap_strcasecmp_match(tt, ap)) {
+ return 1;
+ }
+#endif /* !CASE_BLIND_FILESYSTEM */
+ }
+ return 0;
+}
+
+/*****************************************************************
+ *
+ * Actually generating output
+ */
+
+/*
+ * Elements of the emitted document:
+ * Preamble
+ * Emitted unless SUPPRESS_PREAMBLE is set AND ap_run_sub_req
+ * succeeds for the (content_type == text/html) header file.
+ * Header file
+ * Emitted if found (and able).
+ * H1 tag line
+ * Emitted if a header file is NOT emitted.
+ * Directory stuff
+ * Always emitted.
+ * HR
+ * Emitted if FANCY_INDEXING is set.
+ * Readme file
+ * Emitted if found (and able).
+ * ServerSig
+ * Emitted if ServerSignature is not Off AND a readme file
+ * is NOT emitted.
+ * Postamble
+ * Emitted unless SUPPRESS_PREAMBLE is set AND ap_run_sub_req
+ * succeeds for the (content_type == text/html) readme file.
+ */
+
+
+/*
+ * emit a plain text file
+ */
+static void do_emit_plain(request_rec *r, FILE *f)
+{
+ char buf[IOBUFSIZE + 1];
+ int i, n, c, ch;
+
+ ap_rputs("\n", r);
+ while (!feof(f)) {
+ do {
+ n = fread(buf, sizeof(char), IOBUFSIZE, f);
+ }
+ while (n == -1 && ferror(f) && errno == EINTR);
+ if (n == -1 || n == 0) {
+ break;
+ }
+ buf[n] = '\0';
+ c = 0;
+ while (c < n) {
+ for (i = c; i < n; i++) {
+ if (buf[i] == '<' || buf[i] == '>' || buf[i] == '&') {
+ break;
+ }
+ }
+ ch = buf[i];
+ buf[i] = '\0';
+ ap_rputs(&buf[c], r);
+ if (ch == '<') {
+ ap_rputs("<", r);
+ }
+ else if (ch == '>') {
+ ap_rputs(">", r);
+ }
+ else if (ch == '&') {
+ ap_rputs("&", r);
+ }
+ c = i + 1;
+ }
+ }
+ ap_rputs("
\n", r);
+}
+
+/*
+ * Handle the preamble through the H1 tag line, inclusive. Locate
+ * the file with a subrequests. Process text/html documents by actually
+ * running the subrequest; text/xxx documents get copied verbatim,
+ * and any other content type is ignored. This means that a non-text
+ * document (such as HEADER.gif) might get multiviewed as the result
+ * instead of a text document, meaning nothing will be displayed, but
+ * oh well.
+ */
+static void emit_head(request_rec *r, char *header_fname, int suppress_amble,
+ char *title)
+{
+ FILE *f;
+ request_rec *rr = NULL;
+ int emit_amble = 1;
+ int emit_H1 = 1;
+
+ /*
+ * If there's a header file, send a subrequest to look for it. If it's
+ * found and a text file, handle it -- otherwise fall through and
+ * pretend there's nothing there.
+ */
+ if ((header_fname != NULL)
+ && (rr = ap_sub_req_lookup_uri(header_fname, r))
+ && (rr->status == HTTP_OK)
+ && (rr->filename != NULL)
+ && S_ISREG(rr->finfo.st_mode)) {
+ /*
+ * Check for the two specific cases we allow: text/html and
+ * text/anything-else. The former is allowed to be processed for
+ * SSIs.
+ */
+ if (rr->content_type != NULL) {
+ if (!strcasecmp(ap_field_noparam(r->pool, rr->content_type),
+ "text/html")) {
+ /* Hope everything will work... */
+ emit_amble = 0;
+ emit_H1 = 0;
+
+ if (! suppress_amble) {
+ emit_preamble(r, title);
+ }
+ /*
+ * If there's a problem running the subrequest, display the
+ * preamble if we didn't do it before -- the header file
+ * didn't get displayed.
+ */
+ if (ap_run_sub_req(rr) != OK) {
+ /* It didn't work */
+ emit_amble = suppress_amble;
+ emit_H1 = 1;
+ }
+ }
+ else if (!strncasecmp("text/", rr->content_type, 5)) {
+ /*
+ * If we can open the file, prefix it with the preamble
+ * regardless; since we'll be sending a block around
+ * the file's contents, any HTML header it had won't end up
+ * where it belongs.
+ */
+ if ((f = ap_pfopen(r->pool, rr->filename, "r")) != 0) {
+ emit_preamble(r, title);
+ emit_amble = 0;
+ do_emit_plain(r, f);
+ ap_pfclose(r->pool, f);
+ emit_H1 = 0;
+ }
+ }
+ }
+ }
+
+ if (emit_amble) {
+ emit_preamble(r, title);
+ }
+ if (emit_H1) {
+ ap_rvputs(r, "Index of ", title, "
\n", NULL);
+ }
+ if (rr != NULL) {
+ ap_destroy_sub_req(rr);
+ }
+}
+
+
+/*
+ * Handle the Readme file through the postamble, inclusive. Locate
+ * the file with a subrequests. Process text/html documents by actually
+ * running the subrequest; text/xxx documents get copied verbatim,
+ * and any other content type is ignored. This means that a non-text
+ * document (such as FOOTER.gif) might get multiviewed as the result
+ * instead of a text document, meaning nothing will be displayed, but
+ * oh well.
+ */
+static void emit_tail(request_rec *r, char *readme_fname, int suppress_amble)
+{
+ FILE *f;
+ request_rec *rr = NULL;
+ int suppress_post = 0;
+ int suppress_sig = 0;
+
+ /*
+ * If there's a readme file, send a subrequest to look for it. If it's
+ * found and a text file, handle it -- otherwise fall through and
+ * pretend there's nothing there.
+ */
+ if ((readme_fname != NULL)
+ && (rr = ap_sub_req_lookup_uri(readme_fname, r))
+ && (rr->status == HTTP_OK)
+ && (rr->filename != NULL)
+ && S_ISREG(rr->finfo.st_mode)) {
+ /*
+ * Check for the two specific cases we allow: text/html and
+ * text/anything-else. The former is allowed to be processed for
+ * SSIs.
+ */
+ if (rr->content_type != NULL) {
+ if (!strcasecmp(ap_field_noparam(r->pool, rr->content_type),
+ "text/html")) {
+ if (ap_run_sub_req(rr) == OK) {
+ /* worked... */
+ suppress_sig = 1;
+ suppress_post = suppress_amble;
+ }
+ }
+ else if (!strncasecmp("text/", rr->content_type, 5)) {
+ /*
+ * If we can open the file, suppress the signature.
+ */
+ if ((f = ap_pfopen(r->pool, rr->filename, "r")) != 0) {
+ do_emit_plain(r, f);
+ ap_pfclose(r->pool, f);
+ suppress_sig = 1;
+ }
+ }
+ }
+ }
+
+ if (!suppress_sig) {
+ ap_rputs(ap_psignature("", r), r);
+ }
+ if (!suppress_post) {
+ ap_rputs("\n", r);
+ }
+ if (rr != NULL) {
+ ap_destroy_sub_req(rr);
+ }
+}
+
+
+static char *find_title(request_rec *r)
+{
+ char titlebuf[MAX_STRING_LEN], *find = "";
+ FILE *thefile = NULL;
+ int x, y, n, p;
+
+ if (r->status != HTTP_OK) {
+ return NULL;
+ }
+ if ((r->content_type != NULL)
+ && (!strcasecmp(ap_field_noparam(r->pool, r->content_type),
+ "text/html")
+ || !strcmp(r->content_type, INCLUDES_MAGIC_TYPE))
+ && !r->content_encoding) {
+ if (!(thefile = ap_pfopen(r->pool, r->filename, "r"))) {
+ return NULL;
+ }
+ n = fread(titlebuf, sizeof(char), MAX_STRING_LEN - 1, thefile);
+ if (n <= 0) {
+ ap_pfclose(r->pool, thefile);
+ return NULL;
+ }
+ titlebuf[n] = '\0';
+ for (x = 0, p = 0; titlebuf[x]; x++) {
+ if (ap_toupper(titlebuf[x]) == find[p]) {
+ if (!find[++p]) {
+ if ((p = ap_ind(&titlebuf[++x], '<')) != -1) {
+ titlebuf[x + p] = '\0';
+ }
+ /* Scan for line breaks for Tanmoy's secretary */
+ for (y = x; titlebuf[y]; y++) {
+ if ((titlebuf[y] == CR) || (titlebuf[y] == LF)) {
+ if (y == x) {
+ x++;
+ }
+ else {
+ titlebuf[y] = ' ';
+ }
+ }
+ }
+ ap_pfclose(r->pool, thefile);
+ return ap_pstrdup(r->pool, &titlebuf[x]);
+ }
+ }
+ else {
+ p = 0;
+ }
+ }
+ ap_pfclose(r->pool, thefile);
+ }
+ return NULL;
+}
+
+static struct ent *make_autoindex_entry(char *name, int autoindex_opts,
+ autoindex_config_rec *d,
+ request_rec *r, char keyid,
+ char direction)
+{
+ struct ent *p;
+
+ if ((name[0] == '.') && (!name[1])) {
+ return (NULL);
+ }
+
+ if (ignore_entry(d, ap_make_full_path(r->pool, r->filename, name))) {
+ return (NULL);
+ }
+
+ p = (struct ent *) ap_pcalloc(r->pool, sizeof(struct ent));
+ p->name = ap_pstrdup(r->pool, name);
+ p->size = -1;
+ p->icon = NULL;
+ p->alt = NULL;
+ p->desc = NULL;
+ p->lm = -1;
+ p->key = ap_toupper(keyid);
+ p->ascending = (ap_toupper(direction) == D_ASCENDING);
+
+ if (autoindex_opts & FANCY_INDEXING) {
+ request_rec *rr = ap_sub_req_lookup_file(name, r);
+
+ if (rr->finfo.st_mode != 0) {
+ p->lm = rr->finfo.st_mtime;
+ if (S_ISDIR(rr->finfo.st_mode)) {
+ if (!(p->icon = find_icon(d, rr, 1))) {
+ p->icon = find_default_icon(d, "^^DIRECTORY^^");
+ }
+ if (!(p->alt = find_alt(d, rr, 1))) {
+ p->alt = "DIR";
+ }
+ p->size = -1;
+ p->name = ap_pstrcat(r->pool, name, "/", NULL);
+ }
+ else {
+ p->icon = find_icon(d, rr, 0);
+ p->alt = find_alt(d, rr, 0);
+ p->size = rr->finfo.st_size;
+ }
+ }
+
+ p->desc = find_desc(d, rr);
+
+ if ((!p->desc) && (autoindex_opts & SCAN_HTML_TITLES)) {
+ p->desc = ap_pstrdup(r->pool, find_title(rr));
+ }
+
+ ap_destroy_sub_req(rr);
+ }
+ /*
+ * We don't need to take any special action for the file size key. If
+ * we did, it would go here.
+ */
+ if (keyid == K_LAST_MOD) {
+ if (p->lm < 0) {
+ p->lm = 0;
+ }
+ }
+ return (p);
+}
+
+static char *terminate_description(autoindex_config_rec *d, char *desc,
+ int autoindex_opts)
+{
+ int maxsize = 23;
+ register int x;
+
+ if (autoindex_opts & SUPPRESS_LAST_MOD) {
+ maxsize += 19;
+ }
+ if (autoindex_opts & SUPPRESS_SIZE) {
+ maxsize += 7;
+ }
+
+ for (x = 0; desc[x] && (maxsize > 0 || desc[x]=='<'); x++) {
+ if (desc[x] == '<') {
+ while (desc[x] != '>') {
+ if (!desc[x]) {
+ maxsize = 0;
+ break;
+ }
+ ++x;
+ }
+ }
+ else if (desc[x] == '&') {
+ /* entities like ä count as one character */
+ --maxsize;
+ for ( ; desc[x] != ';'; ++x) {
+ if (desc[x] == '\0') {
+ maxsize = 0;
+ break;
+ }
+ }
+ }
+ else {
+ --maxsize;
+ }
+ }
+ if (!maxsize && desc[x] != '\0') {
+ desc[x - 1] = '>'; /* Grump. */
+ desc[x] = '\0'; /* Double Grump! */
+ }
+ return desc;
+}
+
+/*
+ * Emit the anchor for the specified field. If a field is the key for the
+ * current request, the link changes its meaning to reverse the order when
+ * selected again. Non-active fields always start in ascending order.
+ */
+static void emit_link(request_rec *r, char *anchor, char fname, char curkey,
+ char curdirection, int nosort)
+{
+ char qvalue[5];
+ int reverse;
+
+ if (!nosort) {
+ qvalue[0] = '?';
+ qvalue[1] = fname;
+ qvalue[2] = '=';
+ qvalue[4] = '\0';
+ reverse = ((curkey == fname) && (curdirection == D_ASCENDING));
+ qvalue[3] = reverse ? D_DESCENDING : D_ASCENDING;
+ ap_rvputs(r, "", anchor, "", NULL);
+ }
+ else {
+ ap_rputs(anchor, r);
+ }
+}
+
+static void output_directories(struct ent **ar, int n,
+ autoindex_config_rec *d, request_rec *r,
+ int autoindex_opts, char keyid, char direction)
+{
+ int x;
+ char *name = r->uri;
+ char *tp;
+ int static_columns = (autoindex_opts & SUPPRESS_COLSORT);
+ pool *scratch = ap_make_sub_pool(r->pool);
+ int name_width;
+ char *name_scratch;
+ char *pad_scratch;
+
+ if (name[0] == '\0') {
+ name = "/";
+ }
+
+ name_width = d->name_width;
+ if (d->name_adjust == K_ADJUST) {
+ for (x = 0; x < n; x++) {
+ int t = strlen(ar[x]->name);
+ if (t > name_width) {
+ name_width = t;
+ }
+ }
+ }
+ name_scratch = ap_palloc(r->pool, name_width + 1);
+ pad_scratch = ap_palloc(r->pool, name_width + 1);
+ memset(pad_scratch, ' ', name_width);
+ pad_scratch[name_width] = '\0';
+
+ if (autoindex_opts & FANCY_INDEXING) {
+ ap_rputs("", r);
+ if ((tp = find_default_icon(d, "^^BLANKICON^^"))) {
+ ap_rvputs(r, "
icon_width && d->icon_height) {
+ ap_rprintf
+ (
+ r,
+ " HEIGHT=\"%d\" WIDTH=\"%d\"",
+ d->icon_height,
+ d->icon_width
+ );
+ }
+ ap_rputs("> ", r);
+ }
+ emit_link(r, "Name", K_NAME, keyid, direction, static_columns);
+ ap_rputs(pad_scratch + 4, r);
+ /*
+ * Emit the guaranteed-at-least-one-space-between-columns byte.
+ */
+ ap_rputs(" ", r);
+ if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
+ emit_link(r, "Last modified", K_LAST_MOD, keyid, direction,
+ static_columns);
+ ap_rputs(" ", r);
+ }
+ if (!(autoindex_opts & SUPPRESS_SIZE)) {
+ emit_link(r, "Size", K_SIZE, keyid, direction, static_columns);
+ ap_rputs(" ", r);
+ }
+ if (!(autoindex_opts & SUPPRESS_DESC)) {
+ emit_link(r, "Description", K_DESC, keyid, direction,
+ static_columns);
+ }
+ ap_rputs("\n
\n", r);
+ }
+ else {
+ ap_rputs("
", r);
+ }
+
+ for (x = 0; x < n; x++) {
+ char *anchor, *t, *t2;
+ int nwidth;
+
+ ap_clear_pool(scratch);
+
+ if (is_parent(ar[x]->name)) {
+ t = ap_make_full_path(scratch, name, "../");
+ ap_getparents(t);
+ if (t[0] == '\0') {
+ t = "/";
+ }
+ t2 = "Parent Directory";
+ anchor = ap_escape_html(scratch, ap_os_escape_path(scratch, t, 0));
+ }
+ else {
+ t = ar[x]->name;
+ t2 = t;
+ anchor = ap_escape_html(scratch, ap_os_escape_path(scratch, t, 0));
+ }
+
+ if (autoindex_opts & FANCY_INDEXING) {
+ if (autoindex_opts & ICONS_ARE_LINKS) {
+ ap_rvputs(r, "", NULL);
+ }
+ if ((ar[x]->icon) || d->default_icon) {
+ ap_rvputs(r, "
icon ? ar[x]->icon
+ : d->default_icon),
+ "\" ALT=\"[", (ar[x]->alt ? ar[x]->alt : " "),
+ "]\"", NULL);
+ if (d->icon_width && d->icon_height) {
+ ap_rprintf(r, " HEIGHT=\"%d\" WIDTH=\"%d\"",
+ d->icon_height, d->icon_width);
+ }
+ ap_rputs(">", r);
+ }
+ if (autoindex_opts & ICONS_ARE_LINKS) {
+ ap_rputs("", r);
+ }
+
+ nwidth = strlen(t2);
+ if (nwidth > name_width) {
+ memcpy(name_scratch, t2, name_width - 3);
+ name_scratch[name_width - 3] = '.';
+ name_scratch[name_width - 2] = '.';
+ name_scratch[name_width - 1] = '>';
+ name_scratch[name_width] = 0;
+ t2 = name_scratch;
+ nwidth = name_width;
+ }
+ ap_rvputs(r, " ",
+ ap_escape_html(scratch, t2), "", pad_scratch + nwidth,
+ NULL);
+ /*
+ * The blank before the storm.. er, before the next field.
+ */
+ ap_rputs(" ", r);
+ if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
+ if (ar[x]->lm != -1) {
+ char time_str[MAX_STRING_LEN];
+ struct tm *ts = localtime(&ar[x]->lm);
+ strftime(time_str, MAX_STRING_LEN, "%d-%b-%Y %H:%M ", ts);
+ ap_rputs(time_str, r);
+ }
+ else {
+ /*Length="22-Feb-1998 23:42 " (see 4 lines above) */
+ ap_rputs(" ", r);
+ }
+ }
+ if (!(autoindex_opts & SUPPRESS_SIZE)) {
+ ap_send_size(ar[x]->size, r);
+ ap_rputs(" ", r);
+ }
+ if (!(autoindex_opts & SUPPRESS_DESC)) {
+ if (ar[x]->desc) {
+ ap_rputs(terminate_description(d, ar[x]->desc,
+ autoindex_opts), r);
+ }
+ }
+ }
+ else {
+ ap_rvputs(r, "- ", t2,
+ "", NULL);
+ }
+ ap_rputc('\n', r);
+ }
+ if (autoindex_opts & FANCY_INDEXING) {
+ ap_rputs("
", r);
+ }
+ else {
+ ap_rputs("", r);
+ }
+}
+
+/*
+ * Compare two file entries according to the sort criteria. The return
+ * is essentially a signum function value.
+ */
+
+static int dsortf(struct ent **e1, struct ent **e2)
+{
+ struct ent *c1;
+ struct ent *c2;
+ int result = 0;
+
+ /*
+ * First, see if either of the entries is for the parent directory.
+ * If so, that *always* sorts lower than anything else.
+ */
+ if (is_parent((*e1)->name)) {
+ return -1;
+ }
+ if (is_parent((*e2)->name)) {
+ return 1;
+ }
+ /*
+ * All of our comparisons will be of the c1 entry against the c2 one,
+ * so assign them appropriately to take care of the ordering.
+ */
+ if ((*e1)->ascending) {
+ c1 = *e1;
+ c2 = *e2;
+ }
+ else {
+ c1 = *e2;
+ c2 = *e1;
+ }
+ switch (c1->key) {
+ case K_LAST_MOD:
+ if (c1->lm > c2->lm) {
+ return 1;
+ }
+ else if (c1->lm < c2->lm) {
+ return -1;
+ }
+ break;
+ case K_SIZE:
+ if (c1->size > c2->size) {
+ return 1;
+ }
+ else if (c1->size < c2->size) {
+ return -1;
+ }
+ break;
+ case K_DESC:
+ result = strcmp(c1->desc ? c1->desc : "", c2->desc ? c2->desc : "");
+ if (result) {
+ return result;
+ }
+ break;
+ }
+ return strcmp(c1->name, c2->name);
+}
+
+
+static int index_directory(request_rec *r,
+ autoindex_config_rec *autoindex_conf)
+{
+ char *title_name = ap_escape_html(r->pool, r->uri);
+ char *title_endp;
+ char *name = r->filename;
+
+ DIR *d;
+ struct DIR_TYPE *dstruct;
+ int num_ent = 0, x;
+ struct ent *head, *p;
+ struct ent **ar = NULL;
+ const char *qstring;
+ int autoindex_opts = autoindex_conf->opts;
+ char keyid;
+ char direction;
+
+ if (!(d = ap_popendir(r->pool, name))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
+ "Can't open directory for index: %s", r->filename);
+ return HTTP_FORBIDDEN;
+ }
+
+ r->content_type = "text/html";
+
+ ap_send_http_header(r);
+
+ if (r->header_only) {
+ ap_pclosedir(r->pool, d);
+ return 0;
+ }
+ ap_hard_timeout("send directory", r);
+
+ /* Spew HTML preamble */
+
+ title_endp = title_name + strlen(title_name) - 1;
+
+ while (title_endp > title_name && *title_endp == '/') {
+ *title_endp-- = '\0';
+ }
+
+ emit_head(r, find_header(autoindex_conf, r),
+ autoindex_opts & SUPPRESS_PREAMBLE, title_name);
+
+ /*
+ * Figure out what sort of indexing (if any) we're supposed to use.
+ *
+ * If no QUERY_STRING was specified or column sorting has been
+ * explicitly disabled, we use the default specified by the
+ * IndexOrderDefault directive (if there is one); otherwise,
+ * we fall back to ascending by name.
+ */
+ qstring = r->args;
+ if ((autoindex_opts & SUPPRESS_COLSORT)
+ || ((qstring == NULL) || (*qstring == '\0'))) {
+ qstring = autoindex_conf->default_order;
+ }
+ /*
+ * If there is no specific ordering defined for this directory,
+ * default to ascending by filename.
+ */
+ if ((qstring == NULL) || (*qstring == '\0')) {
+ keyid = K_NAME;
+ direction = D_ASCENDING;
+ }
+ else {
+ keyid = *qstring;
+ ap_getword(r->pool, &qstring, '=');
+ if (qstring != '\0') {
+ direction = *qstring;
+ }
+ else {
+ direction = D_ASCENDING;
+ }
+ }
+
+ /*
+ * Since we don't know how many dir. entries there are, put them into a
+ * linked list and then arrayificate them so qsort can use them.
+ */
+ head = NULL;
+ while ((dstruct = readdir(d))) {
+ p = make_autoindex_entry(dstruct->d_name, autoindex_opts,
+ autoindex_conf, r, keyid, direction);
+ if (p != NULL) {
+ p->next = head;
+ head = p;
+ num_ent++;
+ }
+ }
+ if (num_ent > 0) {
+ ar = (struct ent **) ap_palloc(r->pool,
+ num_ent * sizeof(struct ent *));
+ p = head;
+ x = 0;
+ while (p) {
+ ar[x++] = p;
+ p = p->next;
+ }
+
+ qsort((void *) ar, num_ent, sizeof(struct ent *),
+ (int (*)(const void *, const void *)) dsortf);
+ }
+ output_directories(ar, num_ent, autoindex_conf, r, autoindex_opts, keyid,
+ direction);
+ ap_pclosedir(r->pool, d);
+
+ if (autoindex_opts & FANCY_INDEXING) {
+ ap_rputs("
\n", r);
+ }
+ emit_tail(r, find_readme(autoindex_conf, r),
+ autoindex_opts & SUPPRESS_PREAMBLE);
+
+ ap_kill_timeout(r);
+ return 0;
+}
+
+/* The formal handler... */
+
+static int handle_autoindex(request_rec *r)
+{
+ autoindex_config_rec *d;
+ int allow_opts = ap_allow_options(r);
+
+ d = (autoindex_config_rec *) ap_get_module_config(r->per_dir_config,
+ &autoindex_module);
+
+ r->allowed |= (1 << M_GET);
+ if (r->method_number != M_GET) {
+ return DECLINED;
+ }
+
+ /* OK, nothing easy. Trot out the heavy artillery... */
+
+ if (allow_opts & OPT_INDEXES) {
+ /* KLUDGE --- make the sub_req lookups happen in the right directory.
+ * Fixing this in the sub_req_lookup functions themselves is difficult,
+ * and would probably break virtual includes...
+ */
+
+ if (r->filename[strlen(r->filename) - 1] != '/') {
+ r->filename = ap_pstrcat(r->pool, r->filename, "/", NULL);
+ }
+ return index_directory(r, d);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Directory index forbidden by rule: %s", r->filename);
+ return HTTP_FORBIDDEN;
+ }
+}
+
+
+static const handler_rec autoindex_handlers[] =
+{
+ {DIR_MAGIC_TYPE, handle_autoindex},
+ {NULL}
+};
+
+module MODULE_VAR_EXPORT autoindex_module =
+{
+ STANDARD_MODULE_STUFF,
+ NULL, /* initializer */
+ create_autoindex_config, /* dir config creater */
+ merge_autoindex_configs, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ autoindex_cmds, /* command table */
+ autoindex_handlers, /* handlers */
+ NULL, /* filename translation */
+ NULL, /* check_user_id */
+ NULL, /* check auth */
+ NULL, /* check access */
+ NULL, /* type_checker */
+ NULL, /* fixups */
+ NULL, /* logger */
+ NULL, /* header parser */
+ NULL, /* child_init */
+ NULL, /* child_exit */
+ NULL /* post read-request */
+};
diff --git a/modules/generators/mod_cgi.c b/modules/generators/mod_cgi.c
new file mode 100644
index 0000000000..392fb5a1f1
--- /dev/null
+++ b/modules/generators/mod_cgi.c
@@ -0,0 +1,609 @@
+/* ====================================================================
+ * Copyright (c) 1995-1999 The Apache Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ * software must display the following acknowledgment:
+ * "This product includes software developed by the Apache Group
+ * for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * 4. The names "Apache Server" and "Apache Group" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For written permission, please contact
+ * apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ * acknowledgment:
+ * "This product includes software developed by the Apache Group
+ * for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Group and was originally based
+ * on public domain software written at the National Center for
+ * Supercomputing Applications, University of Illinois, Urbana-Champaign.
+ * For more information on the Apache Group and the Apache HTTP server
+ * project, please see .
+ *
+ */
+
+/*
+ * http_script: keeps all script-related ramblings together.
+ *
+ * Compliant to CGI/1.1 spec
+ *
+ * Adapted by rst from original NCSA code by Rob McCool
+ *
+ * Apache adds some new env vars; REDIRECT_URL and REDIRECT_QUERY_STRING for
+ * custom error responses, and DOCUMENT_ROOT because we found it useful.
+ * It also adds SERVER_ADMIN - useful for scripts to know who to mail when
+ * they fail.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "util_script.h"
+#include "http_conf_globals.h"
+
+module MODULE_VAR_EXPORT cgi_module;
+
+/* KLUDGE --- for back-combatibility, we don't have to check ExecCGI
+ * in ScriptAliased directories, which means we need to know if this
+ * request came through ScriptAlias or not... so the Alias module
+ * leaves a note for us.
+ */
+
+static int is_scriptaliased(request_rec *r)
+{
+ const char *t = ap_table_get(r->notes, "alias-forced-type");
+ return t && (!strcasecmp(t, "cgi-script"));
+}
+
+/* Configuration stuff */
+
+#define DEFAULT_LOGBYTES 10385760
+#define DEFAULT_BUFBYTES 1024
+
+typedef struct {
+ char *logname;
+ long logbytes;
+ int bufbytes;
+} cgi_server_conf;
+
+static void *create_cgi_config(pool *p, server_rec *s)
+{
+ cgi_server_conf *c =
+ (cgi_server_conf *) ap_pcalloc(p, sizeof(cgi_server_conf));
+
+ c->logname = NULL;
+ c->logbytes = DEFAULT_LOGBYTES;
+ c->bufbytes = DEFAULT_BUFBYTES;
+
+ return c;
+}
+
+static void *merge_cgi_config(pool *p, void *basev, void *overridesv)
+{
+ cgi_server_conf *base = (cgi_server_conf *) basev, *overrides = (cgi_server_conf *) overridesv;
+
+ return overrides->logname ? overrides : base;
+}
+
+static const char *set_scriptlog(cmd_parms *cmd, void *dummy, char *arg)
+{
+ server_rec *s = cmd->server;
+ cgi_server_conf *conf =
+ (cgi_server_conf *) ap_get_module_config(s->module_config, &cgi_module);
+
+ conf->logname = arg;
+ return NULL;
+}
+
+static const char *set_scriptlog_length(cmd_parms *cmd, void *dummy, char *arg)
+{
+ server_rec *s = cmd->server;
+ cgi_server_conf *conf =
+ (cgi_server_conf *) ap_get_module_config(s->module_config, &cgi_module);
+
+ conf->logbytes = atol(arg);
+ return NULL;
+}
+
+static const char *set_scriptlog_buffer(cmd_parms *cmd, void *dummy, char *arg)
+{
+ server_rec *s = cmd->server;
+ cgi_server_conf *conf =
+ (cgi_server_conf *) ap_get_module_config(s->module_config, &cgi_module);
+
+ conf->bufbytes = atoi(arg);
+ return NULL;
+}
+
+static const command_rec cgi_cmds[] =
+{
+ {"ScriptLog", set_scriptlog, NULL, RSRC_CONF, TAKE1,
+ "the name of a log for script debugging info"},
+ {"ScriptLogLength", set_scriptlog_length, NULL, RSRC_CONF, TAKE1,
+ "the maximum length (in bytes) of the script debug log"},
+ {"ScriptLogBuffer", set_scriptlog_buffer, NULL, RSRC_CONF, TAKE1,
+ "the maximum size (in bytes) to record of a POST request"},
+ {NULL}
+};
+
+static int log_scripterror(request_rec *r, cgi_server_conf * conf, int ret,
+ int show_errno, char *error)
+{
+ FILE *f;
+ struct stat finfo;
+
+ ap_log_rerror(APLOG_MARK, show_errno|APLOG_ERR, r,
+ "%s: %s", error, r->filename);
+
+ if (!conf->logname ||
+ ((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0)
+ && (finfo.st_size > conf->logbytes)) ||
+ ((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname),
+ "a")) == NULL)) {
+ return ret;
+ }
+
+ /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
+ fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri,
+ r->args ? "?" : "", r->args ? r->args : "", r->protocol);
+ /* "%% 500 /usr/local/apache/cgi-bin */
+ fprintf(f, "%%%% %d %s\n", ret, r->filename);
+
+ fprintf(f, "%%error\n%s\n", error);
+
+ ap_pfclose(r->pool, f);
+ return ret;
+}
+
+static int log_script(request_rec *r, cgi_server_conf * conf, int ret,
+ char *dbuf, const char *sbuf, BUFF *script_in, BUFF *script_err)
+{
+ array_header *hdrs_arr = ap_table_elts(r->headers_in);
+ table_entry *hdrs = (table_entry *) hdrs_arr->elts;
+ char argsbuffer[HUGE_STRING_LEN];
+ FILE *f;
+ int i;
+ struct stat finfo;
+
+ if (!conf->logname ||
+ ((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0)
+ && (finfo.st_size > conf->logbytes)) ||
+ ((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname),
+ "a")) == NULL)) {
+ /* Soak up script output */
+ while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0)
+ continue;
+#ifdef WIN32
+ /* Soak up stderr and redirect it to the error log.
+ * Script output to stderr is already directed to the error log
+ * on Unix, thanks to the magic of fork().
+ */
+ while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
+ "%s", argsbuffer);
+ }
+#else
+ while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0)
+ continue;
+#endif
+ return ret;
+ }
+
+ /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
+ fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri,
+ r->args ? "?" : "", r->args ? r->args : "", r->protocol);
+ /* "%% 500 /usr/local/apache/cgi-bin" */
+ fprintf(f, "%%%% %d %s\n", ret, r->filename);
+
+ fputs("%request\n", f);
+ for (i = 0; i < hdrs_arr->nelts; ++i) {
+ if (!hdrs[i].key)
+ continue;
+ fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
+ }
+ if ((r->method_number == M_POST || r->method_number == M_PUT)
+ && *dbuf) {
+ fprintf(f, "\n%s\n", dbuf);
+ }
+
+ fputs("%response\n", f);
+ hdrs_arr = ap_table_elts(r->err_headers_out);
+ hdrs = (table_entry *) hdrs_arr->elts;
+
+ for (i = 0; i < hdrs_arr->nelts; ++i) {
+ if (!hdrs[i].key)
+ continue;
+ fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
+ }
+
+ if (sbuf && *sbuf)
+ fprintf(f, "%s\n", sbuf);
+
+ if (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) {
+ fputs("%stdout\n", f);
+ fputs(argsbuffer, f);
+ while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0)
+ fputs(argsbuffer, f);
+ fputs("\n", f);
+ }
+
+ if (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
+ fputs("%stderr\n", f);
+ fputs(argsbuffer, f);
+ while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0)
+ fputs(argsbuffer, f);
+ fputs("\n", f);
+ }
+
+ ap_bclose(script_in);
+ ap_bclose(script_err);
+
+ ap_pfclose(r->pool, f);
+ return ret;
+}
+
+/****************************************************************
+ *
+ * Actual CGI handling...
+ */
+
+
+struct cgi_child_stuff {
+#ifdef TPF
+ TPF_FORK_CHILD t;
+#endif
+ request_rec *r;
+ int nph;
+ int debug;
+ char *argv0;
+};
+
+static int cgi_child(void *child_stuff, child_info *pinfo)
+{
+ struct cgi_child_stuff *cld = (struct cgi_child_stuff *) child_stuff;
+ request_rec *r = cld->r;
+ char *argv0 = cld->argv0;
+ int child_pid;
+
+#ifdef DEBUG_CGI
+#ifdef OS2
+ /* Under OS/2 need to use device con. */
+ FILE *dbg = fopen("con", "w");
+#else
+ FILE *dbg = fopen("/dev/tty", "w");
+#endif
+ int i;
+#endif
+
+ char **env;
+
+ RAISE_SIGSTOP(CGI_CHILD);
+#ifdef DEBUG_CGI
+ fprintf(dbg, "Attempting to exec %s as %sCGI child (argv0 = %s)\n",
+ r->filename, cld->nph ? "NPH " : "", argv0);
+#endif
+
+ ap_add_cgi_vars(r);
+ env = ap_create_environment(r->pool, r->subprocess_env);
+
+#ifdef DEBUG_CGI
+ fprintf(dbg, "Environment: \n");
+ for (i = 0; env[i]; ++i)
+ fprintf(dbg, "'%s'\n", env[i]);
+#endif
+
+#ifndef WIN32
+ ap_chdir_file(r->filename);
+#endif
+ if (!cld->debug)
+ ap_error_log2stderr(r->server);
+
+ /* Transumute outselves into the script.
+ * NB only ISINDEX scripts get decoded arguments.
+ */
+
+#ifdef TPF
+ return (0);
+#else
+ ap_cleanup_for_exec();
+
+ child_pid = ap_call_exec(r, pinfo, argv0, env, 0);
+#if defined(WIN32) || defined(OS2)
+ return (child_pid);
+#else
+
+ /* Uh oh. Still here. Where's the kaboom? There was supposed to be an
+ * EARTH-shattering kaboom!
+ *
+ * Oh, well. Muddle through as best we can...
+ *
+ * Note that only stderr is available at this point, so don't pass in
+ * a server to aplog_error.
+ */
+
+ ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed", r->filename);
+ exit(0);
+ /* NOT REACHED */
+ return (0);
+#endif
+#endif /* TPF */
+}
+
+static int cgi_handler(request_rec *r)
+{
+ int retval, nph, dbpos = 0;
+ char *argv0, *dbuf = NULL;
+ BUFF *script_out, *script_in, *script_err;
+ char argsbuffer[HUGE_STRING_LEN];
+ int is_included = !strcmp(r->protocol, "INCLUDED");
+ void *sconf = r->server->module_config;
+ cgi_server_conf *conf =
+ (cgi_server_conf *) ap_get_module_config(sconf, &cgi_module);
+
+ struct cgi_child_stuff cld;
+
+ if (r->method_number == M_OPTIONS) {
+ /* 99 out of 100 CGI scripts, this is all they support */
+ r->allowed |= (1 << M_GET);
+ r->allowed |= (1 << M_POST);
+ return DECLINED;
+ }
+
+ if ((argv0 = strrchr(r->filename, '/')) != NULL)
+ argv0++;
+ else
+ argv0 = r->filename;
+
+ nph = !(strncmp(argv0, "nph-", 4));
+
+ if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r))
+ return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
+ "Options ExecCGI is off in this directory");
+ if (nph && is_included)
+ return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
+ "attempt to include NPH CGI script");
+
+#if defined(OS2) || defined(WIN32)
+ /* Allow for cgi files without the .EXE extension on them under OS/2 */
+ if (r->finfo.st_mode == 0) {
+ struct stat statbuf;
+ char *newfile;
+
+ newfile = ap_pstrcat(r->pool, r->filename, ".EXE", NULL);
+
+ if ((stat(newfile, &statbuf) != 0) || (!S_ISREG(statbuf.st_mode))) {
+ return log_scripterror(r, conf, NOT_FOUND, 0,
+ "script not found or unable to stat");
+ } else {
+ r->filename = newfile;
+ }
+ }
+#else
+ if (r->finfo.st_mode == 0)
+ return log_scripterror(r, conf, NOT_FOUND, APLOG_NOERRNO,
+ "script not found or unable to stat");
+#endif
+ if (S_ISDIR(r->finfo.st_mode))
+ return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
+ "attempt to invoke directory as script");
+ if (!ap_suexec_enabled) {
+ if (!ap_can_exec(&r->finfo))
+ return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
+ "file permissions deny server execution");
+ }
+
+ if ((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)))
+ return retval;
+
+ ap_add_common_vars(r);
+ cld.argv0 = argv0;
+ cld.r = r;
+ cld.nph = nph;
+ cld.debug = conf->logname ? 1 : 0;
+#ifdef TPF
+ cld.t.filename = r->filename;
+ cld.t.subprocess_env = r->subprocess_env;
+ cld.t.prog_type = FORK_FILE;
+#endif /* TPF */
+
+#ifdef CHARSET_EBCDIC
+ /* XXX:@@@ Is the generated/included output ALWAYS in text/ebcdic format? */
+ /* Or must we check the Content-Type first? */
+ ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, 1);
+#endif /*CHARSET_EBCDIC*/
+
+ /*
+ * we spawn out of r->main if it's there so that we can avoid
+ * waiting for free_proc_chain to cleanup in the middle of an
+ * SSI request -djg
+ */
+ if (!ap_bspawn_child(r->main ? r->main->pool : r->pool, cgi_child,
+ (void *) &cld, kill_after_timeout,
+ &script_out, &script_in, &script_err)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
+ "couldn't spawn child process: %s", r->filename);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Transfer any put/post args, CERN style...
+ * Note that we already ignore SIGPIPE in the core server.
+ */
+
+ if (ap_should_client_block(r)) {
+ int dbsize, len_read;
+
+ if (conf->logname) {
+ dbuf = ap_pcalloc(r->pool, conf->bufbytes + 1);
+ dbpos = 0;
+ }
+
+ ap_hard_timeout("copy script args", r);
+
+ while ((len_read =
+ ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN)) > 0) {
+ if (conf->logname) {
+ if ((dbpos + len_read) > conf->bufbytes) {
+ dbsize = conf->bufbytes - dbpos;
+ }
+ else {
+ dbsize = len_read;
+ }
+ memcpy(dbuf + dbpos, argsbuffer, dbsize);
+ dbpos += dbsize;
+ }
+ ap_reset_timeout(r);
+ if (ap_bwrite(script_out, argsbuffer, len_read) < len_read) {
+ /* silly script stopped reading, soak up remaining message */
+ while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN) > 0) {
+ /* dump it */
+ }
+ break;
+ }
+ }
+
+ ap_bflush(script_out);
+
+ ap_kill_timeout(r);
+ }
+
+ ap_bclose(script_out);
+
+ /* Handle script return... */
+ if (script_in && !nph) {
+ const char *location;
+ char sbuf[MAX_STRING_LEN];
+ int ret;
+
+ if ((ret = ap_scan_script_header_err_buff(r, script_in, sbuf))) {
+ return log_script(r, conf, ret, dbuf, sbuf, script_in, script_err);
+ }
+
+#ifdef CHARSET_EBCDIC
+ /* Now check the Content-Type to decide if conversion is needed */
+ ap_checkconv(r);
+#endif /*CHARSET_EBCDIC*/
+
+ location = ap_table_get(r->headers_out, "Location");
+
+ if (location && location[0] == '/' && r->status == 200) {
+
+ /* Soak up all the script output */
+ ap_hard_timeout("read from script", r);
+ while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) {
+ continue;
+ }
+ while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
+ continue;
+ }
+ ap_kill_timeout(r);
+
+
+ /* This redirect needs to be a GET no matter what the original
+ * method was.
+ */
+ r->method = ap_pstrdup(r->pool, "GET");
+ r->method_number = M_GET;
+
+ /* We already read the message body (if any), so don't allow
+ * the redirected request to think it has one. We can ignore
+ * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
+ */
+ ap_table_unset(r->headers_in, "Content-Length");
+
+ ap_internal_redirect_handler(location, r);
+ return OK;
+ }
+ else if (location && r->status == 200) {
+ /* XX Note that if a script wants to produce its own Redirect
+ * body, it now has to explicitly *say* "Status: 302"
+ */
+ return REDIRECT;
+ }
+
+ ap_send_http_header(r);
+ if (!r->header_only) {
+ ap_send_fb(script_in, r);
+ }
+ ap_bclose(script_in);
+
+ ap_soft_timeout("soaking script stderr", r);
+ while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
+ continue;
+ }
+ ap_kill_timeout(r);
+ ap_bclose(script_err);
+ }
+
+ if (script_in && nph) {
+ ap_send_fb(script_in, r);
+ }
+
+ return OK; /* NOT r->status, even if it has changed. */
+}
+
+static const handler_rec cgi_handlers[] =
+{
+ {CGI_MAGIC_TYPE, cgi_handler},
+ {"cgi-script", cgi_handler},
+ {NULL}
+};
+
+module MODULE_VAR_EXPORT cgi_module =
+{
+ STANDARD_MODULE_STUFF,
+ NULL, /* initializer */
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ create_cgi_config, /* server config */
+ merge_cgi_config, /* merge server config */
+ cgi_cmds, /* command table */
+ cgi_handlers, /* handlers */
+ NULL, /* filename translation */
+ NULL, /* check_user_id */
+ NULL, /* check auth */
+ NULL, /* check access */
+ NULL, /* type_checker */
+ NULL, /* fixups */
+ NULL, /* logger */
+ NULL, /* header parser */
+ NULL, /* child_init */
+ NULL, /* child_exit */
+ NULL /* post read-request */
+};
diff --git a/modules/generators/mod_info.c b/modules/generators/mod_info.c
new file mode 100644
index 0000000000..2799585578
--- /dev/null
+++ b/modules/generators/mod_info.c
@@ -0,0 +1,695 @@
+/* ====================================================================
+ * Copyright (c) 1995-1999 The Apache Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ * software must display the following acknowledgment:
+ * "This product includes software developed by the Apache Group
+ * for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * 4. The names "Apache Server" and "Apache Group" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For written permission, please contact
+ * apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ * acknowledgment:
+ * "This product includes software developed by the Apache Group
+ * for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Group and was originally based
+ * on public domain software written at the National Center for
+ * Supercomputing Applications, University of Illinois, Urbana-Champaign.
+ * For more information on the Apache Group and the Apache HTTP server
+ * project, please see .
+ *
+ */
+
+/*
+ * Info Module. Display configuration information for the server and
+ * all included modules.
+ *
+ *
+ * SetHandler server-info
+ *
+ *
+ * GET /server-info - Returns full configuration page for server and all modules
+ * GET /server-info?server - Returns server configuration only
+ * GET /server-info?module_name - Returns configuration for a single module
+ * GET /server-info?list - Returns quick list of included modules
+ *
+ * Rasmus Lerdorf , May 1996
+ *
+ * 05.01.96 Initial Version
+ *
+ * Lou Langholtz , July 1997
+ *
+ * 07.11.97 Addition of the AddModuleInfo directive
+ *
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "http_protocol.h"
+#include "util_script.h"
+#include "http_conf_globals.h"
+
+typedef struct {
+ char *name; /* matching module name */
+ char *info; /* additional info */
+} info_entry;
+
+typedef struct {
+ array_header *more_info;
+} info_svr_conf;
+
+typedef struct info_cfg_lines {
+ char *cmd;
+ char *line;
+ struct info_cfg_lines *next;
+} info_cfg_lines;
+
+module MODULE_VAR_EXPORT info_module;
+extern module *top_module;
+
+static void *create_info_config(pool *p, server_rec *s)
+{
+ info_svr_conf *conf = (info_svr_conf *) ap_pcalloc(p, sizeof(info_svr_conf));
+
+ conf->more_info = ap_make_array(p, 20, sizeof(info_entry));
+ return conf;
+}
+
+static void *merge_info_config(pool *p, void *basev, void *overridesv)
+{
+ info_svr_conf *new = (info_svr_conf *) ap_pcalloc(p, sizeof(info_svr_conf));
+ info_svr_conf *base = (info_svr_conf *) basev;
+ info_svr_conf *overrides = (info_svr_conf *) overridesv;
+
+ new->more_info = ap_append_arrays(p, overrides->more_info, base->more_info);
+ return new;
+}
+
+static char *mod_info_html_cmd_string(const char *string, char *buf, size_t buf_len)
+{
+ const char *s;
+ char *t;
+ char *end_buf;
+
+ s = string;
+ t = buf;
+ /* keep space for \0 byte */
+ end_buf = buf + buf_len - 1;
+ while ((*s) && (t < end_buf)) {
+ if (*s == '<') {
+ strncpy(t, "<", end_buf - t);
+ t += 4;
+ }
+ else if (*s == '>') {
+ strncpy(t, ">", end_buf - t);
+ t += 4;
+ }
+ else if (*s == '&') {
+ strncpy(t, "&", end_buf - t);
+ t += 5;
+ }
+ else {
+ *t++ = *s;
+ }
+ s++;
+ }
+ /* oops, overflowed... don't overwrite */
+ if (t > end_buf) {
+ *end_buf = '\0';
+ }
+ else {
+ *t = '\0';
+ }
+ return (buf);
+}
+
+static info_cfg_lines *mod_info_load_config(pool *p, const char *filename,
+ request_rec *r)
+{
+ char s[MAX_STRING_LEN];
+ configfile_t *fp;
+ info_cfg_lines *new, *ret, *prev;
+ const char *t;
+
+ fp = ap_pcfg_openfile(p, filename);
+ if (!fp) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, r,
+ "mod_info: couldn't open config file %s",
+ filename);
+ return NULL;
+ }
+ ret = NULL;
+ prev = NULL;
+ while (!ap_cfg_getline(s, MAX_STRING_LEN, fp)) {
+ if (*s == '#') {
+ continue; /* skip comments */
+ }
+ new = ap_palloc(p, sizeof(struct info_cfg_lines));
+ new->next = NULL;
+ if (!ret) {
+ ret = new;
+ }
+ if (prev) {
+ prev->next = new;
+ }
+ t = s;
+ new->cmd = ap_getword_conf(p, &t);
+ if (*t) {
+ new->line = ap_pstrdup(p, t);
+ }
+ else {
+ new->line = NULL;
+ }
+ prev = new;
+ }
+ ap_cfg_closefile(fp);
+ return (ret);
+}
+
+static void mod_info_module_cmds(request_rec *r, info_cfg_lines *cfg,
+ const command_rec *cmds, char *label)
+{
+ const command_rec *cmd = cmds;
+ info_cfg_lines *li = cfg, *li_st = NULL, *li_se = NULL;
+ info_cfg_lines *block_start = NULL;
+ int lab = 0, nest = 0;
+ char buf[MAX_STRING_LEN];
+
+ while (li) {
+ if (!strncasecmp(li->cmd, "cmd, "cmd, "cmd, "next;
+ nest++;
+ continue;
+ }
+ else if (nest && (!strncasecmp(li->cmd, "cmd, "cmd, "cmd, "