blob: 6c75a51b4861f36af455b89e70e005a672b6c8cc [file] [log] [blame]
/*
** 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.
*/
#include "assert.h"
#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "util_filter.h"
#include "apr_tables.h"
#include "apr_buckets.h"
#include "http_request.h"
#include "apr_strings.h"
#include "apreq_module_apache2.h"
#include "apreq_private_apache2.h"
#include "apreq_error.h"
#include "apreq_util.h"
#include "apreq_version.h"
static void *apreq_create_dir_config(apr_pool_t *p, char *d)
{
/* d == OR_ALL */
struct dir_config *dc = apr_palloc(p, sizeof *dc);
dc->temp_dir = NULL;
dc->read_limit = APREQ_DEFAULT_READ_LIMIT;
dc->brigade_limit = APREQ_DEFAULT_BRIGADE_LIMIT;
return dc;
}
static void *apreq_merge_dir_config(apr_pool_t *p, void *a_, void *b_)
{
struct dir_config *a = a_, *b = b_, *c = apr_palloc(p, sizeof *c);
c->temp_dir = (b->temp_dir != NULL) /* overrides ok */
? b->temp_dir : a->temp_dir;
c->brigade_limit = (b->brigade_limit == (apr_size_t)-1) /* overrides ok */
? a->brigade_limit : b->brigade_limit;
c->read_limit = (b->read_limit < a->read_limit) /* why min? */
? b->read_limit : a->read_limit;
return c;
}
static const char *apreq_set_temp_dir(cmd_parms *cmd, void *data,
const char *arg)
{
struct dir_config *conf = data;
const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
if (err != NULL)
return err;
conf->temp_dir = arg;
return NULL;
}
static const char *apreq_set_read_limit(cmd_parms *cmd, void *data,
const char *arg)
{
struct dir_config *conf = data;
const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
if (err != NULL)
return err;
conf->read_limit = apreq_atoi64f(arg);
return NULL;
}
static const char *apreq_set_brigade_limit(cmd_parms *cmd, void *data,
const char *arg)
{
struct dir_config *conf = data;
const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
if (err != NULL)
return err;
conf->brigade_limit = apreq_atoi64f(arg);
return NULL;
}
static const command_rec apreq_cmds[] =
{
AP_INIT_TAKE1("APREQ2_TempDir", apreq_set_temp_dir, NULL, OR_ALL,
"Default location of temporary directory"),
AP_INIT_TAKE1("APREQ2_ReadLimit", apreq_set_read_limit, NULL, OR_ALL,
"Maximum amount of data that will be fed into a parser."),
AP_INIT_TAKE1("APREQ2_BrigadeLimit", apreq_set_brigade_limit, NULL, OR_ALL,
"Maximum in-memory bytes a brigade may use."),
{ NULL }
};
void apreq_filter_init_context(ap_filter_t *f)
{
request_rec *r = f->r;
struct filter_ctx *ctx = f->ctx;
apr_bucket_alloc_t *ba = r->connection->bucket_alloc;
const char *cl_header;
if (r->method_number == M_GET) {
/* Don't parse GET (this protects against subrequest body parsing). */
ctx->body_status = APREQ_ERROR_NODATA;
return;
}
cl_header = apr_table_get(r->headers_in, "Content-Length");
if (cl_header != NULL) {
char *dummy;
apr_uint64_t content_length = apr_strtoi64(cl_header,&dummy,0);
if (dummy == NULL || *dummy != 0) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r,
"Invalid Content-Length header (%s)", cl_header);
ctx->body_status = APREQ_ERROR_BADHEADER;
return;
}
else if (content_length > ctx->read_limit) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r,
"Content-Length header (%s) exceeds configured "
"max_body limit (%" APR_UINT64_T_FMT ")",
cl_header, ctx->read_limit);
ctx->body_status = APREQ_ERROR_OVERLIMIT;
return;
}
}
if (ctx->parser == NULL) {
const char *ct_header = apr_table_get(r->headers_in, "Content-Type");
if (ct_header != NULL) {
apreq_parser_function_t pf = apreq_parser(ct_header);
if (pf != NULL) {
ctx->parser = apreq_parser_make(r->pool, ba, ct_header, pf,
ctx->brigade_limit,
ctx->temp_dir,
ctx->hook_queue,
NULL);
}
else {
ctx->body_status = APREQ_ERROR_NOPARSER;
return;
}
}
else {
ctx->body_status = APREQ_ERROR_NOHEADER;
return;
}
}
else {
if (ctx->parser->brigade_limit > ctx->brigade_limit)
ctx->parser->brigade_limit = ctx->brigade_limit;
if (ctx->temp_dir != NULL)
ctx->parser->temp_dir = ctx->temp_dir;
if (ctx->hook_queue != NULL)
apreq_parser_add_hook(ctx->parser, ctx->hook_queue);
}
ctx->hook_queue = NULL;
ctx->bb = apr_brigade_create(r->pool, ba);
ctx->bbtmp = apr_brigade_create(r->pool, ba);
ctx->spool = apr_brigade_create(r->pool, ba);
ctx->body = apr_table_make(r->pool, APREQ_DEFAULT_NELTS);
ctx->body_status = APR_INCOMPLETE;
}
/*
* Situations to contend with:
*
* 1) Often the filter will be added by the content handler itself,
* so the apreq_filter_init hook will not be run.
* 2) If an auth handler uses apreq, the apreq_filter will ensure
* it's part of the protocol filters. apreq_filter_init does NOT need
* to notify the protocol filter that it must not continue parsing,
* the apreq filter can perform this check itself. apreq_filter_init
* just needs to ensure cfg->f does not point at it.
* 3) If req->proto_input_filters and req->input_filters are apreq
* filters, and req->input_filters->next == req->proto_input_filters,
* it is safe for apreq_filter to "steal" the proto filter's context
* and subsequently drop it from the chain.
*/
/* Examines the input_filter chain and moves the apreq filter(s) around
* before the filter chain is stacked by ap_get_brigade.
*/
static apr_status_t apreq_filter_init(ap_filter_t *f)
{
request_rec *r = f->r;
struct filter_ctx *ctx = f->ctx;
struct apache2_handle *handle =
(struct apache2_handle *)apreq_handle_apache2(r);
/* Don't parse GET (this protects against subrequest body parsing). */
if (f->r->method_number == M_GET)
return APR_SUCCESS;
if (ctx == NULL || ctx->body_status == APR_EINIT) {
if (f == r->input_filters) {
handle->f = f;
}
else if (r->input_filters->frec->filter_func.in_func == apreq_filter) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r,
"removing intermediate apreq filter");
if (handle->f == f)
handle->f = r->input_filters;
ap_remove_input_filter(f);
}
else {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r,
"relocating intermediate apreq filter");
apreq_filter_relocate(f);
handle->f = f;
}
return APR_SUCCESS;
}
/* else this is a protocol filter which may still be active.
* if it is, we must deregister it now.
*/
if (handle->f == f) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r,
"disabling stale protocol filter");
if (ctx->body_status == APR_INCOMPLETE)
ctx->body_status = APREQ_ERROR_INTERRUPT;
handle->f = NULL;
}
return APR_SUCCESS;
}
apr_status_t apreq_filter_prefetch(ap_filter_t *f, apr_off_t readbytes)
{
struct filter_ctx *ctx = f->ctx;
request_rec *r = f->r;
apr_status_t rv;
apr_off_t len;
if (ctx->body_status == APR_EINIT)
apreq_filter_init_context(f);
if (ctx->body_status != APR_INCOMPLETE || readbytes == 0)
return ctx->body_status;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r,
"prefetching %" APR_OFF_T_FMT " bytes", readbytes);
rv = ap_get_brigade(f->next, ctx->bb, AP_MODE_READBYTES,
APR_BLOCK_READ, readbytes);
if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"ap_get_brigade failed during prefetch");
ctx->filter_error = rv;
return ctx->body_status = APREQ_ERROR_GENERAL;
}
apreq_brigade_setaside(ctx->bb, r->pool);
apreq_brigade_copy(ctx->bbtmp, ctx->bb);
rv = apreq_brigade_concat(r->pool, ctx->temp_dir, ctx->brigade_limit,
ctx->spool, ctx->bbtmp);
if (rv != APR_SUCCESS && rv != APR_EOF) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"apreq_brigade_concat failed; TempDir problem?");
ctx->filter_error = APR_EGENERAL;
return ctx->body_status = rv;
}
/* Adding "f" to the protocol filter chain ensures the
* spooled data is preserved across internal redirects.
*/
if (f != r->proto_input_filters) {
ap_filter_t *in;
for (in = r->input_filters; in != r->proto_input_filters;
in = in->next)
{
if (f == in) {
r->proto_input_filters = f;
break;
}
}
}
apr_brigade_length(ctx->bb, 1, &len);
ctx->bytes_read += len;
if (ctx->bytes_read > ctx->read_limit) {
ctx->body_status = APREQ_ERROR_OVERLIMIT;
ap_log_rerror(APLOG_MARK, APLOG_ERR, ctx->body_status, r,
"Bytes read (%" APR_UINT64_T_FMT
") exceeds configured read limit (%" APR_UINT64_T_FMT ")",
ctx->bytes_read, ctx->read_limit);
return ctx->body_status;
}
ctx->body_status = apreq_parser_run(ctx->parser, ctx->body, ctx->bb);
apr_brigade_cleanup(ctx->bb);
return ctx->body_status;
}
apr_status_t apreq_filter(ap_filter_t *f,
apr_bucket_brigade *bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes)
{
request_rec *r = f->r;
struct filter_ctx *ctx;
apr_status_t rv;
apr_off_t len;
switch (mode) {
case AP_MODE_READBYTES:
/* only the modes above are supported */
break;
case AP_MODE_EXHAUSTIVE: /* not worth supporting at this level */
case AP_MODE_GETLINE: /* chunked trailers are b0rked in ap_http_filter */
return ap_get_brigade(f->next, bb, mode, block, readbytes);
default:
return APR_ENOTIMPL;
}
if (f->ctx == NULL)
apreq_filter_make_context(f);
ctx = f->ctx;
if (ctx->body_status == APR_EINIT)
apreq_filter_init_context(f);
if (ctx->spool && !APR_BRIGADE_EMPTY(ctx->spool)) {
apr_bucket *e;
rv = apr_brigade_partition(ctx->spool, readbytes, &e);
if (rv != APR_SUCCESS && rv != APR_INCOMPLETE)
return rv;
if (APR_BUCKET_IS_EOS(e))
e = APR_BUCKET_NEXT(e);
apreq_brigade_move(bb, ctx->spool, e);
return APR_SUCCESS;
}
else if (ctx->body_status != APR_INCOMPLETE) {
if (ctx->filter_error)
return ctx->filter_error;
rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
ap_remove_input_filter(f);
return rv;
}
rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
if (rv != APR_SUCCESS)
return rv;
apreq_brigade_copy(ctx->bb, bb);
apr_brigade_length(bb, 1, &len);
ctx->bytes_read += len;
if (ctx->bytes_read > ctx->read_limit) {
ctx->body_status = APREQ_ERROR_OVERLIMIT;
ap_log_rerror(APLOG_MARK, APLOG_ERR, ctx->body_status, r,
"Bytes read (%" APR_UINT64_T_FMT
") exceeds configured max_body limit (%"
APR_UINT64_T_FMT ")",
ctx->bytes_read, ctx->read_limit);
}
else {
ctx->body_status = apreq_parser_run(ctx->parser, ctx->body, ctx->bb);
apr_brigade_cleanup(ctx->bb);
}
return APR_SUCCESS;
}
static int apreq_pre_init(apr_pool_t *p, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *base_server)
{
apr_status_t status;
status = apreq_pre_initialize(p);
if (status != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, status, base_server,
"Failed to pre-initialize libapreq2");
return HTTP_INTERNAL_SERVER_ERROR;
}
return OK;
}
static int apreq_post_init(apr_pool_t *p, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *base_server)
{
apr_status_t status;
ap_add_version_component(p, apr_psprintf(p,
"mod_apreq2-%d/%s",
APREQ_APACHE2_MMN,
apreq_version_string()));
status = apreq_post_initialize(p);
if (status != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, status, base_server,
"Failed to post-initialize libapreq2");
return HTTP_INTERNAL_SERVER_ERROR;
}
return OK;
}
static void register_hooks (apr_pool_t *p)
{
/* APR_HOOK_FIRST because we want other modules to be able to
* register parsers in their post_config hook via APR_HOOK_MIDDLE.
*/
ap_hook_post_config(apreq_pre_init, NULL, NULL, APR_HOOK_FIRST);
/* APR_HOOK_LAST because we need to lock the default_parsers hash
* (to prevent further modifications) before the server forks.
*/
ap_hook_post_config(apreq_post_init, NULL, NULL, APR_HOOK_LAST);
ap_register_input_filter(APREQ_FILTER_NAME, apreq_filter, apreq_filter_init,
AP_FTYPE_PROTOCOL-1);
}
/** @} */
module AP_MODULE_DECLARE_DATA apreq_module = {
#line __LINE__ "mod_apreq2.c"
STANDARD20_MODULE_STUFF,
apreq_create_dir_config,
apreq_merge_dir_config,
NULL,
NULL,
apreq_cmds,
register_hooks,
};
void apreq_filter_make_context(ap_filter_t *f)
{
request_rec *r;
struct filter_ctx *ctx;
struct dir_config *d;
r = f->r;
d = ap_get_module_config(r->per_dir_config, &apreq_module);
if (f == r->input_filters
&& r->proto_input_filters == f->next
&& f->next->frec->filter_func.in_func == apreq_filter
&& f->r->method_number != M_GET)
{
ctx = f->next->ctx;
switch (ctx->body_status) {
case APREQ_ERROR_INTERRUPT:
ctx->body_status = APR_INCOMPLETE;
/* fall thru */
case APR_SUCCESS:
if (d != NULL) {
ctx->temp_dir = d->temp_dir;
ctx->read_limit = d->read_limit;
ctx->brigade_limit = d->brigade_limit;
if (ctx->parser != NULL) {
ctx->parser->temp_dir = d->temp_dir;
ctx->parser->brigade_limit = d->brigade_limit;
}
}
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r,
"stealing filter context");
f->ctx = ctx;
r->proto_input_filters = f;
ap_remove_input_filter(f->next);
return;
default:
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, ctx->body_status, r,
"cannot steal context: bad filter status");
}
}
ctx = apr_pcalloc(r->pool, sizeof *ctx);
ctx->body_status = APR_EINIT;
if (d == NULL) {
ctx->read_limit = (apr_uint64_t)-1;
ctx->brigade_limit = APREQ_DEFAULT_BRIGADE_LIMIT;
} else {
ctx->temp_dir = d->temp_dir;
ctx->read_limit = d->read_limit;
ctx->brigade_limit = d->brigade_limit;
}
f->ctx = ctx;
}