]> granicus.if.org Git - apache/commitdiff
Add a function to the http filters that is able to parse an HTML
authorGraham Leggett <minfrin@apache.org>
Wed, 9 Apr 2008 11:39:58 +0000 (11:39 +0000)
committerGraham Leggett <minfrin@apache.org>
Wed, 9 Apr 2008 11:39:58 +0000 (11:39 +0000)
form request with the type of application/x-www-form-urlencoded.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@646281 13f79535-47bb-0310-9956-ffa450edef68

CHANGES
include/http_protocol.h
modules/http/http_filters.c

diff --git a/CHANGES b/CHANGES
index 3381175a0fbe699f2cc92891afd09b12a4f30d83..daf40246db13ecdfb1e2267f5f82b01555d97fd4 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,10 @@
 Changes with Apache 2.3.0
 [ When backported to 2.2.x, remove entry from this file ]
 
+  *) Add a function to the http filters that is able to parse an HTML
+     form request with the type of application/x-www-form-urlencoded.
+     [Graham Leggett]
+
   *) mod_session_crypto: Initialise SSL in the post config hook.
      [Ruediger Pluem, Graham Leggett]
 
index def77deab9b75c7590709f6871aae1e4507447a2..e85a3d1d0a8e7d81f64c9a0e87b96c15c8853fca 100644 (file)
@@ -669,7 +669,55 @@ AP_DECLARE(void) ap_finalize_sub_req_protocol(request_rec *sub_r);
  * @param send_headers Whether to send&clear headers in r->headers_out
  */
 AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers);
-                                                                                
+
+/**
+ * Structure to store the contents of an HTTP form of the type
+ * application/x-www-form-urlencoded.
+ * 
+ * Currently it contains the name as a char* of maximum length
+ * HUGE_STRING_LEN, and a value in the form of a bucket brigade
+ * of arbitrary length.
+ */
+typedef struct {
+    const char *name;
+    apr_bucket_brigade *value;
+} ap_form_pair_t;
+
+/**
+ * Read the body and parse any form found, which must be of the
+ * type application/x-www-form-urlencoded.
+ *
+ * Name/value pairs are returned in an array, with the names as
+ * strings with a maximum length of HUGE_STRING_LEN, and the
+ * values as bucket brigades. This allows values to be arbitrarily
+ * large.
+ *
+ * All url-encoding is removed from both the names and the values
+ * on the fly. The names are interpreted as strings, while the
+ * values are interpreted as blocks of binary data, that may
+ * contain the 0 character.
+ *
+ * In order to ensure that resource limits are not exceeded, a
+ * maximum size must be provided. If the sum of the lengths of
+ * the names and the values exceed this size, this function
+ * will return HTTP_REQUEST_ENTITY_TOO_LARGE.
+ *
+ * An optional number of parameters can be provided, if the number
+ * of parameters provided exceeds this amount, this function will
+ * return HTTP_REQUEST_ENTITY_TOO_LARGE. If this value is negative,
+ * no limit is imposed, and the number of parameters is in turn
+ * constrained by the size parameter above.
+ * 
+ * This function honours any kept_body configuration, and the
+ * original raw request body will be saved to the kept_body brigade
+ * if so configured, just as ap_discard_request_body does.
+ * 
+ * NOTE: File upload is not yet supported, but can be without change
+ * to the function call.
+ */
+AP_DECLARE(int) ap_parse_request_form(request_rec * r, apr_array_header_t ** ptr,
+                                      apr_size_t num, apr_size_t size);
+
 #ifdef __cplusplus
 }
 #endif
index cb77bb53d4f45108b5374d3b1a7505045069a01d..964eba26ba0796bc546b1e34d5ea999da05fd93e 100644 (file)
@@ -1724,3 +1724,240 @@ apr_status_t ap_kept_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
     return APR_SUCCESS;
 
 }
+
+/* form parsing stuff */
+typedef enum {
+    FORM_NORMAL,
+    FORM_AMP,
+    FORM_NAME,
+    FORM_VALUE,
+    FORM_PERCENTA,
+    FORM_PERCENTB,
+    FORM_ABORT
+} ap_form_type_t;
+
+/**
+ * Read the body and parse any form found, which must be of the
+ * type application/x-www-form-urlencoded.
+ *
+ * Name/value pairs are returned in an array, with the names as
+ * strings with a maximum length of HUGE_STRING_LEN, and the
+ * values as bucket brigades. This allows values to be arbitrarily
+ * large.
+ *
+ * All url-encoding is removed from both the names and the values
+ * on the fly. The names are interpreted as strings, while the
+ * values are interpreted as blocks of binary data, that may
+ * contain the 0 character.
+ *
+ * In order to ensure that resource limits are not exceeded, a
+ * maximum size must be provided. If the sum of the lengths of
+ * the names and the values exceed this size, this function
+ * will return HTTP_REQUEST_ENTITY_TOO_LARGE.
+ *
+ * An optional number of parameters can be provided, if the number
+ * of parameters provided exceeds this amount, this function will
+ * return HTTP_REQUEST_ENTITY_TOO_LARGE. If this value is negative,
+ * no limit is imposed, and the number of parameters is in turn
+ * constrained by the size parameter above.
+ * 
+ * This function honours any kept_body configuration, and the
+ * original raw request body will be saved to the kept_body brigade
+ * if so configured, just as ap_discard_request_body does.
+ * 
+ * NOTE: File upload is not yet supported, but can be without change
+ * to the function call.
+ */
+AP_DECLARE(int) ap_parse_request_form(request_rec * r, apr_array_header_t ** ptr,
+                                      apr_size_t num, apr_size_t size)
+{
+    core_dir_conf *dconf;
+    apr_off_t left = 0;
+    apr_bucket_brigade *bb = NULL, *kept_body = NULL;
+    apr_bucket *e;
+    int seen_eos = 0;
+    char buffer[HUGE_STRING_LEN + 1];
+    const char *ct;
+    apr_size_t offset = 0;
+    ap_form_type_t state = FORM_NAME, percent = FORM_NORMAL;
+    ap_form_pair_t *pair = NULL;
+    apr_array_header_t *pairs = apr_array_make(r->pool, 4, sizeof(ap_form_pair_t));
+
+    char hi = 0;
+    char low = 0;
+
+    *ptr = pairs;
+
+    /* sanity check - we only support forms for now */
+    ct = apr_table_get(r->headers_in, "Content-Type");
+    if (!ct || strcmp("application/x-www-form-urlencoded", ct)) {
+        return ap_discard_request_body(r);
+    }
+
+    dconf = ap_get_module_config(r->per_dir_config,
+                                     &http_module);
+    if (dconf->keep_body > 0) {
+        left = dconf->keep_body;
+        kept_body = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+    }
+
+    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+    do {
+        apr_bucket *bucket = NULL, *last = NULL;
+
+        int rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
+                                APR_BLOCK_READ, HUGE_STRING_LEN);
+        if (rv != APR_SUCCESS) {
+            apr_brigade_destroy(bb);
+            return (rv == AP_FILTER_ERROR) ? rv : HTTP_BAD_REQUEST;
+        }
+
+        for (bucket = APR_BRIGADE_FIRST(bb);
+             bucket != APR_BRIGADE_SENTINEL(bb);
+             last = bucket, bucket = APR_BUCKET_NEXT(bucket)) {
+            const char *data;
+            apr_size_t len, slide;
+
+            if (last) {
+                apr_bucket_delete(last);
+            }
+            if (APR_BUCKET_IS_EOS(bucket)) {
+                seen_eos = 1;
+                break;
+            }
+            if (bucket->length == 0) {
+                continue;
+            }
+
+            rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
+            if (rv != APR_SUCCESS) {
+                apr_brigade_destroy(bb);
+                return HTTP_BAD_REQUEST;
+            }
+
+            slide = len;
+            while (state != FORM_ABORT && slide-- > 0 && size >= 0 && num != 0) {
+                char c = *data++;
+                if ('+' == c) {
+                    c = ' ';
+                }
+                else if ('&' == c) {
+                    state = FORM_AMP;
+                }
+                if ('%' == c) {
+                    percent = FORM_PERCENTA;
+                    continue;
+                }
+                if (FORM_PERCENTA == percent) {
+                    if (c >= 'a') {
+                        hi = c - 'a' + 10;
+                    }
+                    else if (c >= 'A') {
+                        hi = c - 'A' + 10;
+                    }
+                    else if (c >= '0') {
+                        hi = c - '0';
+                    }
+                    hi = hi << 4;
+                    percent = FORM_PERCENTB;
+                    continue;
+                }
+                if (FORM_PERCENTB == percent) {
+                    if (c >= 'a') {
+                        low = c - 'a' + 10;
+                    }
+                    else if (c >= 'A') {
+                        low = c - 'A' + 10;
+                    }
+                    else if (c >= '0') {
+                        low = c - '0';
+                    }
+                    c = low ^ hi;
+                    percent = FORM_NORMAL;
+                }
+                switch (state) {
+                case FORM_AMP:
+                    if (pair) {
+                        const char *tmp = apr_pmemdup(r->pool, buffer, offset);
+                        apr_bucket *b = apr_bucket_pool_create(tmp, offset, r->pool, r->connection->bucket_alloc);
+                        APR_BRIGADE_INSERT_TAIL(pair->value, b);
+                    }
+                    state = FORM_NAME;
+                    pair = NULL;
+                    offset = 0;
+                    num--;
+                    break;
+                case FORM_NAME:
+                    if (offset < HUGE_STRING_LEN) {
+                        if ('=' == c) {
+                            buffer[offset] = 0;
+                            offset = 0;
+                            pair = (ap_form_pair_t *) apr_array_push(pairs);
+                            pair->name = apr_pstrdup(r->pool, buffer);
+                            pair->value = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+                            state = FORM_VALUE;
+                        }
+                        else {
+                            buffer[offset++] = c;
+                            size--;
+                        }
+                    }
+                    else {
+                        state = FORM_ABORT;
+                    }
+                    break;
+                case FORM_VALUE:
+                    if (offset >= HUGE_STRING_LEN) {
+                        const char *tmp = apr_pmemdup(r->pool, buffer, offset);
+                        apr_bucket *b = apr_bucket_pool_create(tmp, offset, r->pool, r->connection->bucket_alloc);
+                        APR_BRIGADE_INSERT_TAIL(pair->value, b);
+                        offset = 0;
+                    }
+                    buffer[offset++] = c;
+                    size--;
+                    break;
+                default:
+                    break;
+                }
+            }
+
+            /* If we have been asked to, keep the data up until the
+             * configured limit. If the limit is exceeded, we return an
+             * HTTP_REQUEST_ENTITY_TOO_LARGE response so the caller is
+             * clear the server couldn't handle their request.
+             */
+            if (kept_body) {
+                if (len <= left) {
+                    apr_bucket_copy(bucket, &e);
+                    APR_BRIGADE_INSERT_TAIL(kept_body, e);
+                    left -= len;
+                }
+                else {
+                    apr_brigade_destroy(bb);
+                    apr_brigade_destroy(kept_body);
+                    return HTTP_REQUEST_ENTITY_TOO_LARGE;
+                }
+            }
+
+        }
+
+        apr_brigade_cleanup(bb);
+    } while (!seen_eos);
+
+    if (FORM_ABORT == state || size < 0 || num == 0) {
+        return HTTP_REQUEST_ENTITY_TOO_LARGE;
+    }
+    else if (FORM_VALUE == state && pair && offset > 0) {
+        const char *tmp = apr_pmemdup(r->pool, buffer, offset);
+        apr_bucket *b = apr_bucket_pool_create(tmp, offset, r->pool, r->connection->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(pair->value, b);
+    }
+
+    if (kept_body) {
+        r->kept_body = kept_body;
+    }
+
+    return OK;
+
+}
+