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