summaryrefslogtreecommitdiffstats
path: root/modules/filters/mod_request.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/filters/mod_request.c')
-rw-r--r--modules/filters/mod_request.c393
1 files changed, 393 insertions, 0 deletions
diff --git a/modules/filters/mod_request.c b/modules/filters/mod_request.c
new file mode 100644
index 0000000..1768edc
--- /dev/null
+++ b/modules/filters/mod_request.c
@@ -0,0 +1,393 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_request.c --- HTTP routines to set aside or process request bodies.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_lib.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "http_log.h" /* For errors detected in basic auth common
+ * support code... */
+#include "http_request.h"
+
+#include "mod_request.h"
+
+/* Handles for core filters */
+static ap_filter_rec_t *keep_body_input_filter_handle;
+static ap_filter_rec_t *kept_body_input_filter_handle;
+
+static apr_status_t bail_out_on_error(apr_bucket_brigade *bb,
+ ap_filter_t *f,
+ int http_error)
+{
+ apr_bucket *e;
+
+ apr_brigade_cleanup(bb);
+ e = ap_bucket_error_create(http_error,
+ NULL, f->r->pool,
+ f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ return ap_pass_brigade(f->r->output_filters, bb);
+}
+
+typedef struct keep_body_filter_ctx {
+ apr_off_t remaining;
+ apr_off_t keep_body;
+} keep_body_ctx_t;
+
+/**
+ * This is the KEEP_BODY_INPUT filter for HTTP requests, for times when the
+ * body should be set aside for future use by other modules.
+ */
+static apr_status_t keep_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ apr_bucket *e;
+ keep_body_ctx_t *ctx = f->ctx;
+ apr_status_t rv;
+ apr_bucket *bucket;
+ apr_off_t len = 0;
+
+ if (!ctx) {
+ const char *lenp;
+ request_dir_conf *dconf = ap_get_module_config(f->r->per_dir_config,
+ &request_module);
+
+ /* must we step out of the way? */
+ if (!dconf->keep_body || f->r->kept_body) {
+ ap_remove_input_filter(f);
+ return ap_get_brigade(f->next, b, mode, block, readbytes);
+ }
+
+ f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
+
+ /* fail fast if the content length exceeds keep body */
+ lenp = apr_table_get(f->r->headers_in, "Content-Length");
+ if (lenp) {
+
+ /* Protects against over/underflow, non-digit chars in the
+ * string, leading plus/minus signs, trailing characters and
+ * a negative number.
+ */
+ if (!ap_parse_strict_length(&ctx->remaining, lenp)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01411)
+ "Invalid Content-Length '%s'", lenp);
+
+ ap_remove_input_filter(f);
+ return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
+ }
+
+ /* If we have a limit in effect and we know the C-L ahead of
+ * time, stop it here if it is invalid.
+ */
+ if (dconf->keep_body < ctx->remaining) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01412)
+ "Requested content-length of %" APR_OFF_T_FMT
+ " is larger than the configured limit"
+ " of %" APR_OFF_T_FMT, ctx->remaining, dconf->keep_body);
+ ap_remove_input_filter(f);
+ return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
+ }
+
+ }
+
+ f->r->kept_body = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc);
+ ctx->remaining = dconf->keep_body;
+ }
+
+ /* get the brigade from upstream, and read it in to get its length */
+ rv = ap_get_brigade(f->next, b, mode, block, readbytes);
+ if (rv == APR_SUCCESS) {
+ rv = apr_brigade_length(b, 1, &len);
+ }
+
+ /* does the length take us over the limit? */
+ if (APR_SUCCESS == rv && len > ctx->remaining) {
+ if (f->r->kept_body) {
+ apr_brigade_cleanup(f->r->kept_body);
+ f->r->kept_body = NULL;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01413)
+ "Requested content-length of %" APR_OFF_T_FMT
+ " is larger than the configured limit"
+ " of %" APR_OFF_T_FMT, len, ctx->keep_body);
+ return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
+ }
+ ctx->remaining -= len;
+
+ /* pass any errors downstream */
+ if (rv != APR_SUCCESS) {
+ if (f->r->kept_body) {
+ apr_brigade_cleanup(f->r->kept_body);
+ f->r->kept_body = NULL;
+ }
+ return rv;
+ }
+
+ /* all is well, set aside the buckets */
+ for (bucket = APR_BRIGADE_FIRST(b);
+ bucket != APR_BRIGADE_SENTINEL(b);
+ bucket = APR_BUCKET_NEXT(bucket))
+ {
+ apr_bucket_copy(bucket, &e);
+ APR_BRIGADE_INSERT_TAIL(f->r->kept_body, e);
+ }
+
+ return APR_SUCCESS;
+}
+
+
+typedef struct kept_body_filter_ctx {
+ apr_off_t offset;
+ apr_off_t remaining;
+} kept_body_ctx_t;
+
+/**
+ * Initialisation of filter to handle a kept body on subrequests.
+ *
+ * If a body is to be reinserted into a subrequest, any chunking will have
+ * been removed from the body during storage. We need to change the request
+ * from Transfer-Encoding: chunked to an explicit Content-Length.
+ */
+static int kept_body_filter_init(ap_filter_t *f)
+{
+ apr_off_t length = 0;
+ request_rec *r = f->r;
+ apr_bucket_brigade *kept_body = r->kept_body;
+
+ if (kept_body) {
+ apr_table_unset(r->headers_in, "Transfer-Encoding");
+ apr_brigade_length(kept_body, 1, &length);
+ apr_table_setn(r->headers_in, "Content-Length", apr_off_t_toa(r->pool, length));
+ }
+
+ return OK;
+}
+
+/**
+ * Filter to handle a kept body on subrequests.
+ *
+ * If a body has been previously kept by the request, and if a subrequest wants
+ * to re-insert the body into the request, this input filter makes it happen.
+ */
+static apr_status_t kept_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ request_rec *r = f->r;
+ apr_bucket_brigade *kept_body = r->kept_body;
+ kept_body_ctx_t *ctx = f->ctx;
+ apr_bucket *ec, *e2;
+ apr_status_t rv;
+
+ /* just get out of the way of things we don't want. */
+ if (!kept_body || (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE)) {
+ return ap_get_brigade(f->next, b, mode, block, readbytes);
+ }
+
+ /* set up the context if it does not already exist */
+ if (!ctx) {
+ f->ctx = ctx = apr_palloc(f->r->pool, sizeof(*ctx));
+ ctx->offset = 0;
+ apr_brigade_length(kept_body, 1, &ctx->remaining);
+ }
+
+ /* kept_body is finished, send next filter */
+ if (ctx->remaining <= 0) {
+ return ap_get_brigade(f->next, b, mode, block, readbytes);
+ }
+
+ /* send all of the kept_body, but no more */
+ if (readbytes > ctx->remaining) {
+ readbytes = ctx->remaining;
+ }
+
+ /* send part of the kept_body */
+ if ((rv = apr_brigade_partition(kept_body, ctx->offset, &ec)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01414)
+ "apr_brigade_partition() failed on kept_body at %" APR_OFF_T_FMT, ctx->offset);
+ return rv;
+ }
+ if ((rv = apr_brigade_partition(kept_body, ctx->offset + readbytes, &e2)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01415)
+ "apr_brigade_partition() failed on kept_body at %" APR_OFF_T_FMT, ctx->offset + readbytes);
+ return rv;
+ }
+
+ do {
+ apr_bucket *foo;
+ const char *str;
+ apr_size_t len;
+
+ if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
+ /* As above; this should not fail since the bucket has
+ * a known length, but just to be sure, this takes
+ * care of uncopyable buckets that do somehow manage
+ * to slip through. */
+ /* XXX: check for failure? */
+ apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
+ apr_bucket_copy(ec, &foo);
+ }
+ APR_BRIGADE_INSERT_TAIL(b, foo);
+ ec = APR_BUCKET_NEXT(ec);
+ } while (ec != e2);
+
+ ctx->remaining -= readbytes;
+ ctx->offset += readbytes;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Check whether this filter is not already present.
+ */
+static int request_is_filter_present(request_rec * r, ap_filter_rec_t *fn)
+{
+ ap_filter_t * f = r->input_filters;
+ while (f) {
+ if (f->frec == fn) {
+ return 1;
+ }
+ f = f->next;
+ }
+ return 0;
+}
+
+/**
+ * Insert filter hook.
+ *
+ * Add the KEEP_BODY filter to the request, if the admin wants to keep
+ * the body using the KeptBodySize directive.
+ *
+ * As a precaution, any pre-existing instances of either the kept_body or
+ * keep_body filters will be removed before the filter is added.
+ *
+ * @param r The request
+ */
+static void ap_request_insert_filter(request_rec * r)
+{
+ request_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &request_module);
+
+ if (r->kept_body) {
+ if (!request_is_filter_present(r, kept_body_input_filter_handle)) {
+ ap_add_input_filter_handle(kept_body_input_filter_handle,
+ NULL, r, r->connection);
+ }
+ }
+ else if (conf->keep_body) {
+ if (!request_is_filter_present(r, kept_body_input_filter_handle)) {
+ ap_add_input_filter_handle(keep_body_input_filter_handle,
+ NULL, r, r->connection);
+ }
+ }
+}
+
+/*
+ * Remove the kept_body and keep_body filters from this specific request.
+ */
+static void ap_request_remove_filter(request_rec *r)
+{
+ ap_filter_t *f = r->input_filters;
+
+ while (f) {
+ if (f->frec->filter_func.in_func == kept_body_filter ||
+ f->frec->filter_func.in_func == keep_body_filter) {
+ ap_remove_input_filter(f);
+ }
+ f = f->next;
+ }
+}
+
+static void *create_request_dir_config(apr_pool_t *p, char *dummy)
+{
+ request_dir_conf *new =
+ (request_dir_conf *) apr_pcalloc(p, sizeof(request_dir_conf));
+
+ new->keep_body_set = 0; /* unset */
+ new->keep_body = 0; /* don't by default */
+
+ return (void *) new;
+}
+
+static void *merge_request_dir_config(apr_pool_t *p, void *basev, void *addv)
+{
+ request_dir_conf *new = (request_dir_conf *) apr_pcalloc(p, sizeof(request_dir_conf));
+ request_dir_conf *add = (request_dir_conf *) addv;
+ request_dir_conf *base = (request_dir_conf *) basev;
+
+ new->keep_body = (add->keep_body_set == 0) ? base->keep_body : add->keep_body;
+ new->keep_body_set = add->keep_body_set || base->keep_body_set;
+
+ return new;
+}
+
+static const char *set_kept_body_size(cmd_parms *cmd, void *dconf,
+ const char *arg)
+{
+ request_dir_conf *conf = dconf;
+ char *end = NULL;
+
+ if (APR_SUCCESS != apr_strtoff(&(conf->keep_body), arg, &end, 10)
+ || conf->keep_body < 0 || *end) {
+ return "KeptBodySize must be a valid size in bytes, or zero.";
+ }
+ conf->keep_body_set = 1;
+
+ return NULL;
+}
+
+static const command_rec request_cmds[] = {
+ AP_INIT_TAKE1("KeptBodySize", set_kept_body_size, NULL, ACCESS_CONF,
+ "Maximum size of request bodies kept aside for use by filters"),
+ { NULL }
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ keep_body_input_filter_handle =
+ ap_register_input_filter(KEEP_BODY_FILTER, keep_body_filter,
+ NULL, AP_FTYPE_RESOURCE);
+ kept_body_input_filter_handle =
+ ap_register_input_filter(KEPT_BODY_FILTER, kept_body_filter,
+ kept_body_filter_init, AP_FTYPE_RESOURCE);
+ ap_hook_insert_filter(ap_request_insert_filter, NULL, NULL, APR_HOOK_LAST);
+ APR_REGISTER_OPTIONAL_FN(ap_request_insert_filter);
+ APR_REGISTER_OPTIONAL_FN(ap_request_remove_filter);
+}
+
+AP_DECLARE_MODULE(request) = {
+ STANDARD20_MODULE_STUFF,
+ create_request_dir_config, /* create per-directory config structure */
+ merge_request_dir_config, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ request_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};