2 ** Licensed to the Apache Software Foundation (ASF) under one or more
3 ** contributor license agreements. See the NOTICE file distributed with
4 ** this work for additional information regarding copyright ownership.
5 ** The ASF licenses this file to You under the Apache License, Version 2.0
6 ** (the "License"); you may not use this file except in compliance with
7 ** the License. You may obtain a copy of the License at
9 ** http://www.apache.org/licenses/LICENSE-2.0
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
18 #define APR_WANT_STRFUNC
20 #include "apreq_module.h"
21 #include "apreq_error.h"
22 #include "apr_strings.h"
25 #include "apreq_util.h"
27 #define USER_DATA_KEY "apreq"
29 /* Parroting APLOG_* ... */
31 #define CGILOG_EMERG 0 /* system is unusable */
32 #define CGILOG_ALERT 1 /* action must be taken immediately */
33 #define CGILOG_CRIT 2 /* critical conditions */
34 #define CGILOG_ERR 3 /* error conditions */
35 #define CGILOG_WARNING 4 /* warning conditions */
36 #define CGILOG_NOTICE 5 /* normal but significant condition */
37 #define CGILOG_INFO 6 /* informational */
38 #define CGILOG_DEBUG 7 /* debug-level messages */
40 #define CGILOG_LEVELMASK 7
41 #define CGILOG_MARK __FILE__, __LINE__
43 /** Interactive patch:
44 * TODO Don't use 65K buffer
45 * TODO Handle empty/non-existent parameters
46 * TODO Allow body elements to be files
47 * TODO When running body/get/cookies all at once, include previous cached
48 * values (and don't start at 0 in count)
49 * TODO What happens if user does apreq_param, but needs POST value - we'll
50 * never catch it now, as args param will match...
54 struct apreq_handle_t handle;
56 apr_table_t *jar, *args, *body;
57 apr_status_t jar_status,
61 apreq_parser_t *parser;
62 apreq_hook_t *hook_queue;
63 apreq_hook_t *find_param;
66 apr_size_t brigade_limit;
67 apr_uint64_t read_limit;
68 apr_uint64_t bytes_read;
70 apr_bucket_brigade *in;
71 apr_bucket_brigade *tmpbb;
74 const char *promptstr;
75 apr_file_t *sout, *sin;
78 #define CRLF "\015\012"
79 static const char *nullstr = 0;
80 #define DEFAULT_PROMPT "([$t] )$n(\\($l\\))([$d]): "
81 #define MAX_PROMPT_NESTING_LEVELS 8
82 #define MAX_BUFFER_SIZE 65536
89 static const TRANS priorities[] = {
90 {"emerg", CGILOG_EMERG},
91 {"alert", CGILOG_ALERT},
92 {"crit", CGILOG_CRIT},
93 {"error", CGILOG_ERR},
94 {"warn", CGILOG_WARNING},
95 {"notice", CGILOG_NOTICE},
96 {"info", CGILOG_INFO},
97 {"debug", CGILOG_DEBUG},
101 static char* chomp(char* str)
103 long p = (long)strlen(str);
105 switch ((char)(str[p])) {
107 case '\012':str[p]='\000';
115 /** TODO: Support wide-characters */
116 /* prompt takes a apreq_handle and 2 strings - name and type - and prompts a
117 user for input via stdin/stdout. used in interactive mode.
119 name must be defined. type can be null.
121 we take the promptstring defined in the handle and interpolate variables as
124 $n - name of the variable we're asking for (param 2 to prompt())
125 $t - type of the variable we're asking for - like cookie, get, post, etc
126 (param 3 to prompt())
127 parentheses - if a variable is surrounded by parentheses, and interpolates
128 as null, then nothing else in the parentheses will be displayed
129 Useful if you want a string to only show up if a given variable
132 These are planned for forward-compatibility, but the underlying features
133 need some love... I left these in here just as feature reminders, rather
134 than completely removing them from the code - at least they provide sanity
135 testing of the default prompt & parentheses - issac
137 $l - label for the param - the end-user-developer can provide a textual
138 description of the param (name) being requested (currently unused in
140 $d - default value for the param (currently unused in lib)
143 static char *prompt(apreq_handle_t *handle, const char *name,
145 struct cgi_handle *req = (struct cgi_handle *)handle;
146 const char *defval = nullstr;
147 const char *label = NULL;
149 char buf[MAX_PROMPT_NESTING_LEVELS][MAX_BUFFER_SIZE];
150 /* Array of current arg for given p-level */
151 char *start, curarg[MAX_PROMPT_NESTING_LEVELS] = "";
152 /* Parenthesis level (for argument/text grouping) */
155 cprompt = req->promptstr - 1;
156 *buf[0] = plevel = 0;
159 while (*(++cprompt) != 0) {
161 case '$': /* interpolate argument; curarg[plevel] => 1 */
167 start += strlen(type);
170 curarg[plevel] = curarg[plevel] | 0;
174 /* Name can't be null :-) [If it can, we should
175 * immediately return NULL] */
177 start += strlen(name);
182 strcpy(start, label);
183 start += strlen(label);
186 curarg[plevel] = curarg[plevel] | 0;
190 /* TODO: Once null defaults are available,
191 * remove if and use nullstr if defval == NULL */
192 if (defval != NULL) {
193 strcpy(start, defval);
194 start += strlen(defval);
197 curarg[plevel] = curarg[plevel] | 0;
207 if (plevel <= MAX_PROMPT_NESTING_LEVELS) {
209 curarg[plevel] = *buf[plevel] = 0;
217 *start = 0; /* Null terminate current string */
219 /* Move pointer to end of string */
221 start = buf[plevel] + strlen(buf[plevel]);
223 /* If old curarg was set, concat buffer with level down */
224 if (curarg[plevel + 1]) {
225 strcpy(start, buf[plevel + 1]);
226 start += strlen(buf[plevel + 1]);
231 case '\\': /* Check next character for escape sequence
232 * (just ignore it for now) */
241 *start = 0; /* Null terminate the string */
243 apr_file_printf(req->sout, "%s", buf[0]);
244 apr_file_gets(buf[0], MAX_BUFFER_SIZE, req->sin);
246 if (strcmp(buf[0], "")) {
247 /* if (strcmp(buf[0], nullstr)) */
248 return apr_pstrdup(handle->pool, buf[0]);
252 if (defval != nullstr)
253 return apr_pstrdup(handle->pool, defval);
258 static const char *cgi_header_in(apreq_handle_t *handle,
261 apr_pool_t *p = handle->pool;
262 char *key = apr_pstrcat(p, "HTTP_", name, NULL);
263 char *k, *value = NULL;
265 for (k = key+5; *k; ++k) {
269 *k = apr_toupper(*k);
272 if (!strcmp(key, "HTTP_CONTENT_TYPE")
273 || !strcmp(key, "HTTP_CONTENT_LENGTH")) {
275 key += 5; /* strlen("HTTP_") */
278 apr_env_get(&value, key, p);
284 static void cgi_log_error(const char *file, int line, int level,
285 apr_status_t status, apreq_handle_t *handle,
286 const char *fmt, ...)
288 apr_pool_t *p = handle->pool;
290 char *log_level_string, *ra;
291 const char *remote_addr;
292 unsigned log_level = CGILOG_WARNING;
293 char date[APR_CTIME_LEN];
301 if (apr_env_get(&log_level_string, "LOG_LEVEL", p) == APR_SUCCESS)
302 log_level = (log_level_string[0] - '0');
304 level &= CGILOG_LEVELMASK;
306 if (level < (int)log_level) {
308 if (apr_env_get(&ra, "REMOTE_ADDR", p) == APR_SUCCESS)
311 remote_addr = "address unavailable";
313 apr_ctime(date, apr_time_now());
317 apr_file_open_stderr(&err, p);
318 apr_file_printf(err, "[%s] [%s] [%s] %s(%d): %s: %s\n",
319 date, priorities[level].t_name, remote_addr, file, line,
320 apr_strerror(status,buf,255),apr_pvsprintf(p,fmt,vp));
324 fprintf(stderr, "[%s] [%s] [%s] %s(%d): %s: %s\n",
325 date, priorities[level].t_name, remote_addr, file, line,
326 apr_strerror(status,buf,255),apr_pvsprintf(p,fmt,vp));
336 static const char *cgi_query_string(apreq_handle_t *handle)
338 char *value = NULL, qs[] = "QUERY_STRING";
339 apr_env_get(&value, qs, handle->pool);
344 static void init_body(apreq_handle_t *handle)
346 struct cgi_handle *req = (struct cgi_handle *)handle;
347 const char *cl_header = cgi_header_in(handle, "Content-Length");
348 apr_bucket_alloc_t *ba = handle->bucket_alloc;
349 apr_pool_t *pool = handle->pool;
351 apr_bucket *eos, *pipe;
353 if (cl_header != NULL) {
355 apr_int64_t content_length = apr_strtoi64(cl_header, &dummy, 10);
357 if (dummy == NULL || *dummy != 0) {
358 req->body_status = APREQ_ERROR_BADHEADER;
359 cgi_log_error(CGILOG_MARK, CGILOG_ERR, req->body_status, handle,
360 "Invalid Content-Length header (%s)", cl_header);
363 else if ((apr_uint64_t)content_length > req->read_limit) {
364 req->body_status = APREQ_ERROR_OVERLIMIT;
365 cgi_log_error(CGILOG_MARK, CGILOG_ERR, req->body_status, handle,
366 "Content-Length header (%s) exceeds configured "
367 "max_body limit (%" APR_UINT64_T_FMT ")",
368 cl_header, req->read_limit);
373 if (req->parser == NULL) {
374 const char *ct_header = cgi_header_in(handle, "Content-Type");
376 if (ct_header != NULL) {
377 apreq_parser_function_t pf = apreq_parser(ct_header);
380 req->parser = apreq_parser_make(pool,
390 req->body_status = APREQ_ERROR_NOPARSER;
395 req->body_status = APREQ_ERROR_NOHEADER;
400 if (req->parser->brigade_limit > req->brigade_limit)
401 req->parser->brigade_limit = req->brigade_limit;
402 if (req->temp_dir != NULL)
403 req->parser->temp_dir = req->temp_dir;
404 if (req->hook_queue != NULL)
405 apreq_parser_add_hook(req->parser, req->hook_queue);
408 req->hook_queue = NULL;
409 req->in = apr_brigade_create(pool, ba);
410 req->tmpbb = apr_brigade_create(pool, ba);
412 apr_file_open_stdin(&file, pool); /* error status? */
413 pipe = apr_bucket_pipe_create(file, ba);
414 eos = apr_bucket_eos_create(ba);
415 APR_BRIGADE_INSERT_HEAD(req->in, pipe);
416 APR_BRIGADE_INSERT_TAIL(req->in, eos);
418 req->body_status = APR_INCOMPLETE;
422 static apr_status_t cgi_read(apreq_handle_t *handle,
425 struct cgi_handle *req = (struct cgi_handle *)handle;
429 if (req->body_status == APR_EINIT)
432 if (req->body_status != APR_INCOMPLETE)
433 return req->body_status;
436 switch (s = apr_brigade_partition(req->in, bytes, &e)) {
441 apreq_brigade_move(req->tmpbb, req->in, e);
442 req->bytes_read += bytes;
444 if (req->bytes_read > req->read_limit) {
445 req->body_status = APREQ_ERROR_OVERLIMIT;
446 cgi_log_error(CGILOG_MARK, CGILOG_ERR, req->body_status,
447 handle, "Bytes read (%" APR_UINT64_T_FMT
448 ") exceeds configured limit (%" APR_UINT64_T_FMT ")",
449 req->bytes_read, req->read_limit);
454 apreq_parser_run(req->parser, req->body, req->tmpbb);
455 apr_brigade_cleanup(req->tmpbb);
461 apreq_brigade_move(req->tmpbb, req->in, e);
462 s = apr_brigade_length(req->tmpbb, 1, &len);
464 if (s != APR_SUCCESS) {
465 req->body_status = s;
468 req->bytes_read += len;
470 if (req->bytes_read > req->read_limit) {
471 req->body_status = APREQ_ERROR_OVERLIMIT;
472 cgi_log_error(CGILOG_MARK, CGILOG_ERR, req->body_status, handle,
473 "Bytes read (%" APR_UINT64_T_FMT
474 ") exceeds configured limit (%" APR_UINT64_T_FMT ")",
475 req->bytes_read, req->read_limit);
481 apreq_parser_run(req->parser, req->body, req->tmpbb);
482 apr_brigade_cleanup(req->tmpbb);
486 req->body_status = s;
489 return req->body_status;
494 static apr_status_t cgi_jar(apreq_handle_t *handle,
495 const apr_table_t **t)
497 struct cgi_handle *req = (struct cgi_handle *)handle;
499 if (req->interactive_mode && req->jar_status != APR_SUCCESS) {
501 const char *name, *val;
504 apr_file_printf(req->sout, "[CGI] Requested all cookies\n");
506 apr_file_printf(req->sout, "[CGI] Please enter a name for cookie %d (or just hit ENTER to end): ",
508 apr_file_gets(buf, 65536, req->sin);
510 if (!strcmp(buf, "")) {
513 name = apr_pstrdup(handle->pool, buf);
514 val = prompt(handle, name, "cookie");
517 p = apreq_cookie_make(handle->pool, name, strlen(name), val, strlen(val));
518 apreq_cookie_tainted_on(p);
519 apreq_value_table_add(&p->v, req->jar);
521 req->jar_status = APR_SUCCESS;
524 if (req->jar_status == APR_EINIT) {
525 const char *cookies = cgi_header_in(handle, "Cookie");
526 if (cookies != NULL) {
528 apreq_parse_cookie_header(handle->pool, req->jar, cookies);
531 req->jar_status = APREQ_ERROR_NODATA;
535 return req->jar_status;
538 static apr_status_t cgi_args(apreq_handle_t *handle,
539 const apr_table_t **t)
541 struct cgi_handle *req = (struct cgi_handle *)handle;
543 if (req->interactive_mode && req->args_status != APR_SUCCESS) {
545 const char *name, *val;
548 apr_file_printf(req->sout, "[CGI] Requested all argument parameters\n");
550 apr_file_printf(req->sout, "[CGI] Please enter a name for parameter %d (or just hit ENTER to end): ",
552 apr_file_gets(buf, 65536, req->sin);
554 if (!strcmp(buf, "")) {
557 name = apr_pstrdup(handle->pool, buf);
558 val = prompt(handle, name, "parameter");
561 p = apreq_param_make(handle->pool, name, strlen(name), val, strlen(val));
562 apreq_param_tainted_on(p);
563 apreq_value_table_add(&p->v, req->args);
566 req->args_status = APR_SUCCESS;
569 if (req->args_status == APR_EINIT) {
570 const char *qs = cgi_query_string(handle);
573 apreq_parse_query_string(handle->pool, req->args, qs);
576 req->args_status = APREQ_ERROR_NODATA;
580 return req->args_status;
586 static apreq_cookie_t *cgi_jar_get(apreq_handle_t *handle,
589 struct cgi_handle *req = (struct cgi_handle *)handle;
590 const apr_table_t *t;
591 const char *val = NULL;
593 if (req->jar_status == APR_EINIT && !req->interactive_mode)
598 val = apr_table_get(t, name);
600 if (!req->interactive_mode) {
604 val = prompt(handle, name, "cookie");
607 p = apreq_cookie_make(handle->pool, name, strlen(name), val, strlen(val));
608 apreq_cookie_tainted_on(p);
609 apreq_value_table_add(&p->v, req->jar);
615 return apreq_value_to_cookie(val);
618 static apreq_param_t *cgi_args_get(apreq_handle_t *handle,
621 struct cgi_handle *req = (struct cgi_handle *)handle;
622 const apr_table_t *t;
623 const char *val = NULL;
625 if (req->args_status == APR_EINIT && !req->interactive_mode)
626 cgi_args(handle, &t);
630 val = apr_table_get(t, name);
632 if (!req->interactive_mode) {
636 val = prompt(handle, name, "parameter");
639 p = apreq_param_make(handle->pool, name, strlen(name), val, strlen(val));
640 apreq_param_tainted_on(p);
641 apreq_value_table_add(&p->v, req->args);
647 return apreq_value_to_param(val);
652 static apr_status_t cgi_body(apreq_handle_t *handle,
653 const apr_table_t **t)
655 struct cgi_handle *req = (struct cgi_handle *)handle;
657 if (req->interactive_mode && req->body_status != APR_SUCCESS) {
658 const char *name, *val;
661 apr_file_printf(req->sout, "[CGI] Requested all body parameters\n");
664 apr_file_printf(req->sout, "[CGI] Please enter a name for parameter %d (or just hit ENTER to end): ",
666 apr_file_gets(buf, 65536, req->sin);
668 if (!strcmp(buf, "")) {
671 name = apr_pstrdup(handle->pool, buf);
672 val = prompt(handle, name, "parameter");
675 p = apreq_param_make(handle->pool, name, strlen(name), val, strlen(val));
676 apreq_param_tainted_on(p);
677 apreq_value_table_add(&p->v, req->body);
680 req->body_status = APR_SUCCESS;
683 switch (req->body_status) {
687 if (req->body_status != APR_INCOMPLETE)
691 while (cgi_read(handle, APREQ_DEFAULT_READ_BLOCK_SIZE)
697 return req->body_status;
700 static apreq_param_t *cgi_body_get(apreq_handle_t *handle,
703 struct cgi_handle *req = (struct cgi_handle *)handle;
704 const char *val = NULL;
706 apreq_hook_find_param_ctx_t *hook_ctx;
708 if (req->interactive_mode) {
709 val = apr_table_get(req->body, name);
714 val = prompt(handle, name, "parameter");
717 p = apreq_param_make(handle->pool, name, strlen(name), val, strlen(val));
718 apreq_param_tainted_on(p);
719 apreq_value_table_add(&p->v, req->body);
721 return apreq_value_to_param(val);
726 switch (req->body_status) {
730 val = apr_table_get(req->body, name);
732 return apreq_value_to_param(val);
739 if (req->body_status != APR_INCOMPLETE)
741 cgi_read(handle, APREQ_DEFAULT_READ_BLOCK_SIZE);
746 val = apr_table_get(req->body, name);
748 return apreq_value_to_param(val);
750 /* Not seen yet, so we need to scan for
751 param while prefetching the body */
753 hook_ctx = apr_palloc(handle->pool, sizeof *hook_ctx);
755 if (req->find_param == NULL)
756 req->find_param = apreq_hook_make(handle->pool,
757 apreq_hook_find_param,
760 h->next = req->parser->hook;
761 req->parser->hook = h;
763 hook_ctx->name = name;
764 hook_ctx->param = NULL;
765 hook_ctx->prev = req->parser->hook;
768 cgi_read(handle, APREQ_DEFAULT_READ_BLOCK_SIZE);
769 if (hook_ctx->param != NULL)
770 return hook_ctx->param;
771 } while (req->body_status == APR_INCOMPLETE);
773 req->parser->hook = h->next;
779 if (req->body == NULL)
782 val = apr_table_get(req->body, name);
784 return apreq_value_to_param(val);
792 static apr_status_t cgi_parser_get(apreq_handle_t *handle,
793 const apreq_parser_t **parser)
795 struct cgi_handle *req = (struct cgi_handle *)handle;
797 *parser = req->parser;
801 static apr_status_t cgi_parser_set(apreq_handle_t *handle,
802 apreq_parser_t *parser)
804 struct cgi_handle *req = (struct cgi_handle *)handle;
806 if (req->parser == NULL) {
808 if (req->hook_queue != NULL) {
809 apr_status_t s = apreq_parser_add_hook(parser, req->hook_queue);
810 if (s != APR_SUCCESS)
813 if (req->temp_dir != NULL) {
814 parser->temp_dir = req->temp_dir;
816 if (req->brigade_limit < parser->brigade_limit) {
817 parser->brigade_limit = req->brigade_limit;
820 req->hook_queue = NULL;
821 req->parser = parser;
825 return APREQ_ERROR_MISMATCH;
829 static apr_status_t cgi_hook_add(apreq_handle_t *handle,
832 struct cgi_handle *req = (struct cgi_handle *)handle;
834 if (req->parser != NULL) {
835 return apreq_parser_add_hook(req->parser, hook);
837 else if (req->hook_queue != NULL) {
838 apreq_hook_t *h = req->hook_queue;
839 while (h->next != NULL)
844 req->hook_queue = hook;
850 static apr_status_t cgi_brigade_limit_set(apreq_handle_t *handle,
853 struct cgi_handle *req = (struct cgi_handle *)handle;
854 apr_size_t *limit = (req->parser == NULL)
855 ? &req->brigade_limit
856 : &req->parser->brigade_limit;
858 if (*limit > bytes) {
863 return APREQ_ERROR_MISMATCH;
866 static apr_status_t cgi_brigade_limit_get(apreq_handle_t *handle,
869 struct cgi_handle *req = (struct cgi_handle *)handle;
870 *bytes = (req->parser == NULL)
872 : req->parser->brigade_limit;
877 static apr_status_t cgi_read_limit_set(apreq_handle_t *handle,
880 struct cgi_handle *req = (struct cgi_handle *)handle;
882 if (req->read_limit > bytes && req->bytes_read < bytes) {
883 req->read_limit = bytes;
887 return APREQ_ERROR_MISMATCH;
891 static apr_status_t cgi_read_limit_get(apreq_handle_t *handle,
894 struct cgi_handle *req = (struct cgi_handle *)handle;
895 *bytes = req->read_limit;
900 static apr_status_t cgi_temp_dir_set(apreq_handle_t *handle,
903 struct cgi_handle *req = (struct cgi_handle *)handle;
904 const char **temp_dir = (req->parser == NULL)
906 : &req->parser->temp_dir;
909 if (*temp_dir == NULL && req->bytes_read == 0) {
911 *temp_dir = apr_pstrdup(handle->pool, path);
915 return APREQ_ERROR_MISMATCH;
919 static apr_status_t cgi_temp_dir_get(apreq_handle_t *handle,
922 struct cgi_handle *req = (struct cgi_handle *)handle;
923 *path = (req->parser == NULL)
925 : req->parser->temp_dir;
931 #ifdef APR_POOL_DEBUG
932 static apr_status_t ba_cleanup(void *data)
934 apr_bucket_alloc_t *ba = data;
935 apr_bucket_alloc_destroy(ba);
940 /** Determine if we're interactive mode or not. Order is
941 QUERY_STRING ? NO : Interactive
943 I think we should just rely on GATEWAY_INTERFACE to set
944 non-interactive mode, and be interactive if it's not there
946 Behaviour change should really be:
947 Always check query_string before prompting user,
948 but rewrite body/cookies to get if interactive
950 Definitely more work needed here...
952 static int is_interactive_mode(apr_pool_t *pool)
954 char *value = NULL, qs[] = "GATEWAY_INTERFACE";
957 rv = apr_env_get(&value, qs, pool);
958 if (rv != APR_SUCCESS)
959 if (rv == APR_ENOENT)
962 /** handle else? (!SUCCESS && !ENOENT) */
966 static APREQ_MODULE(cgi, 20090110);
968 APREQ_DECLARE(apreq_handle_t *)apreq_handle_cgi(apr_pool_t *pool)
970 apr_bucket_alloc_t *ba;
971 struct cgi_handle *req;
974 apr_pool_userdata_get(&data, USER_DATA_KEY, pool);
979 req = apr_pcalloc(pool, sizeof *req);
980 ba = apr_bucket_alloc_create(pool);
982 /* check pool's userdata first. */
984 req->handle.module = &cgi_module;
985 req->handle.pool = pool;
986 req->handle.bucket_alloc = ba;
987 req->read_limit = (apr_uint64_t) -1;
988 req->brigade_limit = APREQ_DEFAULT_BRIGADE_LIMIT;
990 req->args = apr_table_make(pool, APREQ_DEFAULT_NELTS);
991 req->body = apr_table_make(pool, APREQ_DEFAULT_NELTS);
992 req->jar = apr_table_make(pool, APREQ_DEFAULT_NELTS);
996 req->body_status = APR_EINIT;
998 if (is_interactive_mode(pool)) {
999 req->interactive_mode = 1;
1000 apr_file_open_stdout(&(req->sout), pool);
1001 apr_file_open_stdin(&(req->sin), pool);
1002 req->promptstr=apr_pstrdup(pool, DEFAULT_PROMPT);
1005 apr_pool_userdata_setn(&req->handle, USER_DATA_KEY, NULL, pool);
1007 #ifdef APR_POOL_DEBUG
1008 apr_pool_cleanup_register(pool, ba, ba_cleanup, ba_cleanup);
1011 return &req->handle;