| /* |
| ** 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; |
| } |