mirror of
https://github.com/apache/httpd.git
synced 2025-04-18 22:24:07 +03:00
The function apreq_param_make() will return NULL on failure. However NULL check are forgetten before derenference, which could lead to NULL pointer dereference. Adding NULL check to all use of apreq_param_make(). Submitted by: Zhou Qingyang <zhou1615@umn.edu> Github: closes #303 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1908981 13f79535-47bb-0310-9956-ffa450edef68
1026 lines
30 KiB
C
1026 lines
30 KiB
C
/*
|
|
** 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.
|
|
*/
|
|
|
|
#define APR_WANT_STRFUNC
|
|
#include "apr_want.h"
|
|
#include "apreq_module.h"
|
|
#include "apreq_error.h"
|
|
#include "apr_strings.h"
|
|
#include "apr_lib.h"
|
|
#include "apr_env.h"
|
|
#include "apreq_util.h"
|
|
|
|
#include "httpd.h"
|
|
|
|
#define USER_DATA_KEY "apreq"
|
|
|
|
/* Parroting APLOG_* ... */
|
|
|
|
#define CGILOG_EMERG 0 /* system is unusable */
|
|
#define CGILOG_ALERT 1 /* action must be taken immediately */
|
|
#define CGILOG_CRIT 2 /* critical conditions */
|
|
#define CGILOG_ERR 3 /* error conditions */
|
|
#define CGILOG_WARNING 4 /* warning conditions */
|
|
#define CGILOG_NOTICE 5 /* normal but significant condition */
|
|
#define CGILOG_INFO 6 /* informational */
|
|
#define CGILOG_DEBUG 7 /* debug-level messages */
|
|
|
|
#define CGILOG_LEVELMASK 7
|
|
#define CGILOG_MARK __FILE__, __LINE__
|
|
|
|
/** Interactive patch:
|
|
* TODO Don't use 65K buffer
|
|
* TODO Handle empty/non-existent parameters
|
|
* TODO Allow body elements to be files
|
|
* TODO When running body/get/cookies all at once, include previous cached
|
|
* values (and don't start at 0 in count)
|
|
* TODO What happens if user does apreq_param, but needs POST value - we'll
|
|
* never catch it now, as args param will match...
|
|
*/
|
|
|
|
struct cgi_handle {
|
|
struct apreq_handle_t handle;
|
|
|
|
apr_table_t *jar, *args, *body;
|
|
apr_status_t jar_status,
|
|
args_status,
|
|
body_status;
|
|
|
|
apreq_parser_t *parser;
|
|
apreq_hook_t *hook_queue;
|
|
apreq_hook_t *find_param;
|
|
|
|
const char *temp_dir;
|
|
apr_size_t brigade_limit;
|
|
apr_uint64_t read_limit;
|
|
apr_uint64_t bytes_read;
|
|
|
|
apr_bucket_brigade *in;
|
|
apr_bucket_brigade *tmpbb;
|
|
|
|
int interactive_mode;
|
|
const char *promptstr;
|
|
apr_file_t *sout, *sin;
|
|
};
|
|
|
|
#define CRLF "\015\012"
|
|
static const char *nullstr = 0;
|
|
#define DEFAULT_PROMPT "([$t] )$n(\\($l\\))([$d]): "
|
|
#define MAX_PROMPT_NESTING_LEVELS 8
|
|
#define MAX_BUFFER_SIZE 65536
|
|
|
|
typedef struct {
|
|
const char *t_name;
|
|
int t_val;
|
|
} TRANS;
|
|
|
|
static const TRANS priorities[] = {
|
|
{"emerg", CGILOG_EMERG},
|
|
{"alert", CGILOG_ALERT},
|
|
{"crit", CGILOG_CRIT},
|
|
{"error", CGILOG_ERR},
|
|
{"warn", CGILOG_WARNING},
|
|
{"notice", CGILOG_NOTICE},
|
|
{"info", CGILOG_INFO},
|
|
{"debug", CGILOG_DEBUG},
|
|
{NULL, -1},
|
|
};
|
|
|
|
static char* chomp(char* str)
|
|
{
|
|
long p = (long)strlen(str);
|
|
while (--p >= 0) {
|
|
switch ((char)(str[p])) {
|
|
case '\015':
|
|
case '\012':str[p]='\000';
|
|
break;
|
|
default:return str;
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/** TODO: Support wide-characters */
|
|
/* prompt takes a apreq_handle and 2 strings - name and type - and prompts a
|
|
user for input via stdin/stdout. used in interactive mode.
|
|
|
|
name must be defined. type can be null.
|
|
|
|
we take the promptstring defined in the handle and interpolate variables as
|
|
follows:
|
|
|
|
$n - name of the variable we're asking for (param 2 to prompt())
|
|
$t - type of the variable we're asking for - like cookie, get, post, etc
|
|
(param 3 to prompt())
|
|
parentheses - if a variable is surrounded by parentheses, and interpolates
|
|
as null, then nothing else in the parentheses will be displayed
|
|
Useful if you want a string to only show up if a given variable
|
|
is available
|
|
|
|
These are planned for forward-compatibility, but the underlying features
|
|
need some love... I left these in here just as feature reminders, rather
|
|
than completely removing them from the code - at least they provide sanity
|
|
testing of the default prompt & parentheses - issac
|
|
|
|
$l - label for the param - the end-user-developer can provide a textual
|
|
description of the param (name) being requested (currently unused in
|
|
lib)
|
|
$d - default value for the param (currently unused in lib)
|
|
|
|
*/
|
|
static char *prompt(apreq_handle_t *handle, const char *name,
|
|
const char *type) {
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
const char *defval = nullstr;
|
|
const char *label = NULL;
|
|
const char *cprompt;
|
|
char buf[MAX_PROMPT_NESTING_LEVELS][MAX_BUFFER_SIZE];
|
|
/* Array of current arg for given p-level */
|
|
char *start, curarg[MAX_PROMPT_NESTING_LEVELS] = "";
|
|
/* Parenthesis level (for argument/text grouping) */
|
|
int plevel;
|
|
|
|
cprompt = req->promptstr - 1;
|
|
*buf[0] = plevel = 0;
|
|
start = buf[0];
|
|
|
|
while (*(++cprompt) != 0) {
|
|
switch (*cprompt) {
|
|
case '$': /* interpolate argument; curarg[plevel] => 1 */
|
|
cprompt++;
|
|
switch (*cprompt) {
|
|
case 't':
|
|
if (type != NULL) {
|
|
strcpy(start, type);
|
|
start += strlen(type);
|
|
curarg[plevel] = 1;
|
|
} else {
|
|
curarg[plevel] = curarg[plevel] | 0;
|
|
}
|
|
break;
|
|
case 'n':
|
|
/* Name can't be null :-) [If it can, we should
|
|
* immediately return NULL] */
|
|
strcpy(start, name);
|
|
start += strlen(name);
|
|
curarg[plevel] = 1;
|
|
break;
|
|
case 'l':
|
|
if (label != NULL) {
|
|
strcpy(start, label);
|
|
start += strlen(label);
|
|
curarg[plevel] = 1;
|
|
} else {
|
|
curarg[plevel] = curarg[plevel] | 0;
|
|
}
|
|
break;
|
|
case 'd':
|
|
/* TODO: Once null defaults are available,
|
|
* remove if and use nullstr if defval == NULL */
|
|
if (defval != NULL) {
|
|
strcpy(start, defval);
|
|
start += strlen(defval);
|
|
curarg[plevel] = 1;
|
|
} else {
|
|
curarg[plevel] = curarg[plevel] | 0;
|
|
}
|
|
break;
|
|
default:
|
|
/* Handle this? */
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case '(':
|
|
if (plevel <= MAX_PROMPT_NESTING_LEVELS) {
|
|
plevel++;
|
|
curarg[plevel] = *buf[plevel] = 0;
|
|
start = buf[plevel];
|
|
}
|
|
/* else? */
|
|
break;
|
|
|
|
case ')':
|
|
if (plevel > 0) {
|
|
*start = 0; /* Null terminate current string */
|
|
|
|
/* Move pointer to end of string */
|
|
plevel--;
|
|
start = buf[plevel] + strlen(buf[plevel]);
|
|
|
|
/* If old curarg was set, concat buffer with level down */
|
|
if (curarg[plevel + 1]) {
|
|
strcpy(start, buf[plevel + 1]);
|
|
start += strlen(buf[plevel + 1]);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case '\\': /* Check next character for escape sequence
|
|
* (just ignore it for now) */
|
|
(void)*cprompt++;
|
|
/* Fallthrough */
|
|
|
|
default:
|
|
*start++ = *cprompt;
|
|
}
|
|
}
|
|
|
|
*start = 0; /* Null terminate the string */
|
|
|
|
apr_file_printf(req->sout, "%s", buf[0]);
|
|
apr_file_gets(buf[0], MAX_BUFFER_SIZE, req->sin);
|
|
chomp(buf[0]);
|
|
if (strcmp(buf[0], "")) {
|
|
/* if (strcmp(buf[0], nullstr)) */
|
|
return apr_pstrdup(handle->pool, buf[0]);
|
|
/* return NULL; */
|
|
}
|
|
|
|
if (defval != nullstr)
|
|
return apr_pstrdup(handle->pool, defval);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char *cgi_header_in(apreq_handle_t *handle,
|
|
const char *name)
|
|
{
|
|
apr_pool_t *p = handle->pool;
|
|
char *key = apr_pstrcat(p, "HTTP_", name, NULL);
|
|
char *k, *value = NULL;
|
|
|
|
for (k = key+5; *k; ++k) {
|
|
if (*k == '-')
|
|
*k = '_';
|
|
else
|
|
*k = apr_toupper(*k);
|
|
}
|
|
|
|
if (!strcmp(key, "HTTP_CONTENT_TYPE")
|
|
|| !strcmp(key, "HTTP_CONTENT_LENGTH")) {
|
|
|
|
key += 5; /* strlen("HTTP_") */
|
|
}
|
|
|
|
apr_env_get(&value, key, p);
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
static void cgi_log_error(const char *file, int line, int level,
|
|
apr_status_t status, apreq_handle_t *handle,
|
|
const char *fmt, ...)
|
|
{
|
|
apr_pool_t *p = handle->pool;
|
|
char buf[256];
|
|
char *log_level_string, *ra;
|
|
const char *remote_addr;
|
|
unsigned log_level = CGILOG_WARNING;
|
|
char date[APR_CTIME_LEN];
|
|
va_list vp;
|
|
#ifndef WIN32
|
|
apr_file_t *err;
|
|
#endif
|
|
|
|
va_start(vp, fmt);
|
|
|
|
if (apr_env_get(&log_level_string, "LOG_LEVEL", p) == APR_SUCCESS)
|
|
log_level = (log_level_string[0] - '0');
|
|
|
|
level &= CGILOG_LEVELMASK;
|
|
|
|
if (level < (int)log_level) {
|
|
|
|
if (apr_env_get(&ra, "REMOTE_ADDR", p) == APR_SUCCESS)
|
|
remote_addr = ra;
|
|
else
|
|
remote_addr = "address unavailable";
|
|
|
|
apr_ctime(date, apr_time_now());
|
|
|
|
#ifndef WIN32
|
|
|
|
apr_file_open_stderr(&err, p);
|
|
apr_file_printf(err, "[%s] [%s] [%s] %s(%d): %s: %s\n",
|
|
date, priorities[level].t_name, remote_addr, file, line,
|
|
apr_strerror(status,buf,255),apr_pvsprintf(p,fmt,vp));
|
|
apr_file_flush(err);
|
|
|
|
#else
|
|
fprintf(stderr, "[%s] [%s] [%s] %s(%d): %s: %s\n",
|
|
date, priorities[level].t_name, remote_addr, file, line,
|
|
apr_strerror(status,buf,255),apr_pvsprintf(p,fmt,vp));
|
|
#endif
|
|
}
|
|
|
|
va_end(vp);
|
|
|
|
}
|
|
|
|
|
|
APR_INLINE
|
|
static const char *cgi_query_string(apreq_handle_t *handle)
|
|
{
|
|
char *value = NULL, qs[] = "QUERY_STRING";
|
|
apr_env_get(&value, qs, handle->pool);
|
|
return value;
|
|
}
|
|
|
|
|
|
static void init_body(apreq_handle_t *handle)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
const char *cl_header = cgi_header_in(handle, "Content-Length");
|
|
apr_bucket_alloc_t *ba = handle->bucket_alloc;
|
|
apr_pool_t *pool = handle->pool;
|
|
apr_file_t *file;
|
|
apr_bucket *eos, *pipe;
|
|
|
|
if (cl_header != NULL) {
|
|
apr_off_t content_length;
|
|
|
|
if (!ap_parse_strict_length(&content_length, cl_header)) {
|
|
req->body_status = APREQ_ERROR_BADHEADER;
|
|
cgi_log_error(CGILOG_MARK, CGILOG_ERR, req->body_status, handle,
|
|
"Invalid Content-Length header (%s)", cl_header);
|
|
return;
|
|
}
|
|
if ((apr_uint64_t)content_length > req->read_limit) {
|
|
req->body_status = APREQ_ERROR_OVERLIMIT;
|
|
cgi_log_error(CGILOG_MARK, CGILOG_ERR, req->body_status, handle,
|
|
"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 = cgi_header_in(handle, "Content-Type");
|
|
|
|
if (ct_header != NULL) {
|
|
apreq_parser_function_t pf = apreq_parser(ct_header);
|
|
|
|
if (pf != NULL) {
|
|
req->parser = apreq_parser_make(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(pool, ba);
|
|
req->tmpbb = apr_brigade_create(pool, ba);
|
|
|
|
apr_file_open_stdin(&file, pool); /* error status? */
|
|
pipe = apr_bucket_pipe_create(file, ba);
|
|
eos = apr_bucket_eos_create(ba);
|
|
APR_BRIGADE_INSERT_HEAD(req->in, pipe);
|
|
APR_BRIGADE_INSERT_TAIL(req->in, eos);
|
|
|
|
req->body_status = APR_INCOMPLETE;
|
|
|
|
}
|
|
|
|
static apr_status_t cgi_read(apreq_handle_t *handle,
|
|
apr_off_t bytes)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
apr_bucket *e;
|
|
apr_status_t s;
|
|
|
|
if (req->body_status == APR_EINIT)
|
|
init_body(handle);
|
|
|
|
if (req->body_status != APR_INCOMPLETE)
|
|
return req->body_status;
|
|
|
|
|
|
switch (s = apr_brigade_partition(req->in, bytes, &e)) {
|
|
apr_off_t len;
|
|
|
|
case APR_SUCCESS:
|
|
|
|
apreq_brigade_move(req->tmpbb, req->in, e);
|
|
req->bytes_read += bytes;
|
|
|
|
if (req->bytes_read > req->read_limit) {
|
|
req->body_status = APREQ_ERROR_OVERLIMIT;
|
|
cgi_log_error(CGILOG_MARK, CGILOG_ERR, req->body_status,
|
|
handle, "Bytes read (%" APR_UINT64_T_FMT
|
|
") exceeds configured limit (%" APR_UINT64_T_FMT ")",
|
|
req->bytes_read, req->read_limit);
|
|
break;
|
|
}
|
|
|
|
req->body_status =
|
|
apreq_parser_run(req->parser, req->body, req->tmpbb);
|
|
apr_brigade_cleanup(req->tmpbb);
|
|
break;
|
|
|
|
|
|
case APR_INCOMPLETE:
|
|
|
|
apreq_brigade_move(req->tmpbb, req->in, e);
|
|
s = apr_brigade_length(req->tmpbb, 1, &len);
|
|
|
|
if (s != APR_SUCCESS) {
|
|
req->body_status = s;
|
|
break;
|
|
}
|
|
req->bytes_read += len;
|
|
|
|
if (req->bytes_read > req->read_limit) {
|
|
req->body_status = APREQ_ERROR_OVERLIMIT;
|
|
cgi_log_error(CGILOG_MARK, CGILOG_ERR, req->body_status, handle,
|
|
"Bytes read (%" APR_UINT64_T_FMT
|
|
") exceeds configured limit (%" APR_UINT64_T_FMT ")",
|
|
req->bytes_read, req->read_limit);
|
|
|
|
break;
|
|
}
|
|
|
|
req->body_status =
|
|
apreq_parser_run(req->parser, req->body, req->tmpbb);
|
|
apr_brigade_cleanup(req->tmpbb);
|
|
break;
|
|
|
|
default:
|
|
req->body_status = s;
|
|
}
|
|
|
|
return req->body_status;
|
|
}
|
|
|
|
|
|
|
|
static apr_status_t cgi_jar(apreq_handle_t *handle,
|
|
const apr_table_t **t)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
|
|
if (req->interactive_mode && req->jar_status != APR_SUCCESS) {
|
|
char buf[65536];
|
|
const char *name, *val;
|
|
apreq_cookie_t *p;
|
|
int i = 1;
|
|
apr_file_printf(req->sout, "[CGI] Requested all cookies\n");
|
|
while (1) {
|
|
apr_file_printf(req->sout, "[CGI] Please enter a name for cookie %d (or just hit ENTER to end): ",
|
|
i++);
|
|
apr_file_gets(buf, 65536, req->sin);
|
|
chomp(buf);
|
|
if (!strcmp(buf, "")) {
|
|
break;
|
|
}
|
|
name = apr_pstrdup(handle->pool, buf);
|
|
val = prompt(handle, name, "cookie");
|
|
if (val == NULL)
|
|
val = "";
|
|
p = apreq_cookie_make(handle->pool, name, strlen(name), val, strlen(val));
|
|
if (p == NULL)
|
|
return APR_ENOMEM;
|
|
apreq_cookie_tainted_on(p);
|
|
apreq_value_table_add(&p->v, req->jar);
|
|
}
|
|
req->jar_status = APR_SUCCESS;
|
|
} /** Fallthrough */
|
|
|
|
if (req->jar_status == APR_EINIT) {
|
|
const char *cookies = cgi_header_in(handle, "Cookie");
|
|
if (cookies != NULL) {
|
|
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 cgi_args(apreq_handle_t *handle,
|
|
const apr_table_t **t)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
|
|
if (req->interactive_mode && req->args_status != APR_SUCCESS) {
|
|
char buf[65536];
|
|
const char *name, *val;
|
|
apreq_param_t *p;
|
|
int i = 1;
|
|
apr_file_printf(req->sout, "[CGI] Requested all argument parameters\n");
|
|
while (1) {
|
|
apr_file_printf(req->sout, "[CGI] Please enter a name for parameter %d (or just hit ENTER to end): ",
|
|
i++);
|
|
apr_file_gets(buf, 65536, req->sin);
|
|
chomp(buf);
|
|
if (!strcmp(buf, "")) {
|
|
break;
|
|
}
|
|
name = apr_pstrdup(handle->pool, buf);
|
|
val = prompt(handle, name, "parameter");
|
|
if (val == NULL)
|
|
val = "";
|
|
p = apreq_param_make(handle->pool, name, strlen(name), val, strlen(val));
|
|
if (p == NULL)
|
|
return APR_ENOMEM;
|
|
apreq_param_tainted_on(p);
|
|
apreq_value_table_add(&p->v, req->args);
|
|
val = p->v.data;
|
|
}
|
|
req->args_status = APR_SUCCESS;
|
|
} /** Fallthrough */
|
|
|
|
if (req->args_status == APR_EINIT) {
|
|
const char *qs = cgi_query_string(handle);
|
|
if (qs != NULL) {
|
|
req->args_status =
|
|
apreq_parse_query_string(handle->pool, req->args, qs);
|
|
}
|
|
else
|
|
req->args_status = APREQ_ERROR_NODATA;
|
|
}
|
|
|
|
*t = req->args;
|
|
return req->args_status;
|
|
}
|
|
|
|
|
|
|
|
|
|
static apreq_cookie_t *cgi_jar_get(apreq_handle_t *handle,
|
|
const char *name)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
const apr_table_t *t;
|
|
const char *val = NULL;
|
|
|
|
if (req->jar_status == APR_EINIT && !req->interactive_mode)
|
|
cgi_jar(handle, &t);
|
|
else
|
|
t = req->jar;
|
|
|
|
val = apr_table_get(t, name);
|
|
if (val == NULL) {
|
|
if (!req->interactive_mode) {
|
|
return NULL;
|
|
} else {
|
|
apreq_cookie_t *p;
|
|
val = prompt(handle, name, "cookie");
|
|
if (val == NULL)
|
|
return NULL;
|
|
p = apreq_cookie_make(handle->pool, name, strlen(name), val, strlen(val));
|
|
if (p == NULL)
|
|
return NULL;
|
|
apreq_cookie_tainted_on(p);
|
|
apreq_value_table_add(&p->v, req->jar);
|
|
val = p->v.data;
|
|
}
|
|
}
|
|
|
|
|
|
return apreq_value_to_cookie(val);
|
|
}
|
|
|
|
static apreq_param_t *cgi_args_get(apreq_handle_t *handle,
|
|
const char *name)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
const apr_table_t *t;
|
|
const char *val = NULL;
|
|
|
|
if (req->args_status == APR_EINIT && !req->interactive_mode)
|
|
cgi_args(handle, &t);
|
|
else
|
|
t = req->args;
|
|
|
|
val = apr_table_get(t, name);
|
|
if (val == NULL) {
|
|
if (!req->interactive_mode) {
|
|
return NULL;
|
|
} else {
|
|
apreq_param_t *p;
|
|
val = prompt(handle, name, "parameter");
|
|
if (val == NULL)
|
|
return NULL;
|
|
p = apreq_param_make(handle->pool, name, strlen(name), val, strlen(val));
|
|
if (p == NULL)
|
|
return NULL;
|
|
apreq_param_tainted_on(p);
|
|
apreq_value_table_add(&p->v, req->args);
|
|
val = p->v.data;
|
|
}
|
|
}
|
|
|
|
|
|
return apreq_value_to_param(val);
|
|
}
|
|
|
|
|
|
|
|
static apr_status_t cgi_body(apreq_handle_t *handle,
|
|
const apr_table_t **t)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
|
|
if (req->interactive_mode && req->body_status != APR_SUCCESS) {
|
|
const char *name, *val;
|
|
apreq_param_t *p;
|
|
int i = 1;
|
|
apr_file_printf(req->sout, "[CGI] Requested all body parameters\n");
|
|
while (1) {
|
|
char buf[65536];
|
|
apr_file_printf(req->sout, "[CGI] Please enter a name for parameter %d (or just hit ENTER to end): ",
|
|
i++);
|
|
apr_file_gets(buf, 65536, req->sin);
|
|
chomp(buf);
|
|
if (!strcmp(buf, "")) {
|
|
break;
|
|
}
|
|
name = apr_pstrdup(handle->pool, buf);
|
|
val = prompt(handle, name, "parameter");
|
|
if (val == NULL)
|
|
val = "";
|
|
p = apreq_param_make(handle->pool, name, strlen(name), val, strlen(val));
|
|
if (p == NULL)
|
|
return APR_ENOMEM;
|
|
apreq_param_tainted_on(p);
|
|
apreq_value_table_add(&p->v, req->body);
|
|
val = p->v.data;
|
|
}
|
|
req->body_status = APR_SUCCESS;
|
|
} /** Fallthrough */
|
|
|
|
switch (req->body_status) {
|
|
|
|
case APR_EINIT:
|
|
init_body(handle);
|
|
if (req->body_status != APR_INCOMPLETE)
|
|
break;
|
|
|
|
case APR_INCOMPLETE:
|
|
while (cgi_read(handle, APREQ_DEFAULT_READ_BLOCK_SIZE)
|
|
== APR_INCOMPLETE)
|
|
; /*loop*/
|
|
}
|
|
|
|
*t = req->body;
|
|
return req->body_status;
|
|
}
|
|
|
|
static apreq_param_t *cgi_body_get(apreq_handle_t *handle,
|
|
const char *name)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
const char *val = NULL;
|
|
apreq_hook_t *h;
|
|
apreq_hook_find_param_ctx_t *hook_ctx;
|
|
|
|
if (req->interactive_mode) {
|
|
val = apr_table_get(req->body, name);
|
|
if (val == NULL) {
|
|
return NULL;
|
|
} else {
|
|
apreq_param_t *p;
|
|
val = prompt(handle, name, "parameter");
|
|
if (val == NULL)
|
|
return NULL;
|
|
p = apreq_param_make(handle->pool, name, strlen(name), val, strlen(val));
|
|
if (p == NULL)
|
|
return NULL;
|
|
apreq_param_tainted_on(p);
|
|
apreq_value_table_add(&p->v, req->body);
|
|
val = p->v.data;
|
|
return apreq_value_to_param(val);
|
|
}
|
|
}
|
|
|
|
|
|
switch (req->body_status) {
|
|
|
|
case APR_SUCCESS:
|
|
|
|
val = apr_table_get(req->body, name);
|
|
if (val != NULL)
|
|
return apreq_value_to_param(val);
|
|
return NULL;
|
|
|
|
|
|
case APR_EINIT:
|
|
|
|
init_body(handle);
|
|
if (req->body_status != APR_INCOMPLETE)
|
|
return NULL;
|
|
cgi_read(handle, APREQ_DEFAULT_READ_BLOCK_SIZE);
|
|
|
|
|
|
case APR_INCOMPLETE:
|
|
|
|
val = apr_table_get(req->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 (req->find_param == NULL)
|
|
req->find_param = apreq_hook_make(handle->pool,
|
|
apreq_hook_find_param,
|
|
NULL, NULL);
|
|
h = req->find_param;
|
|
h->next = req->parser->hook;
|
|
req->parser->hook = h;
|
|
h->ctx = hook_ctx;
|
|
hook_ctx->name = name;
|
|
hook_ctx->param = NULL;
|
|
hook_ctx->prev = req->parser->hook;
|
|
|
|
do {
|
|
cgi_read(handle, APREQ_DEFAULT_READ_BLOCK_SIZE);
|
|
if (hook_ctx->param != NULL)
|
|
return hook_ctx->param;
|
|
} while (req->body_status == APR_INCOMPLETE);
|
|
|
|
req->parser->hook = h->next;
|
|
return NULL;
|
|
|
|
|
|
default:
|
|
|
|
if (req->body == NULL)
|
|
return NULL;
|
|
|
|
val = apr_table_get(req->body, name);
|
|
if (val != NULL)
|
|
return apreq_value_to_param(val);
|
|
return NULL;
|
|
}
|
|
|
|
/* not reached */
|
|
return NULL;
|
|
}
|
|
|
|
static apr_status_t cgi_parser_get(apreq_handle_t *handle,
|
|
const apreq_parser_t **parser)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
|
|
*parser = req->parser;
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
static apr_status_t cgi_parser_set(apreq_handle_t *handle,
|
|
apreq_parser_t *parser)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
|
|
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) {
|
|
parser->temp_dir = req->temp_dir;
|
|
}
|
|
if (req->brigade_limit < parser->brigade_limit) {
|
|
parser->brigade_limit = req->brigade_limit;
|
|
}
|
|
|
|
req->hook_queue = NULL;
|
|
req->parser = parser;
|
|
return APR_SUCCESS;
|
|
}
|
|
else
|
|
return APREQ_ERROR_MISMATCH;
|
|
}
|
|
|
|
|
|
static apr_status_t cgi_hook_add(apreq_handle_t *handle,
|
|
apreq_hook_t *hook)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
|
|
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 cgi_brigade_limit_set(apreq_handle_t *handle,
|
|
apr_size_t bytes)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
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 cgi_brigade_limit_get(apreq_handle_t *handle,
|
|
apr_size_t *bytes)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
*bytes = (req->parser == NULL)
|
|
? req->brigade_limit
|
|
: req->parser->brigade_limit;
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
static apr_status_t cgi_read_limit_set(apreq_handle_t *handle,
|
|
apr_uint64_t bytes)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
|
|
if (req->read_limit > bytes && req->bytes_read < bytes) {
|
|
req->read_limit = bytes;
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
return APREQ_ERROR_MISMATCH;
|
|
}
|
|
|
|
|
|
static apr_status_t cgi_read_limit_get(apreq_handle_t *handle,
|
|
apr_uint64_t *bytes)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
*bytes = req->read_limit;
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
|
|
static apr_status_t cgi_temp_dir_set(apreq_handle_t *handle,
|
|
const char *path)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
const char **temp_dir = (req->parser == NULL)
|
|
? &req->temp_dir
|
|
: &req->parser->temp_dir;
|
|
|
|
|
|
if (*temp_dir == NULL && req->bytes_read == 0) {
|
|
if (path != NULL)
|
|
*temp_dir = apr_pstrdup(handle->pool, path);
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
return APREQ_ERROR_MISMATCH;
|
|
}
|
|
|
|
|
|
static apr_status_t cgi_temp_dir_get(apreq_handle_t *handle,
|
|
const char **path)
|
|
{
|
|
struct cgi_handle *req = (struct cgi_handle *)handle;
|
|
*path = (req->parser == NULL)
|
|
? req->temp_dir
|
|
: req->parser->temp_dir;
|
|
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
|
|
|
|
/** Determine if we're interactive mode or not. Order is
|
|
QUERY_STRING ? NO : Interactive
|
|
|
|
I think we should just rely on GATEWAY_INTERFACE to set
|
|
non-interactive mode, and be interactive if it's not there
|
|
|
|
Behaviour change should really be:
|
|
Always check query_string before prompting user,
|
|
but rewrite body/cookies to get if interactive
|
|
|
|
Definitely more work needed here...
|
|
*/
|
|
static int is_interactive_mode(apr_pool_t *pool)
|
|
{
|
|
char *value = NULL, qs[] = "GATEWAY_INTERFACE";
|
|
apr_status_t rv;
|
|
|
|
rv = apr_env_get(&value, qs, pool);
|
|
if (rv != APR_SUCCESS)
|
|
if (rv == APR_ENOENT)
|
|
return 1;
|
|
|
|
/** handle else? (!SUCCESS && !ENOENT) */
|
|
return 0;
|
|
}
|
|
|
|
static APREQ_MODULE(cgi, 20090110);
|
|
|
|
APREQ_DECLARE(apreq_handle_t *)apreq_handle_cgi(apr_pool_t *pool)
|
|
{
|
|
apr_bucket_alloc_t *ba;
|
|
struct cgi_handle *req;
|
|
void *data;
|
|
|
|
apr_pool_userdata_get(&data, USER_DATA_KEY, pool);
|
|
|
|
if (data != NULL)
|
|
return data;
|
|
|
|
req = apr_pcalloc(pool, sizeof *req);
|
|
ba = apr_bucket_alloc_create(pool);
|
|
|
|
/* check pool's userdata first. */
|
|
|
|
req->handle.module = &cgi_module;
|
|
req->handle.pool = pool;
|
|
req->handle.bucket_alloc = ba;
|
|
req->read_limit = (apr_uint64_t) -1;
|
|
req->brigade_limit = APREQ_DEFAULT_BRIGADE_LIMIT;
|
|
|
|
req->args = apr_table_make(pool, APREQ_DEFAULT_NELTS);
|
|
req->body = apr_table_make(pool, APREQ_DEFAULT_NELTS);
|
|
req->jar = apr_table_make(pool, APREQ_DEFAULT_NELTS);
|
|
|
|
req->args_status =
|
|
req->jar_status =
|
|
req->body_status = APR_EINIT;
|
|
|
|
if (is_interactive_mode(pool)) {
|
|
req->interactive_mode = 1;
|
|
apr_file_open_stdout(&(req->sout), pool);
|
|
apr_file_open_stdin(&(req->sin), pool);
|
|
req->promptstr=apr_pstrdup(pool, DEFAULT_PROMPT);
|
|
}
|
|
|
|
apr_pool_userdata_setn(&req->handle, USER_DATA_KEY, NULL, pool);
|
|
|
|
#ifdef APR_POOL_DEBUG
|
|
apr_pool_cleanup_register(pool, ba, ba_cleanup, ba_cleanup);
|
|
#endif
|
|
|
|
return &req->handle;
|
|
}
|