From: Graham Leggett Date: Wed, 9 Apr 2008 11:39:58 +0000 (+0000) Subject: Add a function to the http filters that is able to parse an HTML X-Git-Tag: 2.3.0~747 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=d5030b2190b9f7a2a291398095d19a46945a7723;p=apache 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. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@646281 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 3381175a0f..daf40246db 100644 --- 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] diff --git a/include/http_protocol.h b/include/http_protocol.h index def77deab9..e85a3d1d0a 100644 --- a/include/http_protocol.h +++ b/include/http_protocol.h @@ -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 diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c index cb77bb53d4..964eba26ba 100644 --- a/modules/http/http_filters.c +++ b/modules/http/http_filters.c @@ -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; + +} +