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_request.c --- HTTP routines to set aside or process request bodies.
19 */
20
21#include "apr.h"
22#include "apr_strings.h"
23#include "apr_buckets.h"
24#include "apr_lib.h"
25
26#include "ap_config.h"
27#include "httpd.h"
28#include "http_config.h"
29#include "http_protocol.h"
30#include "http_log.h"           /* For errors detected in basic auth common
31                                 * support code... */
32#include "http_request.h"
33
34#include "mod_request.h"
35
36/* Handles for core filters */
37static ap_filter_rec_t *keep_body_input_filter_handle;
38static ap_filter_rec_t *kept_body_input_filter_handle;
39
40static apr_status_t bail_out_on_error(apr_bucket_brigade *bb,
41                                      ap_filter_t *f,
42                                      int http_error)
43{
44    apr_bucket *e;
45
46    apr_brigade_cleanup(bb);
47    e = ap_bucket_error_create(http_error,
48                               NULL, f->r->pool,
49                               f->c->bucket_alloc);
50    APR_BRIGADE_INSERT_TAIL(bb, e);
51    e = apr_bucket_eos_create(f->c->bucket_alloc);
52    APR_BRIGADE_INSERT_TAIL(bb, e);
53    return ap_pass_brigade(f->r->output_filters, bb);
54}
55
56typedef struct keep_body_filter_ctx {
57    apr_off_t remaining;
58    apr_off_t keep_body;
59} keep_body_ctx_t;
60
61/**
62 * This is the KEEP_BODY_INPUT filter for HTTP requests, for times when the
63 * body should be set aside for future use by other modules.
64 */
65static apr_status_t keep_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
66                                     ap_input_mode_t mode,
67                                     apr_read_type_e block,
68                                     apr_off_t readbytes)
69{
70    apr_bucket *e;
71    keep_body_ctx_t *ctx = f->ctx;
72    apr_status_t rv;
73    apr_bucket *bucket;
74    apr_off_t len = 0;
75
76
77    if (!ctx) {
78        const char *lenp;
79        char *endstr = NULL;
80        request_dir_conf *dconf = ap_get_module_config(f->r->per_dir_config,
81                                                       &request_module);
82
83        /* must we step out of the way? */
84        if (!dconf->keep_body || f->r->kept_body) {
85            ap_remove_input_filter(f);
86            return ap_get_brigade(f->next, b, mode, block, readbytes);
87        }
88
89        f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
90
91        /* fail fast if the content length exceeds keep body */
92        lenp = apr_table_get(f->r->headers_in, "Content-Length");
93        if (lenp) {
94
95            /* Protects against over/underflow, non-digit chars in the
96             * string (excluding leading space) (the endstr checks)
97             * and a negative number. */
98            if (apr_strtoff(&ctx->remaining, lenp, &endstr, 10)
99                || endstr == lenp || *endstr || ctx->remaining < 0) {
100
101                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01411)
102                              "Invalid Content-Length");
103
104                ap_remove_input_filter(f);
105                return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
106            }
107
108            /* If we have a limit in effect and we know the C-L ahead of
109             * time, stop it here if it is invalid.
110             */
111            if (dconf->keep_body < ctx->remaining) {
112                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01412)
113                          "Requested content-length of %" APR_OFF_T_FMT
114                          " is larger than the configured limit"
115                          " of %" APR_OFF_T_FMT, ctx->remaining, dconf->keep_body);
116                ap_remove_input_filter(f);
117                return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
118            }
119
120        }
121
122        f->r->kept_body = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc);
123        ctx->remaining = dconf->keep_body;
124
125    }
126
127    /* get the brigade from upstream, and read it in to get its length */
128    rv = ap_get_brigade(f->next, b, mode, block, readbytes);
129    if (rv == APR_SUCCESS) {
130        rv = apr_brigade_length(b, 1, &len);
131    }
132
133    /* does the length take us over the limit? */
134    if (APR_SUCCESS == rv && len > ctx->remaining) {
135        if (f->r->kept_body) {
136            apr_brigade_cleanup(f->r->kept_body);
137            f->r->kept_body = NULL;
138        }
139        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01413)
140                      "Requested content-length of %" APR_OFF_T_FMT
141                      " is larger than the configured limit"
142                      " of %" APR_OFF_T_FMT, len, ctx->keep_body);
143        return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
144    }
145    ctx->remaining -= len;
146
147    /* pass any errors downstream */
148    if (rv != APR_SUCCESS) {
149        if (f->r->kept_body) {
150            apr_brigade_cleanup(f->r->kept_body);
151            f->r->kept_body = NULL;
152        }
153        return rv;
154    }
155
156    /* all is well, set aside the buckets */
157    for (bucket = APR_BRIGADE_FIRST(b);
158         bucket != APR_BRIGADE_SENTINEL(b);
159         bucket = APR_BUCKET_NEXT(bucket))
160    {
161        apr_bucket_copy(bucket, &e);
162        APR_BRIGADE_INSERT_TAIL(f->r->kept_body, e);
163    }
164
165    return APR_SUCCESS;
166}
167
168
169typedef struct kept_body_filter_ctx {
170    apr_off_t offset;
171    apr_off_t remaining;
172} kept_body_ctx_t;
173
174/**
175 * Initialisation of filter to handle a kept body on subrequests.
176 *
177 * If a body is to be reinserted into a subrequest, any chunking will have
178 * been removed from the body during storage. We need to change the request
179 * from Transfer-Encoding: chunked to an explicit Content-Length.
180 */
181static int kept_body_filter_init(ap_filter_t *f) {
182    apr_off_t length = 0;
183    request_rec *r = f->r;
184    apr_bucket_brigade *kept_body = r->kept_body;
185
186    if (kept_body) {
187        apr_table_unset(r->headers_in, "Transfer-Encoding");
188        apr_brigade_length(kept_body, 1, &length);
189        apr_table_setn(r->headers_in, "Content-Length", apr_off_t_toa(r->pool, length));
190    }
191
192    return OK;
193}
194
195/**
196 * Filter to handle a kept body on subrequests.
197 *
198 * If a body has been previously kept by the request, and if a subrequest wants
199 * to re-insert the body into the request, this input filter makes it happen.
200 */
201static apr_status_t kept_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
202                                     ap_input_mode_t mode,
203                                     apr_read_type_e block,
204                                     apr_off_t readbytes)
205{
206    request_rec *r = f->r;
207    apr_bucket_brigade *kept_body = r->kept_body;
208    kept_body_ctx_t *ctx = f->ctx;
209    apr_bucket *ec, *e2;
210    apr_status_t rv;
211
212    /* just get out of the way of things we don't want. */
213    if (!kept_body || (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE)) {
214        return ap_get_brigade(f->next, b, mode, block, readbytes);
215    }
216
217    /* set up the context if it does not already exist */
218    if (!ctx) {
219        f->ctx = ctx = apr_palloc(f->r->pool, sizeof(*ctx));
220        ctx->offset = 0;
221        apr_brigade_length(kept_body, 1, &ctx->remaining);
222    }
223
224    /* kept_body is finished, send next filter */
225    if (ctx->remaining <= 0) {
226        return ap_get_brigade(f->next, b, mode, block, readbytes);
227    }
228
229    /* send all of the kept_body, but no more */
230    if (readbytes > ctx->remaining) {
231        readbytes = ctx->remaining;
232    }
233
234    /* send part of the kept_body */
235    if ((rv = apr_brigade_partition(kept_body, ctx->offset, &ec)) != APR_SUCCESS) {
236        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01414)
237                      "apr_brigade_partition() failed on kept_body at %" APR_OFF_T_FMT, ctx->offset);
238        return rv;
239    }
240    if ((rv = apr_brigade_partition(kept_body, ctx->offset + readbytes, &e2)) != APR_SUCCESS) {
241        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01415)
242                      "apr_brigade_partition() failed on kept_body at %" APR_OFF_T_FMT, ctx->offset + readbytes);
243        return rv;
244    }
245
246    do {
247        apr_bucket *foo;
248        const char *str;
249        apr_size_t len;
250
251        if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
252            /* As above; this should not fail since the bucket has
253             * a known length, but just to be sure, this takes
254             * care of uncopyable buckets that do somehow manage
255             * to slip through.  */
256            /* XXX: check for failure? */
257            apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
258            apr_bucket_copy(ec, &foo);
259        }
260        APR_BRIGADE_INSERT_TAIL(b, foo);
261        ec = APR_BUCKET_NEXT(ec);
262    } while (ec != e2);
263
264    ctx->remaining -= readbytes;
265    ctx->offset += readbytes;
266    return APR_SUCCESS;
267
268}
269
270/**
271 * Check whether this filter is not already present.
272 */
273static int request_is_filter_present(request_rec * r, ap_filter_rec_t *fn)
274{
275    ap_filter_t * f = r->input_filters;
276    while (f) {
277        if (f->frec == fn) {
278            return 1;
279        }
280        f = f->next;
281    }
282    return 0;
283}
284
285/**
286 * Insert filter hook.
287 *
288 * Add the KEEP_BODY filter to the request, if the admin wants to keep
289 * the body using the KeptBodySize directive.
290 *
291 * As a precaution, any pre-existing instances of either the kept_body or
292 * keep_body filters will be removed before the filter is added.
293 *
294 * @param r The request
295 */
296static void ap_request_insert_filter(request_rec * r)
297{
298    request_dir_conf *conf = ap_get_module_config(r->per_dir_config,
299                                                  &request_module);
300
301    if (r->kept_body) {
302        if (!request_is_filter_present(r, kept_body_input_filter_handle)) {
303            ap_add_input_filter_handle(kept_body_input_filter_handle,
304                                       NULL, r, r->connection);
305        }
306    }
307    else if (conf->keep_body) {
308        if (!request_is_filter_present(r, kept_body_input_filter_handle)) {
309            ap_add_input_filter_handle(keep_body_input_filter_handle,
310                                       NULL, r, r->connection);
311        }
312    }
313
314}
315
316/**
317 * Remove the kept_body and keep body filters from this specific request.
318 */
319static void ap_request_remove_filter(request_rec * r)
320{
321    ap_filter_t * f = r->input_filters;
322    while (f) {
323        if (f->frec->filter_func.in_func == kept_body_filter ||
324                f->frec->filter_func.in_func == keep_body_filter) {
325            ap_remove_input_filter(f);
326        }
327        f = f->next;
328    }
329}
330
331static void *create_request_dir_config(apr_pool_t *p, char *dummy)
332{
333    request_dir_conf *new =
334        (request_dir_conf *) apr_pcalloc(p, sizeof(request_dir_conf));
335
336    new->keep_body_set = 0; /* unset */
337    new->keep_body = 0; /* don't by default */
338
339    return (void *) new;
340}
341
342static void *merge_request_dir_config(apr_pool_t *p, void *basev, void *addv)
343{
344    request_dir_conf *new = (request_dir_conf *) apr_pcalloc(p, sizeof(request_dir_conf));
345    request_dir_conf *add = (request_dir_conf *) addv;
346    request_dir_conf *base = (request_dir_conf *) basev;
347
348    new->keep_body = (add->keep_body_set == 0) ? base->keep_body : add->keep_body;
349    new->keep_body_set = add->keep_body_set || base->keep_body_set;
350
351    return new;
352}
353
354static const char *set_kept_body_size(cmd_parms *cmd, void *dconf,
355                                      const char *arg)
356{
357    request_dir_conf *conf = dconf;
358    char *end = NULL;
359
360    if (APR_SUCCESS != apr_strtoff(&(conf->keep_body), arg, &end, 10)
361            || conf->keep_body < 0 || *end) {
362        return "KeptBodySize must be a valid size in bytes, or zero.";
363    }
364    conf->keep_body_set = 1;
365
366    return NULL;
367}
368
369static const command_rec request_cmds[] = {
370    AP_INIT_TAKE1("KeptBodySize", set_kept_body_size, NULL, ACCESS_CONF,
371                  "Maximum size of request bodies kept aside for use by filters"),
372    { NULL }
373};
374
375static void register_hooks(apr_pool_t *p)
376{
377    keep_body_input_filter_handle =
378        ap_register_input_filter(KEEP_BODY_FILTER, keep_body_filter,
379                                 NULL, AP_FTYPE_RESOURCE);
380    kept_body_input_filter_handle =
381        ap_register_input_filter(KEPT_BODY_FILTER, kept_body_filter,
382                                 kept_body_filter_init, AP_FTYPE_RESOURCE);
383    ap_hook_insert_filter(ap_request_insert_filter, NULL, NULL, APR_HOOK_LAST);
384    APR_REGISTER_OPTIONAL_FN(ap_request_insert_filter);
385    APR_REGISTER_OPTIONAL_FN(ap_request_remove_filter);
386}
387
388AP_DECLARE_MODULE(request) = {
389    STANDARD20_MODULE_STUFF,
390    create_request_dir_config, /* create per-directory config structure */
391    merge_request_dir_config,  /* merge per-directory config structures */
392    NULL,                      /* create per-server config structure */
393    NULL,                      /* merge per-server config structures */
394    request_cmds,              /* command apr_table_t */
395    register_hooks             /* register hooks */
396};
397