Thanks for all the ideas. They've come in very handy. I haven't done extensive testing, and the module is still too stupid to read configuration data, but I've gotten much farther. Right now, static html works fine, but php pages (need both per customer requirements) spit out too much at the tail end. I'll post my code below for review, as well as a snippet of the php page code as delivered. The problem is much worse when the pages are very large, so the example php code uses a loop to print a comment 1000 times. I suspect I'm filling the brigade or a bucket to full, or not doing something to tell it where the end is. Please send me any and all feedback that may help (stupid mistakes or items I've overlooked most of all). Here's my php test code: \n"; echo "\n"; echo "\n"; echo "PHP Test Page\n"; echo "\n"; echo "\n"; echo "Now, I say goodbye at " . date("m/d/Y") . "\n"; echo "\n"; echo "\n"; Here's what php code looks like when delivered: PHP Test Page Now, I say goodbye at 01/01/2007 <---- looks good to here, but then... ng just filling up space, nothing just filling up space, [snip ~1000 lines the same] nothing just filling up space, --> PHP Test Page Now, I say goodbye at 01/01/2007 And here's the module: #define DEBUG 1 #include "httpd.h" #include "http_config.h" #include "http_log.h" #include "http_request.h" #include "apr_general.h" #include "apr_strings.h" #include "apr_buckets.h" #include "util_filter.h" #include module AP_MODULE_DECLARE_DATA lt_insert_module; typedef struct lt_insert_t { apr_bucket_brigade *bb; } lt_insert_struct; static int lt_insert_filter(ap_filter_t *f, apr_bucket_brigade *bb) { lt_insert_struct *ctx = f->ctx; apr_bucket *e; if ( ! ctx ) { f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); } for( e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e) ) { if ( APR_BUCKET_IS_EOS(e) || APR_BUCKET_IS_FLUSH(e) ) { APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(ctx->bb, e); ap_pass_brigade(f->next, ctx->bb); return APR_SUCCESS; } apr_size_t len; const char *str; const char *search_tag = ""; const char *insert_line = "\n"; char *position = NULL; char *tail = NULL; int i = 0; int insert_done = 0; apr_bucket_read(e, &str, &len, APR_NONBLOCK_READ); if ( ( ( position = strcasestr(str, search_tag) ) == NULL ) || ( insert_done ) ) { /* * If we didn't find the tag, just pass along * everything to the next filter and we're done. */ i = 0; while ( i < len ) { ap_fputc(f->next, ctx->bb, str[i++]); } } else { /* * so, we have a tag. So, lets find and process it * and insert our notice. */ tail = position + strlen(search_tag); i = 0; while ( (str + i) < ( position + strlen(search_tag) ) ) { ap_fputc(f->next, ctx->bb, str[i++]); } ap_fputs(f->next, ctx->bb, "\n"); ap_fputs(f->next, ctx->bb, insert_line); while ( i < len ) { ap_fputc(f->next, ctx->bb, str[i++]); } insert_done = 1; } } return APR_SUCCESS; } static void register_hooks(apr_pool_t *p) { ap_register_output_filter("LT_INSERT", lt_insert_filter, NULL, AP_FTYPE_CONTENT_SET); } module AP_MODULE_DECLARE_DATA lt_insert_module = { STANDARD20_MODULE_STUFF, 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 apr_table_t */ register_hooks /* register hooks */ }; ---------------------------------------------------------------- -- Drew