1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17/* 18 * mod_bucketeer.c: split buckets whenever we find a control-char 19 * 20 * Written by Ian Holsman 21 * 22 */ 23 24#include "httpd.h" 25#include "http_config.h" 26#include "http_log.h" 27#include "apr_strings.h" 28#include "apr_general.h" 29#include "util_filter.h" 30#include "apr_buckets.h" 31#include "http_request.h" 32#include "http_protocol.h" 33 34static const char bucketeerFilterName[] = "BUCKETEER"; 35module AP_MODULE_DECLARE_DATA bucketeer_module; 36 37typedef struct bucketeer_filter_config_t 38{ 39 char bucketdelimiter; 40 char passdelimiter; 41 char flushdelimiter; 42} bucketeer_filter_config_t; 43 44 45static void *create_bucketeer_server_config(apr_pool_t *p, server_rec *s) 46{ 47 bucketeer_filter_config_t *c = apr_pcalloc(p, sizeof *c); 48 49 c->bucketdelimiter = 0x02; /* ^B */ 50 c->passdelimiter = 0x10; /* ^P */ 51 c->flushdelimiter = 0x06; /* ^F */ 52 53 return c; 54} 55 56typedef struct bucketeer_ctx_t 57{ 58 apr_bucket_brigade *bb; 59} bucketeer_ctx_t; 60 61static apr_status_t bucketeer_out_filter(ap_filter_t *f, 62 apr_bucket_brigade *bb) 63{ 64 apr_bucket *e; 65 request_rec *r = f->r; 66 bucketeer_ctx_t *ctx = f->ctx; 67 bucketeer_filter_config_t *c; 68 69 c = ap_get_module_config(r->server->module_config, &bucketeer_module); 70 71 /* If have a context, it means we've done this before successfully. */ 72 if (!ctx) { 73 if (!r->content_type || strncmp(r->content_type, "text/", 5)) { 74 ap_remove_output_filter(f); 75 return ap_pass_brigade(f->next, bb); 76 } 77 78 /* We're cool with filtering this. */ 79 ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); 80 ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); 81 apr_table_unset(f->r->headers_out, "Content-Length"); 82 } 83 84 for (e = APR_BRIGADE_FIRST(bb); 85 e != APR_BRIGADE_SENTINEL(bb); 86 e = APR_BUCKET_NEXT(e)) 87 { 88 const char *data; 89 apr_size_t len, i, lastpos; 90 91 if (APR_BUCKET_IS_EOS(e)) { 92 APR_BUCKET_REMOVE(e); 93 APR_BRIGADE_INSERT_TAIL(ctx->bb, e); 94 95 /* Okay, we've seen the EOS. 96 * Time to pass it along down the chain. 97 */ 98 return ap_pass_brigade(f->next, ctx->bb); 99 } 100 101 if (APR_BUCKET_IS_FLUSH(e)) { 102 /* 103 * Ignore flush buckets for the moment.. 104 * we decide what to stream 105 */ 106 continue; 107 } 108 109 if (APR_BUCKET_IS_METADATA(e)) { 110 /* metadata bucket */ 111 apr_bucket *cpy; 112 apr_bucket_copy(e, &cpy); 113 APR_BRIGADE_INSERT_TAIL(ctx->bb, cpy); 114 continue; 115 } 116 117 /* read */ 118 apr_bucket_read(e, &data, &len, APR_BLOCK_READ); 119 120 if (len > 0) { 121 lastpos = 0; 122 for (i = 0; i < len; i++) { 123 if (data[i] == c->flushdelimiter || 124 data[i] == c->bucketdelimiter || 125 data[i] == c->passdelimiter) { 126 apr_bucket *p; 127 if (i - lastpos > 0) { 128 p = apr_bucket_pool_create(apr_pmemdup(f->r->pool, 129 &data[lastpos], 130 i - lastpos), 131 i - lastpos, 132 f->r->pool, 133 f->c->bucket_alloc); 134 APR_BRIGADE_INSERT_TAIL(ctx->bb, p); 135 } 136 lastpos = i + 1; 137 if (data[i] == c->flushdelimiter) { 138 p = apr_bucket_flush_create(f->c->bucket_alloc); 139 APR_BRIGADE_INSERT_TAIL(ctx->bb, p); 140 } 141 if (data[i] == c->flushdelimiter || 142 data[i] == c->passdelimiter) { 143 ap_pass_brigade(f->next, ctx->bb); 144 /* apr_brigade_cleanup(ctx->bb);*/ 145 } 146 } 147 } 148 /* XXX: really should append this to the next 'real' bucket */ 149 if (lastpos < i) { 150 apr_bucket *p; 151 p = apr_bucket_pool_create(apr_pmemdup(f->r->pool, 152 &data[lastpos], 153 i - lastpos), 154 i - lastpos, 155 f->r->pool, 156 f->c->bucket_alloc); 157 lastpos = i; 158 APR_BRIGADE_INSERT_TAIL(ctx->bb, p); 159 } 160 } 161 } 162 163 return APR_SUCCESS; 164} 165 166static void register_hooks(apr_pool_t * p) 167{ 168 ap_register_output_filter(bucketeerFilterName, bucketeer_out_filter, 169 NULL, AP_FTYPE_RESOURCE-1); 170} 171 172static const command_rec bucketeer_filter_cmds[] = { 173 {NULL} 174}; 175 176module AP_MODULE_DECLARE_DATA bucketeer_module = { 177 STANDARD20_MODULE_STUFF, 178 NULL, 179 NULL, 180 create_bucketeer_server_config, 181 NULL, 182 bucketeer_filter_cmds, 183 register_hooks 184}; 185