blob: 08e47892acf0ffa639e6c63853952dedfc05dd2a [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 "apr_strings.h"
#include "apreq_module_apache.h"
#include "http_main.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"
#include "apreq_private_apache.h"
/* This is basically the cgi_handle struct with a request_rec */
struct apache_handle {
struct apreq_handle_t handle;
request_rec *r;
apr_pool_t *pool;
apr_bucket_alloc_t *bucket_alloc;
apr_table_t *jar, *args, *body;
apr_status_t jar_status,
args_status,
body_status;
apreq_parser_t *parser;
apreq_hook_t *hook_queue;
const char *temp_dir;
apr_size_t brigade_limit;
apr_uint64_t read_limit;
apr_uint64_t bytes_read;
apr_bucket_brigade *in;
};
static const char *apache_header_in(apreq_handle_t *env, const char *name)
{
struct apache_handle *req = (struct apache_handle *)env;
return ap_table_get(req->r->headers_in, name);
}
static apr_status_t apache_header_out(apreq_handle_t *env,
const char *name, char *value)
{
struct apache_handle *req = (struct apache_handle *)env;
ap_table_add(req->r->err_headers_out, name, value);
return APR_SUCCESS;
}
#ifdef APR_POOL_DEBUG
static apr_status_t ba_cleanup(void *data)
{
apr_bucket_alloc_t *ba = data;
apr_bucket_alloc_destroy(ba);
return APR_SUCCESS;
}
#endif
static void init_body(apreq_handle_t *env)
{
struct apache_handle *req = (struct apache_handle *)env;
const char *cl_header = apache_header_in(env, "Content-Length");
apr_bucket_alloc_t *ba = req->bucket_alloc;
request_rec *r = req->r;
req->body = apr_table_make(req->pool, APREQ_DEFAULT_NELTS);
#ifdef APR_POOL_DEBUG
apr_pool_cleanup_register(req->pool, ba, ba_cleanup, ba_cleanup);
#endif
if (cl_header != NULL) {
char *dummy;
apr_int64_t content_length = apr_strtoi64(cl_header, &dummy, 10);
if (dummy == NULL || *dummy != 0) {
req->body_status = APREQ_ERROR_BADHEADER;
ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
"Invalid Content-Length header (%s)", cl_header);
return;
}
else if ((apr_uint64_t)content_length > req->read_limit) {
req->body_status = APREQ_ERROR_OVERLIMIT;
ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
"Content-Length header (%s) exceeds configured "
"max_body limit (%" APR_UINT64_T_FMT ")",
cl_header, req->read_limit);
return;
}
}
if (req->parser == NULL) {
const char *ct_header = apache_header_in(env, "Content-Type");
if (ct_header != NULL) {
apreq_parser_function_t pf = apreq_parser(ct_header);
if (pf != NULL) {
req->parser = apreq_parser_make(req->pool,
ba,
ct_header,
pf,
req->brigade_limit,
req->temp_dir,
req->hook_queue,
NULL);
}
else {
req->body_status = APREQ_ERROR_NOPARSER;
return;
}
}
else {
req->body_status = APREQ_ERROR_NOHEADER;
return;
}
}
else {
if (req->parser->brigade_limit > req->brigade_limit)
req->parser->brigade_limit = req->brigade_limit;
if (req->temp_dir != NULL)
req->parser->temp_dir = req->temp_dir;
if (req->hook_queue != NULL)
apreq_parser_add_hook(req->parser, req->hook_queue);
}
req->hook_queue = NULL;
req->in = apr_brigade_create(req->pool, ba);
req->body_status = APR_INCOMPLETE;
}
static apr_status_t apache_read(apreq_handle_t *env,
apr_off_t bytes)
{
struct apache_handle *req = (struct apache_handle *)env;
request_rec *r = req->r;
apr_bucket *e;
long got = 0;
char buf[HUGE_STRING_LEN];
char tc[] = "[apreq] apache_read";
int want = sizeof buf;
if (req->body_status == APR_EINIT)
init_body(env);
if (req->body_status != APR_INCOMPLETE)
return req->body_status;
/* XXX want a loop here, instead of reducing bytes */
if (bytes > HUGE_STRING_LEN)
want = HUGE_STRING_LEN;
else
want = bytes;
ap_hard_timeout(tc, r);
got = ap_get_client_block(r, buf, want);
ap_kill_timeout(r);
if (got > 0) {
e = apr_bucket_transient_create(buf, got, req->bucket_alloc);
req->bytes_read += got;
}
else
e = apr_bucket_eos_create(req->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(req->in, e);
if (req->bytes_read <= req->read_limit) {
req->body_status = apreq_parser_run(req->parser, req->body, req->in);
apr_brigade_cleanup(req->in);
}
else {
req->body_status = APREQ_ERROR_OVERLIMIT;
ap_log_rerror(APLOG_MARK, APLOG_ERR, req->r,
"Bytes read (%" APR_UINT64_T_FMT
") exceeds configured limit (%" APR_UINT64_T_FMT ")",
req->bytes_read, req->read_limit);
}
return req->body_status;
}
static apr_status_t apache_jar(apreq_handle_t *env, const apr_table_t **t)
{
struct apache_handle *req = (struct apache_handle *)env;
request_rec *r = req->r;
if (req->jar_status == APR_EINIT) {
const char *cookies = ap_table_get(r->headers_in, "Cookie");
if (cookies != NULL) {
req->jar = apr_table_make(req->pool, APREQ_DEFAULT_NELTS);
req->jar_status =
apreq_parse_cookie_header(req->pool, req->jar, cookies);
}
else
req->jar_status = APREQ_ERROR_NODATA;
}
*t = req->jar;
return req->jar_status;
}
static apr_status_t apache_args(apreq_handle_t *env, const apr_table_t **t)
{
struct apache_handle *req = (struct apache_handle *)env;
request_rec *r = req->r;
if (req->args_status == APR_EINIT) {
if (r->args != NULL) {
req->args = apr_table_make(req->pool, APREQ_DEFAULT_NELTS);
req->args_status =
apreq_parse_query_string(req->pool, req->args, r->args);
}
else
req->args_status = APREQ_ERROR_NODATA;
}
*t = req->args;
return req->args_status;
}
static apreq_cookie_t *apache_jar_get(apreq_handle_t *env, const char *name)
{
struct apache_handle *req = (struct apache_handle *)env;
const apr_table_t *t;
const char *val;
if (req->jar_status == APR_EINIT)
apache_jar(env, &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 *apache_args_get(apreq_handle_t *env, const char *name)
{
struct apache_handle *req = (struct apache_handle *)env;
const apr_table_t *t;
const char *val;
if (req->args_status == APR_EINIT)
apache_args(env, &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 apache_body(apreq_handle_t *env, const apr_table_t **t)
{
struct apache_handle *req = (struct apache_handle *)env;
switch (req->body_status) {
case APR_EINIT:
init_body(env);
if (req->body_status != APR_INCOMPLETE)
break;
case APR_INCOMPLETE:
while (apache_read(env, APREQ_DEFAULT_READ_BLOCK_SIZE) == APR_INCOMPLETE)
; /*loop*/
}
*t = req->body;
return req->body_status;
}
static apreq_param_t *apache_body_get(apreq_handle_t *env, const char *name)
{
struct apache_handle *req = (struct apache_handle *)env;
const char *val;
switch (req->body_status) {
case APR_EINIT:
init_body(env);
if (req->body_status != APR_INCOMPLETE)
return NULL;
apache_read(env, APREQ_DEFAULT_READ_BLOCK_SIZE);
case APR_INCOMPLETE:
val = apr_table_get(req->body, name);
if (val != NULL)
return apreq_value_to_param(val);
do {
/* riff on Duff's device */
apache_read(env, APREQ_DEFAULT_READ_BLOCK_SIZE);
default:
val = apr_table_get(req->body, name);
if (val != NULL)
return apreq_value_to_param(val);
} while (req->body_status == APR_INCOMPLETE);
}
return NULL;
}
static
apr_status_t apache_parser_get(apreq_handle_t *env,
const apreq_parser_t **parser)
{
struct apache_handle *req = (struct apache_handle *)env;
*parser = req->parser;
return APR_SUCCESS;
}
static
apr_status_t apache_parser_set(apreq_handle_t *env,
apreq_parser_t *parser)
{
struct apache_handle *req = (struct apache_handle *)env;
if (req->parser == NULL) {
if (req->hook_queue != NULL) {
apr_status_t s = apreq_parser_add_hook(parser, req->hook_queue);
if (s != APR_SUCCESS)
return s;
}
if (req->temp_dir != NULL) {
req->temp_dir = req->temp_dir;
}
if (req->brigade_limit < req->brigade_limit) {
req->brigade_limit = req->brigade_limit;
}
req->hook_queue = NULL;
req->parser = parser;
return APR_SUCCESS;
}
else
return APREQ_ERROR_MISMATCH;
}
static
apr_status_t apache_hook_add(apreq_handle_t *env,
apreq_hook_t *hook)
{
struct apache_handle *req = (struct apache_handle *)env;
if (req->parser != NULL) {
return apreq_parser_add_hook(req->parser, hook);
}
else if (req->hook_queue != NULL) {
apreq_hook_t *h = req->hook_queue;
while (h->next != NULL)
h = h->next;
h->next = hook;
}
else {
req->hook_queue = hook;
}
return APR_SUCCESS;
}
static
apr_status_t apache_brigade_limit_set(apreq_handle_t *env,
apr_size_t bytes)
{
struct apache_handle *req = (struct apache_handle *)env;
apr_size_t *limit = (req->parser == NULL)
? &req->brigade_limit
: &req->parser->brigade_limit;
if (*limit > bytes) {
*limit = bytes;
return APR_SUCCESS;
}
return APREQ_ERROR_MISMATCH;
}
static
apr_status_t apache_brigade_limit_get(apreq_handle_t *env,
apr_size_t *bytes)
{
struct apache_handle *req = (struct apache_handle *)env;
*bytes = (req->parser == NULL)
? req->brigade_limit
: req->parser->brigade_limit;
return APR_SUCCESS;
}
static
apr_status_t apache_read_limit_set(apreq_handle_t *env,
apr_uint64_t bytes)
{
struct apache_handle *req = (struct apache_handle *)env;
if (req->read_limit > bytes && req->bytes_read < bytes) {
req->read_limit = bytes;
return APR_SUCCESS;
}
return APREQ_ERROR_MISMATCH;
}
static
apr_status_t apache_read_limit_get(apreq_handle_t *env,
apr_uint64_t *bytes)
{
struct apache_handle *req = (struct apache_handle *)env;
*bytes = req->read_limit;
return APR_SUCCESS;
}
static
apr_status_t apache_temp_dir_set(apreq_handle_t *env,
const char *path)
{
struct apache_handle *req = (struct apache_handle *)env;
const char **curpath = (req->parser == NULL)
? &req->temp_dir
: &req->parser->temp_dir;
if (*curpath == NULL) {
*curpath = apr_pstrdup(req->pool, path);
return APR_SUCCESS;
}
return APREQ_ERROR_NOTEMPTY;
}
static
apr_status_t apache_temp_dir_get(apreq_handle_t *env,
const char **path)
{
struct apache_handle *req = (struct apache_handle *)env;
*path = req->parser ? req->parser->temp_dir : req->temp_dir;
return APR_SUCCESS;
}
static APREQ_MODULE(apache, APREQ_APACHE_MMN);
static void apreq_cleanup(void *data)
{
struct apache_handle *req = data;
apr_pool_destroy(req->pool);
}
APREQ_DECLARE(apreq_handle_t *) apreq_handle_apache(request_rec *r)
{
struct apache_handle *req =
ap_get_module_config(r->request_config, &apreq_module);
struct dir_config *d =
ap_get_module_config(r->per_dir_config, &apreq_module);
if (req != NULL)
return &req->handle;
apr_pool_create(&req->pool, NULL);
req = apr_pcalloc(req->pool, sizeof *req);
req->bucket_alloc = apr_bucket_alloc_create(req->pool);
req->handle.module = &apache_module;
req->r = r;
req->args_status = req->jar_status = req->body_status = APR_EINIT;
ap_register_cleanup(r->pool, req, apreq_cleanup, apreq_cleanup);
if (d == NULL) {
req->read_limit = (apr_uint64_t)-1;
req->brigade_limit = APREQ_DEFAULT_BRIGADE_LIMIT;
}
else {
req->temp_dir = d->temp_dir;
req->read_limit = d->read_limit;
req->brigade_limit = d->brigade_limit;
}
ap_set_module_config(r->request_config, &apreq_module, req);
return &req->handle;
}