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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * mod_request.c --- HTTP routines to set aside or process request bodies.
22 #include "apr_strings.h"
23 #include "apr_buckets.h"
26 #include "ap_config.h"
28 #include "http_config.h"
29 #include "http_protocol.h"
30 #include "http_log.h" /* For errors detected in basic auth common
32 #include "http_request.h"
34 #include "mod_request.h"
36 /* Handles for core filters */
37 static ap_filter_rec_t *keep_body_input_filter_handle;
38 static ap_filter_rec_t *kept_body_input_filter_handle;
40 static apr_status_t bail_out_on_error(apr_bucket_brigade *bb,
46 apr_brigade_cleanup(bb);
47 e = ap_bucket_error_create(http_error,
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);
56 typedef struct keep_body_filter_ctx {
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.
65 static apr_status_t keep_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
67 apr_read_type_e block,
71 keep_body_ctx_t *ctx = f->ctx;
80 request_dir_conf *dconf = ap_get_module_config(f->r->per_dir_config,
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);
89 f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
91 /* fail fast if the content length exceeds keep body */
92 lenp = apr_table_get(f->r->headers_in, "Content-Length");
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) {
101 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01411)
102 "Invalid Content-Length");
104 ap_remove_input_filter(f);
105 return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
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.
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);
122 f->r->kept_body = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc);
123 ctx->remaining = dconf->keep_body;
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);
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;
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);
145 ctx->remaining -= len;
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;
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))
161 apr_bucket_copy(bucket, &e);
162 APR_BRIGADE_INSERT_TAIL(f->r->kept_body, e);
169 typedef struct kept_body_filter_ctx {
175 * Initialisation of filter to handle a kept body on subrequests.
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.
181 static int kept_body_filter_init(ap_filter_t *f)
183 apr_off_t length = 0;
184 request_rec *r = f->r;
185 apr_bucket_brigade *kept_body = r->kept_body;
188 apr_table_unset(r->headers_in, "Transfer-Encoding");
189 apr_brigade_length(kept_body, 1, &length);
190 apr_table_setn(r->headers_in, "Content-Length", apr_off_t_toa(r->pool, length));
197 * Filter to handle a kept body on subrequests.
199 * If a body has been previously kept by the request, and if a subrequest wants
200 * to re-insert the body into the request, this input filter makes it happen.
202 static apr_status_t kept_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
203 ap_input_mode_t mode,
204 apr_read_type_e block,
207 request_rec *r = f->r;
208 apr_bucket_brigade *kept_body = r->kept_body;
209 kept_body_ctx_t *ctx = f->ctx;
213 /* just get out of the way of things we don't want. */
214 if (!kept_body || (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE)) {
215 return ap_get_brigade(f->next, b, mode, block, readbytes);
218 /* set up the context if it does not already exist */
220 f->ctx = ctx = apr_palloc(f->r->pool, sizeof(*ctx));
222 apr_brigade_length(kept_body, 1, &ctx->remaining);
225 /* kept_body is finished, send next filter */
226 if (ctx->remaining <= 0) {
227 return ap_get_brigade(f->next, b, mode, block, readbytes);
230 /* send all of the kept_body, but no more */
231 if (readbytes > ctx->remaining) {
232 readbytes = ctx->remaining;
235 /* send part of the kept_body */
236 if ((rv = apr_brigade_partition(kept_body, ctx->offset, &ec)) != APR_SUCCESS) {
237 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01414)
238 "apr_brigade_partition() failed on kept_body at %" APR_OFF_T_FMT, ctx->offset);
241 if ((rv = apr_brigade_partition(kept_body, ctx->offset + readbytes, &e2)) != APR_SUCCESS) {
242 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01415)
243 "apr_brigade_partition() failed on kept_body at %" APR_OFF_T_FMT, ctx->offset + readbytes);
252 if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
253 /* As above; this should not fail since the bucket has
254 * a known length, but just to be sure, this takes
255 * care of uncopyable buckets that do somehow manage
256 * to slip through. */
257 /* XXX: check for failure? */
258 apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
259 apr_bucket_copy(ec, &foo);
261 APR_BRIGADE_INSERT_TAIL(b, foo);
262 ec = APR_BUCKET_NEXT(ec);
265 ctx->remaining -= readbytes;
266 ctx->offset += readbytes;
272 * Check whether this filter is not already present.
274 static int request_is_filter_present(request_rec * r, ap_filter_rec_t *fn)
276 ap_filter_t * f = r->input_filters;
287 * Insert filter hook.
289 * Add the KEEP_BODY filter to the request, if the admin wants to keep
290 * the body using the KeptBodySize directive.
292 * As a precaution, any pre-existing instances of either the kept_body or
293 * keep_body filters will be removed before the filter is added.
295 * @param r The request
297 static void ap_request_insert_filter(request_rec * r)
299 request_dir_conf *conf = ap_get_module_config(r->per_dir_config,
303 if (!request_is_filter_present(r, kept_body_input_filter_handle)) {
304 ap_add_input_filter_handle(kept_body_input_filter_handle,
305 NULL, r, r->connection);
308 else if (conf->keep_body) {
309 if (!request_is_filter_present(r, kept_body_input_filter_handle)) {
310 ap_add_input_filter_handle(keep_body_input_filter_handle,
311 NULL, r, r->connection);
318 * Remove the kept_body and keep body filters from this specific request.
320 static void ap_request_remove_filter(request_rec * r)
322 ap_filter_t * f = r->input_filters;
324 if (f->frec->filter_func.in_func == kept_body_filter ||
325 f->frec->filter_func.in_func == keep_body_filter) {
326 ap_remove_input_filter(f);
332 static void *create_request_dir_config(apr_pool_t *p, char *dummy)
334 request_dir_conf *new =
335 (request_dir_conf *) apr_pcalloc(p, sizeof(request_dir_conf));
337 new->keep_body_set = 0; /* unset */
338 new->keep_body = 0; /* don't by default */
343 static void *merge_request_dir_config(apr_pool_t *p, void *basev, void *addv)
345 request_dir_conf *new = (request_dir_conf *) apr_pcalloc(p, sizeof(request_dir_conf));
346 request_dir_conf *add = (request_dir_conf *) addv;
347 request_dir_conf *base = (request_dir_conf *) basev;
349 new->keep_body = (add->keep_body_set == 0) ? base->keep_body : add->keep_body;
350 new->keep_body_set = add->keep_body_set || base->keep_body_set;
355 static const char *set_kept_body_size(cmd_parms *cmd, void *dconf,
358 request_dir_conf *conf = dconf;
361 if (APR_SUCCESS != apr_strtoff(&(conf->keep_body), arg, &end, 10)
362 || conf->keep_body < 0 || *end) {
363 return "KeptBodySize must be a valid size in bytes, or zero.";
365 conf->keep_body_set = 1;
370 static const command_rec request_cmds[] = {
371 AP_INIT_TAKE1("KeptBodySize", set_kept_body_size, NULL, ACCESS_CONF,
372 "Maximum size of request bodies kept aside for use by filters"),
376 static void register_hooks(apr_pool_t *p)
378 keep_body_input_filter_handle =
379 ap_register_input_filter(KEEP_BODY_FILTER, keep_body_filter,
380 NULL, AP_FTYPE_RESOURCE);
381 kept_body_input_filter_handle =
382 ap_register_input_filter(KEPT_BODY_FILTER, kept_body_filter,
383 kept_body_filter_init, AP_FTYPE_RESOURCE);
384 ap_hook_insert_filter(ap_request_insert_filter, NULL, NULL, APR_HOOK_LAST);
385 APR_REGISTER_OPTIONAL_FN(ap_request_insert_filter);
386 APR_REGISTER_OPTIONAL_FN(ap_request_remove_filter);
389 AP_DECLARE_MODULE(request) = {
390 STANDARD20_MODULE_STUFF,
391 create_request_dir_config, /* create per-directory config structure */
392 merge_request_dir_config, /* merge per-directory config structures */
393 NULL, /* create per-server config structure */
394 NULL, /* merge per-server config structures */
395 request_cmds, /* command apr_table_t */
396 register_hooks /* register hooks */