diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/apreq/Makefile.am | 41 | ||||
-rw-r--r-- | modules/apreq/apreq_module_apache2.h | 182 | ||||
-rw-r--r-- | modules/apreq/apreq_private_apache2.h | 56 | ||||
-rw-r--r-- | modules/apreq/filter.c | 540 | ||||
-rw-r--r-- | modules/apreq/handle.c | 441 |
5 files changed, 1260 insertions, 0 deletions
diff --git a/modules/apreq/Makefile.am b/modules/apreq/Makefile.am new file mode 100644 index 0000000000..230dbbee9d --- /dev/null +++ b/modules/apreq/Makefile.am @@ -0,0 +1,41 @@ +TEST_CONFIG_SCRIPT = package Apache::TestMM; filter_args(); generate_script("t/TEST") +mod_apreq2_la_LDFLAGS = -export-dynamic -module -avoid-version \ + `@APREQ_CONFIG@ --link-libtool --libs` @APR_LTFLAGS@ +mod_apreq2_la_SOURCES = apreq_private_apache2.h handle.c filter.c + +pkgcfgdir = `@APACHE2_APXS@ -q SYSCONFDIR` +pkgincludedir = `@APACHE2_APXS@ -q INCLUDEDIR`/@APREQ_LIBNAME@ +pkglibdir = `@APACHE2_APXS@ -q LIBEXECDIR` + +AM_CPPFLAGS = @APACHE2_INCLUDES@ @APR_INCLUDES@ + +if BUILD_HTTPD + +# XXX FIXME: static builds don't work anymore +# mod_apreq2 needs to be built from httpd-2.X, e.g. +# +# % cd ../httpd-2.X; +# % ./configure --with-module=filters:../httpd-apreq-2/module/apache2/mod_apreq2.c ... +# +# See the INSTALL file for details. + +@APACHE2_HTTPD@: + cd @APACHE2_SRC@ && $(MAKE) + +all-local: @APACHE2_HTTPD@ + +else + +pkginclude_HEADERS = apreq_module_apache2.h +pkglib_LTLIBRARIES = mod_apreq2.la + +install-exec-local : + @echo "----------------------------------------------------------------------" + @echo "Before you can use mod_apreq2, you must ensure that an appropriate" + @echo "\"LoadModule\" line appears in your webserver's config file:" + @echo "$(pkgcfgdir)/httpd.conf" + @echo + @echo "LoadModule apreq_module $(pkglibdir)/mod_apreq2.so" + @echo "----------------------------------------------------------------------" + +endif diff --git a/modules/apreq/apreq_module_apache2.h b/modules/apreq/apreq_module_apache2.h new file mode 100644 index 0000000000..e1642f5f67 --- /dev/null +++ b/modules/apreq/apreq_module_apache2.h @@ -0,0 +1,182 @@ +/* +** 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. +*/ + +#ifndef APREQ_APACHE2_H +#define APREQ_APACHE2_H + +#include "apreq_module.h" +#include "apr_optional.h" +#include <httpd.h> + +#ifdef __cplusplus + extern "C" { +#endif + + +/** + * @defgroup mod_apreq2 Apache 2.X Filter Module + * @ingroup apreq_module + * @brief mod_apreq2 - DSO that ties libapreq2 to Apache HTTPD 2.X. + * + * mod_apreq2 provides the "APREQ2" input filter for using libapreq2 + * (and allow its parsed data structures to be shared) within + * the Apache 2.X webserver. Using it, libapreq2 works properly + * in every phase of the HTTP request, from translation handlers + * to output filters, and even for subrequests / internal redirects. + * + * <hr> + * + * <h2>Activating mod_apreq2 in Apache 2.X</h2> + * + * The installation process triggered by + * <code>% make install</code> + * <em>will not modify your webserver's config file</em>. Hence, + * be sure you activate it on startup by adding a LoadModule directive + * to your webserver config; e.g. + * + * @code + * + * LoadModule apreq_module modules/mod_apreq2.so + * + * @endcode + * + * The mod_apreq2 filter is named "apreq2", and may be used in Apache's + * input filter directives, e.g. + * @code + * + * AddInputFilter apreq2 # or + * SetInputFilter apreq2 + * + * @endcode + * + * However, this is not required because libapreq2 will add the filter (only) + * if it's necessary. You just need to ensure that your module invokes + * apreq_handle_apache2() <em>before the content handler ultimately reads + * from the input filter chain</em>. It is important to realize that no + * matter how the input filters are initially arranged, the APREQ2 filter + * will attempt to reposition itself to be the last input filter to read the + * data. + * + * If you want to use other input filters to transform the incoming HTTP + * request data, is important to register those filters with Apache + * as having type AP_FTYPE_CONTENT_SET or AP_FTYPE_RESOURCE. Due to the + * limitations of Apache's current input filter design, types higher than + * AP_FTYPE_CONTENT_SET may not work properly whenever the apreq filter is + * active. + * + * This is especially true when a content handler uses libapreq2 to parse + * some of the post data before doing an internal redirect. Any input + * filter subsequently added to the redirected request will bypass the + * original apreq filter (and therefore lose access to some of the original + * post data), unless its type is less than the type of the apreq filter + * (currently AP_FTYPE_PROTOCOL-1). + * + * + * <H2>Server Configuration Directives</H2> + * + * <TABLE class="qref"> + * <CAPTION>Per-directory commands for mod_apreq2</CAPTION> + * <TR> + * <TH>Directive</TH> + * <TH>Context</TH> + * <TH>Default</TH><TH>Description</TH> + * </TR> + * <TR class="odd"> + * <TD>APREQ2_ReadLimit</TD> + * <TD>directory</TD> + * <TD> #APREQ_DEFAULT_READ_LIMIT </TD> + * <TD> Maximum number of bytes mod_apreq2 will send off to libapreq2 + * for parsing. mod_apreq2 will log this event and subsequently + * remove itself from the filter chain. + * </TD> + * </TR> + * <TR> + * <TD>APREQ2_BrigadeLimit</TD> + * <TD>directory</TD> + * <TD>#APREQ_DEFAULT_BRIGADE_LIMIT</TD> + * <TD> Maximum number of bytes mod_apreq2 will let accumulate + * within the heap-buckets in a brigade. Excess data will be + * spooled to an appended file bucket. + * </TD> + * </TR> + * <TR class="odd"> + * <TD>APREQ2_TempDir</TD> + * <TD>directory</TD> + * <TD>NULL</TD> + * <TD> Sets the location of the temporary directory apreq will use to spool + * overflow brigade data (based on the APREQ2_BrigadeLimit setting). + * If left unset, libapreq2 will select a platform-specific location + * via apr_temp_dir_get(). + * </TD> + * </TR> + * </TABLE> + * + * <H2>Implementation Details</H2> + * <PRE> + * XXX apreq as a normal input filter + * XXX apreq as a "virtual" content handler. + * XXX apreq as a transparent "tee". + * XXX apreq parser registration in post_config + * </PRE> + * + * @{ + */ +/** + * Create an apreq handle which communicates with an Apache 2.X + * request_rec. + */ +APREQ_DECLARE(apreq_handle_t *) apreq_handle_apache2(request_rec *r); + +/** + * + * + */ +#ifdef WIN32 +typedef __declspec(dllexport) apreq_handle_t * +(__stdcall apr_OFN_apreq_handle_apache2_t) (request_rec *r); +#else +APR_DECLARE_OPTIONAL_FN(APREQ_DECLARE(apreq_handle_t *), + apreq_handle_apache2, (request_rec *r)); +#endif + +/** + * The mod_apreq2 filter is named "apreq2", and may be used in Apache's + * input filter directives, e.g. + * @code + * + * AddInputFilter apreq2 # or + * SetInputFilter apreq2 + * @endcode + * See above + */ +#define APREQ_FILTER_NAME "apreq2" + +/** + * The Apache2 Module Magic Number for use in the Apache 2.x module structures + * This gets bumped if changes in th4e API will break third party applications + * using this apache2 module + * @see APREQ_MODULE + */ +#define APREQ_APACHE2_MMN 20101207 + +/** @} */ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/modules/apreq/apreq_private_apache2.h b/modules/apreq/apreq_private_apache2.h new file mode 100644 index 0000000000..173060dc48 --- /dev/null +++ b/modules/apreq/apreq_private_apache2.h @@ -0,0 +1,56 @@ +extern module AP_MODULE_DECLARE_DATA apreq_module; + +struct dir_config { + const char *temp_dir; + apr_uint64_t read_limit; + apr_size_t brigade_limit; +}; + +/* The "warehouse", stored in r->request_config */ +struct apache2_handle { + apreq_handle_t handle; + request_rec *r; + apr_table_t *jar, *args; + apr_status_t jar_status, args_status; + ap_filter_t *f; +}; + +/* Tracks the apreq filter state */ +struct filter_ctx { + apr_bucket_brigade *bb; /* input brigade that's passed to the parser */ + apr_bucket_brigade *bbtmp; /* temporary copy of bb, destined for the spool */ + apr_bucket_brigade *spool; /* copied prefetch data for downstream filters */ + apreq_parser_t *parser; + apreq_hook_t *hook_queue; + apreq_hook_t *find_param; + apr_table_t *body; + apr_status_t body_status; + apr_status_t filter_error; + apr_uint64_t bytes_read; /* Total bytes read into this filter. */ + apr_uint64_t read_limit; /* Max bytes the filter may show to parser */ + apr_size_t brigade_limit; + const char *temp_dir; +}; + +apr_status_t apreq_filter_prefetch(ap_filter_t *f, apr_off_t readbytes); +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); + +void apreq_filter_make_context(ap_filter_t *f); +void apreq_filter_init_context(ap_filter_t *f); + +APR_INLINE +static void apreq_filter_relocate(ap_filter_t *f) +{ + request_rec *r = f->r; + + if (f != r->input_filters) { + ap_filter_t *top = r->input_filters; + ap_remove_input_filter(f); + r->input_filters = f; + f->next = top; + } +} diff --git a/modules/apreq/filter.c b/modules/apreq/filter.c new file mode 100644 index 0000000000..ef122ba450 --- /dev/null +++ b/modules/apreq/filter.c @@ -0,0 +1,540 @@ +/* +** 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 "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 = -1; + dc->brigade_limit = -1; + 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) /* yes, 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; + } + APR_REGISTER_OPTIONAL_FN(apreq_handle_apache2); + 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 == (apr_uint64_t)-1) + ? APREQ_DEFAULT_READ_LIMIT : d->read_limit; + ctx->brigade_limit = (d->brigade_limit == (apr_size_t)-1) + ? APREQ_DEFAULT_BRIGADE_LIMIT : d->brigade_limit; + } + + f->ctx = ctx; +} diff --git a/modules/apreq/handle.c b/modules/apreq/handle.c new file mode 100644 index 0000000000..2de02dd229 --- /dev/null +++ b/modules/apreq/handle.c @@ -0,0 +1,441 @@ +/* +** 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 "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" + + +APR_INLINE +static ap_filter_t *get_apreq_filter(apreq_handle_t *handle) +{ + struct apache2_handle *req = (struct apache2_handle *)handle; + + if (req->f == NULL) { + req->f = ap_add_input_filter(APREQ_FILTER_NAME, NULL, + req->r, + req->r->connection); + /* ap_add_input_filter does not guarantee cfg->f == r->input_filters, + * so we reposition the new filter there as necessary. + */ + apreq_filter_relocate(req->f); + } + + return req->f; +} + + +static apr_status_t apache2_jar(apreq_handle_t *handle, const apr_table_t **t) +{ + struct apache2_handle *req = (struct apache2_handle*)handle; + request_rec *r = req->r; + + if (req->jar_status == APR_EINIT) { + const char *cookies = apr_table_get(r->headers_in, "Cookie"); + if (cookies != NULL) { + req->jar = apr_table_make(handle->pool, APREQ_DEFAULT_NELTS); + req->jar_status = + apreq_parse_cookie_header(handle->pool, req->jar, cookies); + } + else + req->jar_status = APREQ_ERROR_NODATA; + } + + *t = req->jar; + return req->jar_status; +} + +static apr_status_t apache2_args(apreq_handle_t *handle, const apr_table_t **t) +{ + struct apache2_handle *req = (struct apache2_handle*)handle; + request_rec *r = req->r; + + if (req->args_status == APR_EINIT) { + if (r->args != NULL) { + req->args = apr_table_make(handle->pool, APREQ_DEFAULT_NELTS); + req->args_status = + apreq_parse_query_string(handle->pool, req->args, r->args); + } + else + req->args_status = APREQ_ERROR_NODATA; + } + + *t = req->args; + return req->args_status; +} + + + + +static apreq_cookie_t *apache2_jar_get(apreq_handle_t *handle, const char *name) +{ + struct apache2_handle *req = (struct apache2_handle *)handle; + const apr_table_t *t; + const char *val; + + if (req->jar_status == APR_EINIT) + apache2_jar(handle, &t); + else + t = req->jar; + + if (t == NULL) + return NULL; + + val = apr_table_get(t, name); + if (val == NULL) + return NULL; + + return apreq_value_to_cookie(val); +} + +static apreq_param_t *apache2_args_get(apreq_handle_t *handle, const char *name) +{ + struct apache2_handle *req = (struct apache2_handle *)handle; + const apr_table_t *t; + const char *val; + + if (req->args_status == APR_EINIT) + apache2_args(handle, &t); + else + t = req->args; + + if (t == NULL) + return NULL; + + val = apr_table_get(t, name); + if (val == NULL) + return NULL; + + return apreq_value_to_param(val); +} + + +static apr_status_t apache2_body(apreq_handle_t *handle, const apr_table_t **t) +{ + ap_filter_t *f = get_apreq_filter(handle); + struct filter_ctx *ctx; + + if (f->ctx == NULL) + apreq_filter_make_context(f); + + ctx = f->ctx; + + switch (ctx->body_status) { + + case APR_EINIT: + apreq_filter_init_context(f); + if (ctx->body_status != APR_INCOMPLETE) + break; + + case APR_INCOMPLETE: + while (apreq_filter_prefetch(f, APREQ_DEFAULT_READ_BLOCK_SIZE) == APR_INCOMPLETE) + ; /*loop*/ + } + + *t = ctx->body; + return ctx->body_status; +} + +static apreq_param_t *apache2_body_get(apreq_handle_t *handle, const char *name) +{ + ap_filter_t *f = get_apreq_filter(handle); + struct filter_ctx *ctx; + const char *val; + apreq_hook_t *h; + apreq_hook_find_param_ctx_t *hook_ctx; + + if (f->ctx == NULL) + apreq_filter_make_context(f); + + ctx = f->ctx; + + switch (ctx->body_status) { + + case APR_SUCCESS: + + val = apr_table_get(ctx->body, name); + if (val != NULL) + return apreq_value_to_param(val); + return NULL; + + + case APR_EINIT: + + apreq_filter_init_context(f); + if (ctx->body_status != APR_INCOMPLETE) + return NULL; + apreq_filter_prefetch(f, APREQ_DEFAULT_READ_BLOCK_SIZE); + + + case APR_INCOMPLETE: + + val = apr_table_get(ctx->body, name); + if (val != NULL) + return apreq_value_to_param(val); + + /* Not seen yet, so we need to scan for + param while prefetching the body */ + hook_ctx = apr_palloc(handle->pool, sizeof *hook_ctx); + + if (ctx->find_param == NULL) + ctx->find_param = apreq_hook_make(handle->pool, + apreq_hook_find_param, + NULL, NULL); + h = ctx->find_param; + h->next = ctx->parser->hook; + h->ctx = hook_ctx; + ctx->parser->hook = h; + h->ctx = hook_ctx; + hook_ctx->name = name; + hook_ctx->param = NULL; + hook_ctx->prev = ctx->parser->hook; + + do { + apreq_filter_prefetch(f, APREQ_DEFAULT_READ_BLOCK_SIZE); + if (hook_ctx->param != NULL) + return hook_ctx->param; + } while (ctx->body_status == APR_INCOMPLETE); + + ctx->parser->hook = h->next; + return NULL; + + + default: + + if (ctx->body == NULL) + return NULL; + + val = apr_table_get(ctx->body, name); + if (val != NULL) + return apreq_value_to_param(val); + return NULL; + + } + + /* not reached */ + return NULL; +} + +static +apr_status_t apache2_parser_get(apreq_handle_t *handle, + const apreq_parser_t **parser) +{ + ap_filter_t *f = get_apreq_filter(handle); + struct filter_ctx *ctx = f->ctx; + + if (ctx == NULL) { + *parser = NULL; + return APR_EINIT; + } + *parser = ctx->parser; + return APR_SUCCESS; +} + +static +apr_status_t apache2_parser_set(apreq_handle_t *handle, + apreq_parser_t *parser) +{ + ap_filter_t *f = get_apreq_filter(handle); + struct filter_ctx *ctx; + + if (f->ctx == NULL) + apreq_filter_make_context(f); + + ctx = f->ctx; + + if (ctx->parser == NULL) { + ctx->parser = parser; + return APR_SUCCESS; + } + else + return APREQ_ERROR_NOTEMPTY; +} + + + +static +apr_status_t apache2_hook_add(apreq_handle_t *handle, + apreq_hook_t *hook) +{ + ap_filter_t *f = get_apreq_filter(handle); + struct filter_ctx *ctx; + + if (f->ctx == NULL) + apreq_filter_make_context(f); + + ctx = f->ctx; + + if (ctx->parser != NULL) { + return apreq_parser_add_hook(ctx->parser, hook); + } + else if (ctx->hook_queue != NULL) { + apreq_hook_t *h = ctx->hook_queue; + while (h->next != NULL) + h = h->next; + h->next = hook; + } + else { + ctx->hook_queue = hook; + } + return APR_SUCCESS; + +} + +static +apr_status_t apache2_brigade_limit_set(apreq_handle_t *handle, + apr_size_t bytes) +{ + ap_filter_t *f = get_apreq_filter(handle); + struct filter_ctx *ctx; + + if (f->ctx == NULL) + apreq_filter_make_context(f); + + ctx = f->ctx; + + if (ctx->body_status == APR_EINIT || ctx->brigade_limit > bytes) { + ctx->brigade_limit = bytes; + return APR_SUCCESS; + } + + return APREQ_ERROR_MISMATCH; +} + +static +apr_status_t apache2_brigade_limit_get(apreq_handle_t *handle, + apr_size_t *bytes) +{ + ap_filter_t *f = get_apreq_filter(handle); + struct filter_ctx *ctx; + + if (f->ctx == NULL) + apreq_filter_make_context(f); + + ctx = f->ctx; + *bytes = ctx->brigade_limit; + return APR_SUCCESS; +} + +static +apr_status_t apache2_read_limit_set(apreq_handle_t *handle, + apr_uint64_t bytes) +{ + ap_filter_t *f = get_apreq_filter(handle); + struct filter_ctx *ctx; + + if (f->ctx == NULL) + apreq_filter_make_context(f); + + ctx = f->ctx; + + if (ctx->read_limit > bytes && ctx->bytes_read < bytes) { + ctx->read_limit = bytes; + return APR_SUCCESS; + } + + return APREQ_ERROR_MISMATCH; +} + +static +apr_status_t apache2_read_limit_get(apreq_handle_t *handle, + apr_uint64_t *bytes) +{ + ap_filter_t *f = get_apreq_filter(handle); + struct filter_ctx *ctx; + + if (f->ctx == NULL) + apreq_filter_make_context(f); + + ctx = f->ctx; + *bytes = ctx->read_limit; + return APR_SUCCESS; +} + +static +apr_status_t apache2_temp_dir_set(apreq_handle_t *handle, + const char *path) +{ + ap_filter_t *f = get_apreq_filter(handle); + struct filter_ctx *ctx; + + if (f->ctx == NULL) + apreq_filter_make_context(f); + + ctx = f->ctx; + // init vs incomplete state? + if (ctx->temp_dir == NULL && ctx->bytes_read == 0) { + if (path != NULL) + ctx->temp_dir = apr_pstrdup(handle->pool, path); + return APR_SUCCESS; + } + + return APREQ_ERROR_NOTEMPTY; +} + +static +apr_status_t apache2_temp_dir_get(apreq_handle_t *handle, + const char **path) +{ + ap_filter_t *f = get_apreq_filter(handle); + struct filter_ctx *ctx; + + if (f->ctx == NULL) + apreq_filter_make_context(f); + + ctx = f->ctx; + *path = ctx->parser ? ctx->parser->temp_dir : ctx->temp_dir; + return APR_SUCCESS; +} + +static APREQ_MODULE(apache2, APREQ_APACHE2_MMN); + +APREQ_DECLARE(apreq_handle_t *) apreq_handle_apache2(request_rec *r) +{ + struct apache2_handle *req = + ap_get_module_config(r->request_config, &apreq_module); + + if (req != NULL) { + get_apreq_filter(&req->handle); + return &req->handle; + } + + req = apr_palloc(r->pool, sizeof *req); + ap_set_module_config(r->request_config, &apreq_module, req); + + req->handle.module = &apache2_module; + req->handle.pool = r->pool; + req->handle.bucket_alloc = r->connection->bucket_alloc; + req->r = r; + + req->args_status = req->jar_status = APR_EINIT; + req->args = req->jar = NULL; + + req->f = NULL; + + get_apreq_filter(&req->handle); + return &req->handle; + +} |