]> granicus.if.org Git - apache/blobdiff - modules/filters/mod_include.c
Fix a segfault in mod_include when the original request has no
[apache] / modules / filters / mod_include.c
index 09acb6d1966f47710c8fe3e6f4283c6e6708eca7..b65a50ba10d1c576b664610ebcdf4c55fc63d4f0 100644 (file)
@@ -1,7 +1,7 @@
 /* ====================================================================
  * The Apache Software License, Version 1.1
  *
- * Copyright (c) 2000 The Apache Software Foundation.  All rights
+ * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
  * reserved.
  *
  * Redistribution and use in source and binary forms, with or without
 #include "apr_thread_proc.h"
 #include "apr_hash.h"
 #include "apr_user.h"
+#include "apr_lib.h"
+#include "apr_optional.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
 
 #define CORE_PRIVATE
 
@@ -76,6 +81,7 @@
 #include "util_filter.h"
 #include "httpd.h"
 #include "http_config.h"
+#include "http_core.h"
 #include "http_request.h"
 #include "http_core.h"
 #include "http_protocol.h"
 #include "http_main.h"
 #include "util_script.h"
 #include "http_core.h"
-#include "apr_optional.h"
 #include "mod_include.h"
-#ifdef HAVE_STRING_H
-#include <string.h>
-#endif
-#ifdef HAVE_STRINGS_H
-#include <strings.h>
-#endif
 #include "util_ebcdic.h"
 
-module AP_MODULE_DECLARE_DATA includes_module;
+module AP_MODULE_DECLARE_DATA include_module;
 static apr_hash_t *include_hash;
 static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
 
+#define BYTE_COUNT_THRESHOLD AP_MIN_BYTES_TO_WRITE
 
 /* ------------------------ Environment function -------------------------- */
 
@@ -150,6 +150,8 @@ static apr_bucket *find_start_sequence(apr_bucket *dptr, include_ctx_t *ctx,
     const char *c;
     const char *buf;
     const char *str = STARTING_SEQUENCE;
+    apr_bucket *tmp_bkt;
+    apr_size_t  start_index;
 
     *do_cleanup = 0;
 
@@ -157,13 +159,36 @@ static apr_bucket *find_start_sequence(apr_bucket *dptr, include_ctx_t *ctx,
         if (APR_BUCKET_IS_EOS(dptr)) {
             break;
         }
-        apr_bucket_read(dptr, &buf, &len, 0);
+        apr_bucket_read(dptr, &buf, &len, APR_BLOCK_READ);
         /* XXX handle retcodes */
         if (len == 0) { /* end of pipe? */
             break;
         }
         c = buf;
-        while (c - buf != len) {
+        while (c < buf + len) {
+            if (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD) {
+                apr_bucket *start_bucket;
+
+                if (ctx->head_start_index > 0) {
+                    start_index  = ctx->head_start_index;
+                    start_bucket = ctx->head_start_bucket;
+                }
+                else {
+                    start_index  = (c - buf);
+                    start_bucket = dptr;
+                }
+                apr_bucket_split(start_bucket, start_index);
+                tmp_bkt = APR_BUCKET_NEXT(start_bucket);
+                if (ctx->head_start_index > 0) {
+                    ctx->head_start_index  = 0;
+                    ctx->head_start_bucket = tmp_bkt;
+                    ctx->parse_pos = 0;
+                    ctx->state = PRE_HEAD;
+                }
+
+                return tmp_bkt;
+            }
+
             if (*c == str[ctx->parse_pos]) {
                 if (ctx->state == PRE_HEAD) {
                     ctx->state             = PARSE_HEAD;
@@ -174,10 +199,8 @@ static apr_bucket *find_start_sequence(apr_bucket *dptr, include_ctx_t *ctx,
             }
             else {
                 if (str[ctx->parse_pos] == '\0') {
-                    apr_bucket   *tmp_bkt;
-                    apr_size_t  start_index;
-
                     /* We want to split the bucket at the '<'. */
+                    ctx->bytes_parsed++;
                     ctx->state            = PARSE_DIRECTIVE;
                     ctx->tag_length       = 0;
                     ctx->parse_pos        = 0;
@@ -218,6 +241,7 @@ static apr_bucket *find_start_sequence(apr_bucket *dptr, include_ctx_t *ctx,
                 }
             }
             c++;
+            ctx->bytes_parsed++;
         }
         dptr = APR_BUCKET_NEXT(dptr);
     } while (dptr != APR_BRIGADE_SENTINEL(bb));
@@ -235,7 +259,7 @@ static apr_bucket *find_end_sequence(apr_bucket *dptr, include_ctx_t *ctx, apr_b
         if (APR_BUCKET_IS_EOS(dptr)) {
             break;
         }
-        apr_bucket_read(dptr, &buf, &len, 0);
+        apr_bucket_read(dptr, &buf, &len, APR_BLOCK_READ);
         /* XXX handle retcodes */
         if (len == 0) { /* end of pipe? */
             break;
@@ -246,7 +270,16 @@ static apr_bucket *find_end_sequence(apr_bucket *dptr, include_ctx_t *ctx, apr_b
         else {
             c = buf;
         }
-        while (c - buf != len) {
+        while (c < buf + len) {
+            if (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD) {
+                if (ctx->state == PARSE_DIRECTIVE) {
+                    /* gonna start over parsing the directive next time through */
+                    ctx->directive_length = 0;
+                    ctx->tag_length       = 0;
+                }
+                return dptr;
+            }
+
             if (*c == str[ctx->parse_pos]) {
                 if (ctx->state != PARSE_TAIL) {
                     ctx->state             = PARSE_TAIL;
@@ -286,6 +319,7 @@ static apr_bucket *find_end_sequence(apr_bucket *dptr, include_ctx_t *ctx, apr_b
                          * end of the END_SEQUENCE is in the current bucket.
                          * The beginning might be in a previous bucket.
                          */
+                        ctx->bytes_parsed++;
                         ctx->state = PARSED;
                         if ((c - buf) > 0) {
                             apr_bucket_split(dptr, c - buf);
@@ -325,6 +359,7 @@ static apr_bucket *find_end_sequence(apr_bucket *dptr, include_ctx_t *ctx, apr_b
                 }
             }
             c++;
+            ctx->bytes_parsed++;
         }
         dptr = APR_BUCKET_NEXT(dptr);
     } while (dptr != APR_BRIGADE_SENTINEL(bb));
@@ -338,10 +373,11 @@ static apr_bucket *find_end_sequence(apr_bucket *dptr, include_ctx_t *ctx, apr_b
 static apr_status_t get_combined_directive (include_ctx_t *ctx,
                                             request_rec *r,
                                             apr_bucket_brigade *bb,
-                                            char *tmp_buf, int tmp_buf_size)
+                                            char *tmp_buf, 
+                                            apr_size_t tmp_buf_size)
 {
-    int         done = 0;
-    apr_bucket  *dptr;
+    int        done = 0;
+    apr_bucket *dptr;
     const char *tmp_from;
     apr_size_t tmp_from_len;
 
@@ -386,7 +422,7 @@ static apr_status_t get_combined_directive (include_ctx_t *ctx,
             }
         }
     } while ((!done) &&
-             ((ctx->curr_tag_pos - ctx->combined_tag) < ctx->tag_length));
+             (ctx->curr_tag_pos < ctx->combined_tag + ctx->tag_length));
 
     ctx->combined_tag[ctx->tag_length] = '\0';
     ctx->curr_tag_pos = ctx->combined_tag;
@@ -564,8 +600,8 @@ static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
         }
     }
     
-    *c++ = '\0'; /* Overwrites delimiter (term or WS) with NULL. */
-    ctx->curr_tag_pos = c;
+    *(c-shift_val) = '\0'; /* Overwrites delimiter (term or WS) with NULL. */
+    ctx->curr_tag_pos = ++c;
     if (dodecode) {
         decodehtml(*tag_val);
     }
@@ -796,8 +832,8 @@ static int handle_include(include_ctx_t *ctx, apr_bucket_brigade **bb, request_r
                     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) ){
+                       if ((q->filename && rr->filename && (strcmp(q->filename, rr->filename) == 0)) ||
+                            (strcmp(q->uri, rr->uri) == 0){
                            founddupe = 1;
                            break;
                        }
@@ -814,7 +850,7 @@ static int handle_include(include_ctx_t *ctx, apr_bucket_brigade **bb, request_r
                 /* Basically, it puts a bread crumb in here, then looks */
                 /*   for the crumb later to see if its been here.       */
            if (rr) 
-               ap_set_module_config(rr->request_config, &includes_module, r);
+               ap_set_module_config(rr->request_config, &include_module, r);
 
                 if (!error_fmt) {
                     SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next);
@@ -831,7 +867,7 @@ static int handle_include(include_ctx_t *ctx, apr_bucket_brigade **bb, request_r
 
            /* destroy the sub request if it's not a nested include (crumb) */
                 if (rr != NULL
-               && ap_get_module_config(rr->request_config, &includes_module)
+               && ap_get_module_config(rr->request_config, &include_module)
                    != NESTED_INCLUDE_MAGIC) {
                ap_destroy_sub_req(rr);
                 }
@@ -874,11 +910,10 @@ static int handle_echo(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec
             }
             if (!strcmp(tag, "var")) {
                 const char *val = apr_table_get(r->subprocess_env, tag_val);
-                int b_copy = 0;
 
                 if (val) {
                     switch(encode) {
-                    case E_NONE:   echo_text = val;  b_copy = 1;             break;
+                    case E_NONE:   echo_text = val;                          break;
                     case E_URL:    echo_text = ap_escape_uri(r->pool, val);  break;
                     case E_ENTITY: echo_text = ap_escape_html(r->pool, val); break;
                }
@@ -887,7 +922,7 @@ static int handle_echo(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec
                     tmp_buck = apr_bucket_heap_create(echo_text, e_len, 1, &e_wrt);
                 }
                 else {
-                    tmp_buck = apr_bucket_immortal_create("(none)", sizeof("none"));
+                    tmp_buck = apr_bucket_immortal_create("(none)", sizeof("(none)")-1);
                 }
                 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
                 if (*inserted_head == NULL) {
@@ -1052,33 +1087,6 @@ static int find_file(request_rec *r, const char *directive, const char *tag,
     }
 }
 
-#define NEG_SIGN  "    -"
-#define ZERO_K    "   0k"
-#define ONE_K     "   1k"
-
-static void generate_size(apr_ssize_t size, char *buff, apr_size_t buff_size)
-{
-    /* XXX: this -1 thing is a gross hack */
-    if (size == (apr_ssize_t)-1) {
-       memcpy (buff, NEG_SIGN, sizeof(NEG_SIGN)+1);
-    }
-    else if (!size) {
-       memcpy (buff, ZERO_K, sizeof(ZERO_K)+1);
-    }
-    else if (size < 1024) {
-       memcpy (buff, ONE_K, sizeof(ONE_K)+1);
-    }
-    else if (size < 1048576) {
-        apr_snprintf(buff, buff_size, "%4" APR_SSIZE_T_FMT "k", (size + 512) / 1024);
-    }
-    else if (size < 103809024) {
-        apr_snprintf(buff, buff_size, "%4.1fM", size / 1048576.0);
-    }
-    else {
-        apr_snprintf(buff, buff_size, "%4" APR_SSIZE_T_FMT "M", (size + 524288) / 1048576);
-    }
-}
-
 static int handle_fsize(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
                         ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
 {
@@ -1107,7 +1115,7 @@ static int handle_fsize(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec
                     char buff[50];
 
                     if (!(ctx->flags & FLAG_SIZE_IN_BYTES)) {
-                        generate_size(finfo.size, buff, sizeof(buff));
+                        apr_strfsize(finfo.size, buff);
                         s_len = strlen (buff);
                     }
                     else {
@@ -2316,17 +2324,17 @@ static int handle_printenv(include_ctx_t *ctx, apr_bucket_brigade **bb, request_
     return 0;
 }
 
-
 /* -------------------------- The main function --------------------------- */
 
-static void send_parsed_content(apr_bucket_brigade **bb, request_rec *r
-                                ap_filter_t *f)
+static apr_status_t send_parsed_content(apr_bucket_brigade **bb
+                                        request_rec *r, ap_filter_t *f)
 {
     include_ctx_t *ctx = f->ctx;
     apr_bucket *dptr = APR_BRIGADE_FIRST(*bb);
     apr_bucket *tmp_dptr;
     apr_bucket_brigade *tag_and_after;
     int ret;
+    apr_status_t rv;
 
     if (r->args) {              /* add QUERY stuff to env cause it ain't yet */
         char *arg_copy = apr_pstrdup(r->pool, r->args);
@@ -2352,14 +2360,10 @@ static void send_parsed_content(apr_bucket_brigade **bb, request_rec *r,
             if ((do_cleanup) && (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade))) {
                 apr_bucket *tmp_bkt;
 
-                tmp_bkt = apr_bucket_immortal_create(STARTING_SEQUENCE, cleanup_bytes);
+                tmp_bkt = apr_bucket_immortal_create(STARTING_SEQUENCE,
+                                                     cleanup_bytes);
                 APR_BRIGADE_INSERT_HEAD(*bb, tmp_bkt);
-
-                while (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
-                    tmp_bkt = APR_BRIGADE_FIRST(ctx->ssi_tag_brigade);
-                    APR_BUCKET_REMOVE(tmp_bkt);
-                    apr_bucket_destroy(tmp_bkt);
-                }
+                apr_brigade_cleanup(ctx->ssi_tag_brigade);
             }
 
             /* If I am inside a conditional (if, elif, else) that is false
@@ -2372,8 +2376,7 @@ static void send_parsed_content(apr_bucket_brigade **bb, request_rec *r,
                     apr_bucket *free_bucket = dptr;
 
                     dptr = APR_BUCKET_NEXT (dptr);
-                    APR_BUCKET_REMOVE(free_bucket);
-                    apr_bucket_destroy(free_bucket);
+                    apr_bucket_delete(free_bucket);
                 }
             }
 
@@ -2386,6 +2389,17 @@ static void send_parsed_content(apr_bucket_brigade **bb, request_rec *r,
                     dptr = APR_BRIGADE_SENTINEL(*bb);
                 }
             }
+            else if ((tmp_dptr != NULL) && (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD)) {
+                               /* Send the large chunk of pre-tag bytes...  */
+                tag_and_after = apr_brigade_split(*bb, tmp_dptr);
+                rv = ap_pass_brigade(f->next, *bb);
+                if (rv != APR_SUCCESS) {
+                    return rv;
+                }
+                *bb  = tag_and_after;
+                dptr = tmp_dptr;
+                ctx->bytes_parsed = 0;
+            }
             else if (tmp_dptr == NULL) { /* There was no possible SSI tag in the */
                 dptr = APR_BRIGADE_SENTINEL(*bb);  /* remainder of this brigade...    */
             }
@@ -2412,6 +2426,9 @@ static void send_parsed_content(apr_bucket_brigade **bb, request_rec *r,
                     APR_BRIGADE_CONCAT(ctx->ssi_tag_brigade, *bb);
                     *bb = tag_and_after;
                 }
+                else if (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD) {
+                    SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next);
+                }
             }
             else {
                 dptr = APR_BRIGADE_SENTINEL(*bb);  /* remainder of this brigade...    */
@@ -2445,32 +2462,20 @@ static void send_parsed_content(apr_bucket_brigade **bb, request_rec *r,
                 /* DO CLEANUP HERE!!!!! */
                 tmp_dptr = ctx->head_start_bucket;
                 if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
-                    while (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
-                        tmp_bkt = APR_BRIGADE_FIRST(ctx->ssi_tag_brigade);
-                        APR_BUCKET_REMOVE(tmp_bkt);
-                        apr_bucket_destroy(tmp_bkt);
-                    }
+                    apr_brigade_cleanup(ctx->ssi_tag_brigade);
                 }
                 else {
                     do {
                         tmp_bkt  = tmp_dptr;
                         tmp_dptr = APR_BUCKET_NEXT (tmp_dptr);
-                        APR_BUCKET_REMOVE(tmp_bkt);
-                        apr_bucket_destroy(tmp_bkt);
+                        apr_bucket_delete(tmp_bkt);
                     } while ((tmp_dptr != dptr) &&
                              (tmp_dptr != APR_BRIGADE_SENTINEL(*bb)));
                 }
 
-                return;
+                return APR_SUCCESS;
             }
 
-            /* Even if I don't generate any content, I know at this point that
-             *   I will at least remove the discovered SSI tag, thereby making
-             *   the content shorter than it was. This is the safest point I can
-             *   find to unset this field.
-             */
-            apr_table_unset(f->r->headers_out, "Content-Length");
-
             /* Can't destroy the tag buckets until I'm done processing
              *  because the combined_tag might just be pointing to
              *  the contents of a single bucket!
@@ -2489,9 +2494,9 @@ static void send_parsed_content(apr_bucket_brigade **bb, request_rec *r,
             ctx->curr_tag_pos = &ctx->combined_tag[ctx->directive_length+1];
 
             handle_func = 
-                (int (*)(include_ctx_t *, apr_bucket_brigade **, request_rec *,
-                    ap_filter_t *, apr_bucket *, apr_bucket **))
-                apr_hash_get(include_hash, ctx->combined_tag, ctx->directive_length+1);
+                (include_handler_fn_t *)apr_hash_get(include_hash, 
+                                                     ctx->combined_tag, 
+                                                     ctx->directive_length+1);
             if (handle_func != NULL) {
                 ret = (*handle_func)(ctx, bb, r, f, dptr, &content_head);
             }
@@ -2525,18 +2530,13 @@ static void send_parsed_content(apr_bucket_brigade **bb, request_rec *r,
             }
             tmp_dptr = ctx->head_start_bucket;
             if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
-                while (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
-                    tmp_bkt = APR_BRIGADE_FIRST(ctx->ssi_tag_brigade);
-                    APR_BUCKET_REMOVE(tmp_bkt);
-                    apr_bucket_destroy(tmp_bkt);
-                }
+                apr_brigade_cleanup(ctx->ssi_tag_brigade);
             }
             else {
                 do {
                     tmp_bkt  = tmp_dptr;
                     tmp_dptr = APR_BUCKET_NEXT (tmp_dptr);
-                    APR_BUCKET_REMOVE(tmp_bkt);
-                    apr_bucket_destroy(tmp_bkt);
+                    apr_bucket_delete(tmp_bkt);
                 } while ((tmp_dptr != content_head) &&
                          (tmp_dptr != APR_BRIGADE_SENTINEL(*bb)));
             }
@@ -2558,11 +2558,7 @@ static void send_parsed_content(apr_bucket_brigade **bb, request_rec *r,
             ctx->directive_length  = 0;
 
             if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
-                while (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
-                    tmp_bkt = APR_BRIGADE_FIRST(ctx->ssi_tag_brigade);
-                    APR_BUCKET_REMOVE(tmp_bkt);
-                    apr_bucket_destroy(tmp_bkt);
-                }
+                apr_brigade_cleanup(ctx->ssi_tag_brigade);
             }
 
             ctx->state     = PRE_HEAD;
@@ -2583,12 +2579,15 @@ static void send_parsed_content(apr_bucket_brigade **bb, request_rec *r,
             do {
                 free_bucket = dptr;
                 dptr = APR_BUCKET_NEXT (dptr);
-                APR_BUCKET_REMOVE(free_bucket);
-                apr_bucket_destroy(free_bucket);
+                apr_bucket_delete(free_bucket);
             } while (dptr != APR_BRIGADE_SENTINEL(*bb));
         }
         else { /* Otherwise pass it along... */
-            ap_pass_brigade(f->next, *bb);  /* No SSI tags in this brigade... */
+            rv = ap_pass_brigade(f->next, *bb);  /* No SSI tags in this brigade... */
+            if (rv != APR_SUCCESS) {
+                return rv;
+            }
+            ctx->bytes_parsed = 0;
         }
     }
     else if (ctx->state == PARSED) {     /* Invalid internal condition... */
@@ -2609,10 +2608,15 @@ static void send_parsed_content(apr_bucket_brigade **bb, request_rec *r,
             }
                            /* Set aside tag, pass pre-tag... */
             tag_and_after = apr_brigade_split(*bb, ctx->head_start_bucket);
-            ap_save_brigade(f, &ctx->ssi_tag_brigade, &tag_and_after);
-            ap_pass_brigade(f->next, *bb);
+            ap_save_brigade(f, &ctx->ssi_tag_brigade, &tag_and_after, r->pool);
+            rv = ap_pass_brigade(f->next, *bb);
+            if (rv != APR_SUCCESS) {
+                return rv;
+            }
+            ctx->bytes_parsed = 0;
         }
     }
+    return APR_SUCCESS;
 }
 
 /*****************************************************************
@@ -2621,11 +2625,17 @@ static void send_parsed_content(apr_bucket_brigade **bb, request_rec *r,
  * option only changes the default.
  */
 
-module includes_module;
+module include_module;
 enum xbithack {
     xbithack_off, xbithack_on, xbithack_full
 };
 
+typedef struct {
+    char *default_error_msg;
+    char *default_time_fmt;
+    enum xbithack *xbithack;
+} include_dir_config;
+
 #ifdef XBITHACK
 #define DEFAULT_XBITHACK xbithack_full
 #else
@@ -2634,23 +2644,28 @@ enum xbithack {
 
 static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
 {
-    enum xbithack *result = (enum xbithack *) apr_palloc(p, sizeof(enum xbithack));
-    *result = DEFAULT_XBITHACK;
+    include_dir_config *result =
+        (include_dir_config *)apr_palloc(p, sizeof(include_dir_config));
+    enum xbithack *xbh = (enum xbithack *) apr_palloc(p, sizeof(enum xbithack));
+    *xbh = DEFAULT_XBITHACK;
+    result->default_error_msg = DEFAULT_ERROR_MSG;
+    result->default_time_fmt = DEFAULT_TIME_FORMAT;
+    result->xbithack = xbh;
     return result;
 }
 
 static const char *set_xbithack(cmd_parms *cmd, void *xbp, const char *arg)
 {
-    enum xbithack *state = (enum xbithack *) xbp;
+    include_dir_config *conf = (include_dir_config *)xbp;
 
     if (!strcasecmp(arg, "off")) {
-        *state = xbithack_off;
+        *conf->xbithack = xbithack_off;
     }
     else if (!strcasecmp(arg, "on")) {
-        *state = xbithack_on;
+        *conf->xbithack = xbithack_on;
     }
     else if (!strcasecmp(arg, "full")) {
-        *state = xbithack_full;
+        *conf->xbithack = xbithack_full;
     }
     else {
         return "XBitHack must be set to Off, On, or Full";
@@ -2659,18 +2674,20 @@ static const char *set_xbithack(cmd_parms *cmd, void *xbp, const char *arg)
     return NULL;
 }
 
-static int includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
+static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
 {
     request_rec *r = f->r;
     include_ctx_t *ctx = f->ctx;
-    enum xbithack *state =
-    (enum xbithack *) ap_get_module_config(r->per_dir_config, &includes_module);
     request_rec *parent;
+    apr_status_t rv;
+    include_dir_config *conf = 
+                   (include_dir_config *)ap_get_module_config(r->per_dir_config,
+                                                              &include_module);
 
     if (!(ap_allow_options(r) & OPT_INCLUDES)) {
         return ap_pass_brigade(f->next, b);
     }
-    r->allowed |= (1 << M_GET);
+    r->allowed |= (AP_METHOD_BIT << M_GET);
     if (r->method_number != M_GET) {
         return ap_pass_brigade(f->next, b);
     }
@@ -2685,25 +2702,27 @@ static int includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
             }
             ctx->ssi_tag_brigade = apr_brigade_create(f->c->pool);
 
-            apr_cpystrn(ctx->error_str, DEFAULT_ERROR_MSG,   sizeof(ctx->error_str));
-            apr_cpystrn(ctx->time_str,  DEFAULT_TIME_FORMAT, sizeof(ctx->time_str));
+            apr_cpystrn(ctx->error_str, conf->default_error_msg, sizeof(ctx->error_str));
+            apr_cpystrn(ctx->time_str, conf->default_time_fmt, sizeof(ctx->time_str));
             ctx->error_length = strlen(ctx->error_str);
         }
         else {
-            ap_pass_brigade(f->next, b);
-            return APR_ENOMEM;
+            return ap_pass_brigade(f->next, b);
         }
     }
+    else {
+        ctx->bytes_parsed = 0;
+    }
 
     /* Assure the platform supports Group protections */
-    if ((*state == xbithack_full)
+    if ((*conf->xbithack == xbithack_full)
         && (r->finfo.valid & APR_FINFO_GPROT)
         && (r->finfo.protection & APR_GEXECUTE)) {
         ap_update_mtime(r, r->finfo.mtime);
         ap_set_last_modified(r);
     }
 
-    if ((parent = ap_get_module_config(r->request_config, &includes_module))) {
+    if ((parent = ap_get_module_config(r->request_config, &include_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
@@ -2721,7 +2740,7 @@ static int includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
         * environment */
         ap_add_common_vars(r);
         ap_add_cgi_vars(r);
-        add_include_vars(r, DEFAULT_TIME_FORMAT);
+        add_include_vars(r, conf->default_time_fmt);
     }
     /* 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
@@ -2730,18 +2749,27 @@ static int includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
      */
 
 
-    send_parsed_content(&b, r, f);
+    /* Always unset the content-length.  There is no way to know if
+     * the content will be modified at some point by send_parsed_content.
+     * It is very possible for us to not find any content in the first
+     * 9k of the file, but still have to modify the content of the file.
+     * If we are going to pass the file through send_parsed_content, then
+     * the content-length should just be unset.
+     */
+    apr_table_unset(f->r->headers_out, "Content-Length");
+
+    rv = send_parsed_content(&b, r, f);
 
     if (parent) {
        /* signify that the sub request should not be killed */
-       ap_set_module_config(r->request_config, &includes_module,
+       ap_set_module_config(r->request_config, &include_module,
            NESTED_INCLUDE_MAGIC);
     }
 
-    return OK;
+    return rv;
 }
 
-static void ap_register_include_handler(char *tag, include_handler func)
+static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
 {
     apr_hash_set(include_hash, tag, strlen(tag) + 1, (const void *)func);
 }
@@ -2768,6 +2796,20 @@ static void include_post_config(apr_pool_t *p, apr_pool_t *plog,
     }
 }
 
+static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig, const char *msg)
+{
+    include_dir_config *conf = (include_dir_config *)mconfig;
+    conf->default_error_msg = apr_pstrdup(cmd->pool, msg);
+    return NULL;
+}
+
+static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig, const char *fmt)
+{
+    include_dir_config *conf = (include_dir_config *)mconfig;
+    conf->default_time_fmt = apr_pstrdup(cmd->pool, fmt);
+    return NULL;
+}
+
 /*
  * Module definition and configuration data structs...
  */
@@ -2775,19 +2817,53 @@ static const command_rec includes_cmds[] =
 {
     AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS, 
                   "Off, On, or Full"),
+    AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL, 
+                  "a string"),
+    AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
+                  "a strftime(3) formatted string"),
     {NULL}
 };
 
+static int xbithack_handler(request_rec *r)
+{
+#if defined(OS2) || defined(WIN32) || defined(NETWARE)
+    /* OS/2 dosen't currently support the xbithack. This is being worked on. */
+    return DECLINED;
+#else
+    enum xbithack *state;
+    if (ap_strcmp_match(r->handler, "text/html")) {
+        return DECLINED;
+    }
+    if (!(r->finfo.protection & APR_UEXECUTE)) {
+        return DECLINED;
+    }
+    state = (enum xbithack *) ap_get_module_config(r->per_dir_config,
+                                                &include_module);
+    if (*state == xbithack_off) {
+        return DECLINED;
+    }
+    /* We always return declined, because the default handler will actually
+     * serve the file.  All we have to do is add the filter.
+     */
+    ap_add_output_filter("INCLUDES", NULL, r, r->connection);
+    return DECLINED;
+#endif
+}
+
 static void register_hooks(apr_pool_t *p)
 {
     APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
     APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
     APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
     ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
+    ap_hook_handler(xbithack_handler, NULL, NULL, APR_HOOK_MIDDLE);
     ap_register_output_filter("INCLUDES", includes_filter, AP_FTYPE_CONTENT);
 }
 
-module AP_MODULE_DECLARE_DATA includes_module =
+module AP_MODULE_DECLARE_DATA include_module =
 {
     STANDARD20_MODULE_STUFF,
     create_includes_dir_config, /* dir config creater */