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