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_buffer.c --- Buffer the input and output filter stacks, collapse
19  *                  many small buckets into fewer large buckets.
20  */
21 
22 #include "apr.h"
23 #include "apr_strings.h"
24 #include "apr_buckets.h"
25 #include "apr_lib.h"
26 
27 #include "ap_config.h"
28 #include "util_filter.h"
29 #include "httpd.h"
30 #include "http_config.h"
31 #include "http_log.h"
32 #include "http_request.h"
33 
34 static const char bufferFilterName[] = "BUFFER";
35 module AP_MODULE_DECLARE_DATA buffer_module;
36 
37 #define DEFAULT_BUFFER_SIZE 128*1024
38 
39 typedef struct buffer_conf {
40     apr_off_t size; /* size of the buffer */
41     int size_set; /* has the size been set */
42 } buffer_conf;
43 
44 typedef struct buffer_ctx {
45     apr_bucket_brigade *bb;
46     apr_bucket_brigade *tmp;
47     buffer_conf *conf;
48     apr_off_t remaining;
49     int seen_eos;
50 } buffer_ctx;
51 
52 /**
53  * Buffer buckets being written to the output filter stack.
54  */
buffer_out_filter(ap_filter_t * f,apr_bucket_brigade * bb)55 static apr_status_t buffer_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
56 {
57     apr_bucket *e;
58     request_rec *r = f->r;
59     buffer_ctx *ctx = f->ctx;
60     apr_status_t rv = APR_SUCCESS;
61     int move = 0;
62 
63     /* first time in? create a context */
64     if (!ctx) {
65 
66         /* buffering won't work on subrequests, it would be nice if
67          * it did. Within subrequests, we have no EOS to check for,
68          * so we don't know when to flush the buffer to the network
69          */
70         if (f->r->main) {
71             ap_remove_output_filter(f);
72             return ap_pass_brigade(f->next, bb);
73         }
74 
75         ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
76         ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
77         ctx->conf = ap_get_module_config(f->r->per_dir_config, &buffer_module);
78     }
79 
80     /* Do nothing if asked to filter nothing. */
81     if (APR_BRIGADE_EMPTY(bb)) {
82         return ap_pass_brigade(f->next, bb);
83     }
84 
85     /* Empty buffer means we can potentially optimise below */
86     if (APR_BRIGADE_EMPTY(ctx->bb)) {
87         move = 1;
88     }
89 
90     while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) {
91         const char *data;
92         apr_off_t len;
93         apr_size_t size;
94 
95         e = APR_BRIGADE_FIRST(bb);
96 
97         /* EOS means we are done. */
98         if (APR_BUCKET_IS_EOS(e)) {
99 
100             /* should we add an etag? */
101 
102             /* pass the EOS across */
103             APR_BUCKET_REMOVE(e);
104             APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
105 
106             /* pass what we have down the chain */
107             rv = ap_pass_brigade(f->next, ctx->bb);
108             continue;
109         }
110 
111         /* A flush takes precedence over buffering */
112         if (APR_BUCKET_IS_FLUSH(e)) {
113 
114             /* pass the flush bucket across */
115             APR_BUCKET_REMOVE(e);
116             APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
117 
118             /* pass what we have down the chain */
119             rv = ap_pass_brigade(f->next, ctx->bb);
120             continue;
121         }
122 
123         /* metadata buckets are preserved as is */
124         if (APR_BUCKET_IS_METADATA(e)) {
125             /*
126              * Remove meta data bucket from old brigade and insert into the
127              * new.
128              */
129             APR_BUCKET_REMOVE(e);
130             APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
131             continue;
132         }
133 
134         /* is our buffer full?
135          * If so, send what we have down the filter chain. If the buffer
136          * gets full, we can no longer compute a content length.
137          */
138         apr_brigade_length(ctx->bb, 1, &len);
139         if (len > ctx->conf->size) {
140 
141             /* pass what we have down the chain */
142             rv = ap_pass_brigade(f->next, ctx->bb);
143             if (rv) {
144                 /* should break out of the loop, since our write to the client
145                  * failed in some way. */
146                 continue;
147             }
148         }
149 
150         /* at this point we are ready to buffer.
151          * Buffering takes advantage of an optimisation in the handling of
152          * bucket brigades. Heap buckets are always created at a fixed
153          * size, regardless of the size of the data placed into them.
154          * The apr_brigade_write() call will first try and pack the data
155          * into any free space in the most recent heap bucket, before
156          * allocating a new bucket if necessary.
157          */
158         if (APR_SUCCESS == (rv = apr_bucket_read(e, &data, &size,
159                 APR_BLOCK_READ))) {
160 
161             /* further optimisation: if the buckets are already heap
162              * buckets, and the buckets stay exactly APR_BUCKET_BUFF_SIZE
163              * long (as they would be if we were reading bits of a
164              * large bucket), then move the buckets instead of copying
165              * them.
166              */
167             if (move && APR_BUCKET_IS_HEAP(e)) {
168                 APR_BUCKET_REMOVE(e);
169                 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
170                 if (APR_BUCKET_BUFF_SIZE != size) {
171                     move = 0;
172                 }
173             } else {
174                 apr_brigade_write(ctx->bb, NULL, NULL, data, size);
175                 apr_bucket_delete(e);
176             }
177 
178         }
179 
180     }
181 
182     return rv;
183 
184 }
185 
186 /**
187  * Buffer buckets being read from the input filter stack.
188  */
buffer_in_filter(ap_filter_t * f,apr_bucket_brigade * bb,ap_input_mode_t mode,apr_read_type_e block,apr_off_t readbytes)189 static apr_status_t buffer_in_filter(ap_filter_t *f, apr_bucket_brigade *bb,
190         ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes)
191 {
192     apr_bucket *e, *after;
193     apr_status_t rv;
194     buffer_ctx *ctx = f->ctx;
195 
196     /* buffer on main requests only */
197     if (!ap_is_initial_req(f->r)) {
198         ap_remove_input_filter(f);
199         return ap_get_brigade(f->next, bb, mode, block, readbytes);
200     }
201 
202     /* first time in? create a context */
203     if (!ctx) {
204         ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
205         ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
206         ctx->tmp = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
207         ctx->conf = ap_get_module_config(f->r->per_dir_config, &buffer_module);
208     }
209 
210     /* just get out of the way of things we don't want. */
211     if (mode != AP_MODE_READBYTES) {
212         return ap_get_brigade(f->next, bb, mode, block, readbytes);
213     }
214 
215     /* if our buffer is empty, read off the network until the buffer is full */
216     if (APR_BRIGADE_EMPTY(ctx->bb)) {
217         int seen_flush = 0;
218 
219         ctx->remaining = ctx->conf->size;
220 
221         while (!ctx->seen_eos && !seen_flush && ctx->remaining > 0) {
222             const char *data;
223             apr_size_t size = 0;
224 
225             if (APR_BRIGADE_EMPTY(ctx->tmp)) {
226                 rv = ap_get_brigade(f->next, ctx->tmp, mode, block,
227                                     ctx->remaining);
228 
229                 /* if an error was received, bail out now. If the error is
230                  * EAGAIN and we have not yet seen an EOS, we will definitely
231                  * be called again, at which point we will send our buffered
232                  * data. Instead of sending EAGAIN, some filters return an
233                  * empty brigade instead when data is not yet available. In
234                  * this case, pass through the APR_SUCCESS and emulate the
235                  * underlying filter.
236                  */
237                 if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(ctx->tmp)) {
238                     return rv;
239                 }
240             }
241 
242             do {
243                 e = APR_BRIGADE_FIRST(ctx->tmp);
244 
245                 /* if we see an EOS, we are done */
246                 if (APR_BUCKET_IS_EOS(e)) {
247                     APR_BUCKET_REMOVE(e);
248                     APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
249                     ctx->seen_eos = 1;
250                     break;
251                 }
252 
253                 /* flush buckets clear the buffer */
254                 if (APR_BUCKET_IS_FLUSH(e)) {
255                     APR_BUCKET_REMOVE(e);
256                     APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
257                     seen_flush = 1;
258                     break;
259                 }
260 
261                 /* pass metadata buckets through */
262                 if (APR_BUCKET_IS_METADATA(e)) {
263                     APR_BUCKET_REMOVE(e);
264                     APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
265                     continue;
266                 }
267 
268                 /* read the bucket in, pack it into the buffer */
269                 if (APR_SUCCESS == (rv = apr_bucket_read(e, &data, &size,
270                                                          APR_BLOCK_READ))) {
271                     apr_brigade_write(ctx->bb, NULL, NULL, data, size);
272                     ctx->remaining -= size;
273                     apr_bucket_delete(e);
274                 } else {
275                     return rv;
276                 }
277 
278             } while (!APR_BRIGADE_EMPTY(ctx->tmp));
279         }
280     }
281 
282     /* give the caller the data they asked for from the buffer */
283     apr_brigade_partition(ctx->bb, readbytes, &after);
284     e = APR_BRIGADE_FIRST(ctx->bb);
285     while (e != after) {
286         if (APR_BUCKET_IS_EOS(e)) {
287             /* last bucket read, step out of the way */
288             ap_remove_input_filter(f);
289         }
290         APR_BUCKET_REMOVE(e);
291         APR_BRIGADE_INSERT_TAIL(bb, e);
292         e = APR_BRIGADE_FIRST(ctx->bb);
293     }
294 
295     return APR_SUCCESS;
296 }
297 
create_buffer_config(apr_pool_t * p,char * dummy)298 static void *create_buffer_config(apr_pool_t *p, char *dummy)
299 {
300     buffer_conf *new = (buffer_conf *) apr_pcalloc(p, sizeof(buffer_conf));
301 
302     new->size_set = 0; /* unset */
303     new->size = DEFAULT_BUFFER_SIZE; /* default size */
304 
305     return (void *) new;
306 }
307 
merge_buffer_config(apr_pool_t * p,void * basev,void * addv)308 static void *merge_buffer_config(apr_pool_t *p, void *basev, void *addv)
309 {
310     buffer_conf *new = (buffer_conf *) apr_pcalloc(p, sizeof(buffer_conf));
311     buffer_conf *add = (buffer_conf *) addv;
312     buffer_conf *base = (buffer_conf *) basev;
313 
314     new->size = (add->size_set == 0) ? base->size : add->size;
315     new->size_set = add->size_set || base->size_set;
316 
317     return new;
318 }
319 
set_buffer_size(cmd_parms * cmd,void * dconf,const char * arg)320 static const char *set_buffer_size(cmd_parms *cmd, void *dconf, const char *arg)
321 {
322     buffer_conf *conf = dconf;
323 
324     if (APR_SUCCESS != apr_strtoff(&(conf->size), arg, NULL, 10) || conf->size
325             <= 0) {
326         return "BufferSize must be a size in bytes, and greater than zero";
327     }
328     conf->size_set = 1;
329 
330     return NULL;
331 }
332 
333 static const command_rec buffer_cmds[] = { AP_INIT_TAKE1("BufferSize",
334         set_buffer_size, NULL, ACCESS_CONF,
335         "Maximum size of the buffer used by the buffer filter"), { NULL } };
336 
register_hooks(apr_pool_t * p)337 static void register_hooks(apr_pool_t *p)
338 {
339     ap_register_output_filter(bufferFilterName, buffer_out_filter, NULL,
340             AP_FTYPE_CONTENT_SET);
341     ap_register_input_filter(bufferFilterName, buffer_in_filter, NULL,
342             AP_FTYPE_CONTENT_SET);
343 }
344 
345 AP_DECLARE_MODULE(buffer) = {
346     STANDARD20_MODULE_STUFF,
347     create_buffer_config, /* create per-directory config structure */
348     merge_buffer_config, /* merge per-directory config structures */
349     NULL, /* create per-server config structure */
350     NULL, /* merge per-server config structures */
351     buffer_cmds, /* command apr_table_t */
352     register_hooks /* register hooks */
353 };
354