Hi all,

SHORT VERSION:
Patch to enable CGI scripts running from a terminal (shell) to lazily request their query_string/body/cookie parameters interactively (rather than requiring extra environment variables to be set up, and entity bodies to be piped in).

LONG VERSION:

At my current workplace, we're trying to migrate a lot of old CGI
scripts to work with apreq (among other things).  Once upon a time, well
before apreq 2, we wrote a custom library to do QS/cookie/body
parsing.  When we wrote the library it was enhanced so that
administrative CGI scripts could be run at the command line, and would
automatically prompt the users for missing values.  I think this could
be a useful feature for apreq.  While the existing implementation we
have is a patch against the existing CGI module, my personal thought was
that it might better be made as a separate enhanced-cgi module (which
will still work in dual CGI/interactive fallback mode) - the idea being
that users (users referring to developers who use apreq, not end-users
using CGIs that depend on it) can decide whether they want this
functionality available in their CGI script or not.

Currently, we have a very minimal patch that provides the base
functionality of requesting the body/args/cookies being requested via
stdin/stdout, and storing the received values in the underlying
apr_table_t's (which apreq's CGI module currently use).  The logic
currently being used to detect whether to use interactive mode or not is
based on whether or not the QUERY_STRING environment variable exists
(regardless of whether it's value is set or not).

We also have some ideas for future plans, if people here think it sounds
good:

* Providing support for FILE fields
* Providing support for empty/non-existing parameters
* Providing enhanced support for parameters (will post about this on a
separate thread)
* More maintenance to the existing patch (like removing the 64K buffers
on the stack that we used for quick 'n' dirty reading from stdin)
* Smarter interactive-mode detection
* Smarter storage of data when dealing with mixed
apreq_body/jar/args_get and apreq_body/jar/args calls

Thoughts?

Issac


Index: module_cgi.c
===================================================================
--- module_cgi.c        (revision 451724)
+++ module_cgi.c        (working copy)
@@ -38,6 +38,15 @@
 #define CGILOG_LEVELMASK 7
 #define CGILOG_MARK     __FILE__, __LINE__
 
+/** Interactive patch:
+ * TODO Don't use 65K buffer
+ * TODO Handle empty/non-existant 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...
+ */
 
 
 
@@ -61,6 +70,8 @@
     apr_bucket_brigade          *in;
     apr_bucket_brigade          *tmpbb;
 
+    int interactive_mode;
+    apr_file_t *sout, *sin;
 };
 
 #define CRLF "\015\012"
@@ -82,6 +93,19 @@
     {NULL,      -1},
 };
 
+static char* chomp(char* str) {
+    apr_size_t p = strlen(str);
+    while (--p > 0) {
+        switch ((char)(str[p])) {
+        case '\015':
+        case '\012':str[p]='\000';
+                    break;
+        default:return str;
+        }
+        }
+    return str;
+}
+
 static const char *cgi_header_in(apreq_handle_t *handle,
                                  const char *name)
 {
@@ -178,8 +202,6 @@
     apr_file_t *file;
     apr_bucket *eos, *pipe;
 
-    req->body  = apr_table_make(pool, APREQ_DEFAULT_NELTS);
-
     if (cl_header != NULL) {
         char *dummy;
         apr_int64_t content_length = apr_strtoi64(cl_header, &dummy, 0);
@@ -326,10 +348,36 @@
 {
     struct cgi_handle *req = (struct cgi_handle *)handle;
 
+    if (req->interactive_mode && req->jar_status != APR_SUCCESS) {
+        char buf[65536];
+        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 END to end): ",
+                     i++);
+            apr_file_gets(buf, 65536, req->sin);
+            chomp(buf);
+            if (!strcmp(buf, "END")) {
+                break;
+            }
+            name = apr_pstrdup(handle->pool, buf);
+            apr_file_printf(req->sout, "[CGI] Please enter a value for cookie 
%s: ",
+                     name);
+            apr_file_gets(buf, 65536, req->sin);
+            chomp(buf);
+            p = apreq_cookie_make(handle->pool, name, strlen(name), buf, 
strlen(buf));
+            apreq_cookie_tainted_on(p);
+            apreq_value_table_add(&p->v, req->jar);
+            val = (char *)p->v.data;
+        }
+        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 = apr_table_make(handle->pool, APREQ_DEFAULT_NELTS);
             req->jar_status =
                 apreq_parse_cookie_header(handle->pool, req->jar, cookies);
         }
@@ -346,10 +394,36 @@
 {
     struct cgi_handle *req = (struct cgi_handle *)handle;
 
+    if (req->interactive_mode && req->args_status != APR_SUCCESS) {
+        char buf[65536];
+        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 END to end): ",
+                     i++);
+            apr_file_gets(buf, 65536, req->sin);
+            chomp(buf);
+            if (!strcmp(buf, "END")) {
+                break;
+            }
+            name = apr_pstrdup(handle->pool, buf);
+            apr_file_printf(req->sout, "[CGI] Please enter a value for 
parameter %s: ",
+                     name);
+            apr_file_gets(buf, 65536, req->sin);
+            chomp(buf);
+            p = apreq_param_make(handle->pool, name, strlen(name), buf, 
strlen(buf));
+            apreq_param_tainted_on(p);
+            apreq_value_table_add(&p->v, req->args);
+            val = (char *)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 = apr_table_make(handle->pool, APREQ_DEFAULT_NELTS);
             req->args_status =
                 apreq_parse_query_string(handle->pool, req->args, qs);
         }
@@ -369,19 +443,31 @@
 {
     struct cgi_handle *req = (struct cgi_handle *)handle;
     const apr_table_t *t;
-    const char *val;
+    char *val = NULL;
 
-    if (req->jar_status == APR_EINIT)
+    if (req->jar_status == APR_EINIT && !req->interactive_mode)
         cgi_jar(handle, &t);
     else
         t = req->jar;
 
-    if (t == NULL)
-        return NULL;
+    val = (char *)apr_table_get(t, name);
+    if (val == NULL) {
+        if (!req->interactive_mode) {
+            return NULL;
+        } else {
+            char buf[65536];
+            apreq_cookie_t *p;
+            apr_file_printf(req->sout, "[CGI] Please enter a value for cookie 
%s: ",
+                            name);
+            apr_file_gets(buf, 65536, req->sin);
+            chomp(buf);
+            p = apreq_cookie_make(handle->pool, name, strlen(name), buf, 
strlen(buf));
+            apreq_cookie_tainted_on(p);
+            apreq_value_table_add(&p->v, req->jar);
+            val = (char *)p->v.data;
+        }
+    }
 
-    val = apr_table_get(t, name);
-    if (val == NULL)
-        return NULL;
 
     return apreq_value_to_cookie(val);
 }
@@ -391,19 +477,31 @@
 {
     struct cgi_handle *req = (struct cgi_handle *)handle;
     const apr_table_t *t;
-    const char *val;
+    char *val = NULL;
 
-    if (req->args_status == APR_EINIT)
+    if (req->args_status == APR_EINIT && !req->interactive_mode)
         cgi_args(handle, &t);
     else
         t = req->args;
 
-    if (t == NULL)
-        return NULL;
+    val = (char *)apr_table_get(t, name);
+    if (val == NULL) {
+        if (!req->interactive_mode) {
+            return NULL;
+        } else {
+            char buf[65536];
+            apreq_param_t *p;
+            apr_file_printf(req->sout, "[CGI] Please enter a value for 
parameter %s: ",
+                            name);
+            apr_file_gets(buf, 65536, req->sin);
+            chomp(buf);
+            p = apreq_param_make(handle->pool, name, strlen(name), buf, 
strlen(buf));
+            apreq_param_tainted_on(p);
+            apreq_value_table_add(&p->v, req->args);
+            val = (char *)p->v.data;
+        }
+    }
 
-    val = apr_table_get(t, name);
-    if (val == NULL)
-        return NULL;
 
     return apreq_value_to_param(val);
 }
@@ -415,6 +513,33 @@
 {
     struct cgi_handle *req = (struct cgi_handle *)handle;
 
+    if (req->interactive_mode && req->body_status != APR_SUCCESS) {
+        char buf[65536];
+        char *name, *val;
+        apreq_param_t *p;
+        int i = 1;
+        apr_file_printf(req->sout, "[CGI] Requested all body parameters\n");
+        while (1) {
+            apr_file_printf(req->sout, "[CGI] Please enter a name for 
parameter %d (or END to end): ",
+                     i++);
+            apr_file_gets(buf, 65536, req->sin);
+            chomp(buf);
+            if (!strcmp(buf, "END")) {
+                break;
+            }
+            name = apr_pstrdup(handle->pool, buf);
+            apr_file_printf(req->sout, "[CGI] Please enter a value for 
parameter %s: ",
+                     name);
+            apr_file_gets(buf, 65536, req->sin);
+            chomp(buf);
+            p = apreq_param_make(handle->pool, name, strlen(name), buf, 
strlen(buf));
+            apreq_param_tainted_on(p);
+            apreq_value_table_add(&p->v, req->body);
+            val = (char *)p->v.data;
+        }
+        req->body_status = APR_SUCCESS;
+    } /** Fallthrough */
+    
     switch (req->body_status) {
 
     case APR_EINIT:
@@ -436,14 +561,34 @@
                                    const char *name)
 {
     struct cgi_handle *req = (struct cgi_handle *)handle;
-    const char *val;
+    char *val = NULL;
     apreq_hook_t *h;
 
+    if (req->interactive_mode) {
+        val = (char *)apr_table_get(req->body, name);
+        if (val == NULL) {
+            return NULL;
+        } else {
+            char buf[65536];
+            apreq_param_t *p;
+            apr_file_printf(req->sout, "[CGI] Please enter a value for 
parameter %s: ",
+                            name);
+            apr_file_gets(buf, 65536, req->sin);
+            chomp(buf);
+            p = apreq_param_make(handle->pool, name, strlen(name), buf, 
strlen(buf));
+            apreq_param_tainted_on(p);
+            apreq_value_table_add(&p->v, req->body);
+            val = (char *)p->v.data;
+                       return apreq_value_to_param(val);
+        }
+    }
+
+
     switch (req->body_status) {
 
     case APR_SUCCESS:
 
-        val = apr_table_get(req->body, name);
+        val = (char *)apr_table_get(req->body, name);
         if (val != NULL)
             return apreq_value_to_param(val);
         return NULL;
@@ -459,7 +604,7 @@
 
     case APR_INCOMPLETE:
 
-        val = apr_table_get(req->body, name);
+        val = (char *)apr_table_get(req->body, name);
         if (val != NULL)
             return apreq_value_to_param(val);
 
@@ -492,7 +637,7 @@
         if (req->body == NULL)
             return NULL;
 
-        val = apr_table_get(req->body, name);
+        val = (char *)apr_table_get(req->body, name);
         if (val != NULL)
             return apreq_value_to_param(val);
         return NULL;
@@ -650,6 +795,31 @@
 }
 #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
+
+ Definately more work needed here...
+*/
+static int is_interactive_mode(apr_pool_t *pool) {
+    char *value = NULL, qs[] = "QUERY_STRING";
+    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, 20050425);
 
 APREQ_DECLARE(apreq_handle_t *)apreq_handle_cgi(apr_pool_t *pool)
@@ -674,10 +844,20 @@
     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);
+    }
+
     apr_pool_userdata_setn(&req->handle, USER_DATA_KEY, NULL, pool);
 
 #ifdef APR_POOL_DEBUG

Reply via email to