diff options
-rw-r--r-- | CHANGES | 3 | ||||
-rw-r--r-- | include/ap_mmn.h | 3 | ||||
-rw-r--r-- | include/httpd.h | 27 | ||||
-rw-r--r-- | include/mod_request.h | 50 | ||||
-rw-r--r-- | modules/aaa/mod_auth_form.c | 10 | ||||
-rw-r--r-- | modules/filters/mod_request.c | 216 | ||||
-rw-r--r-- | server/util.c | 222 |
7 files changed, 254 insertions, 277 deletions
@@ -2,6 +2,9 @@ Changes with Apache 2.3.11 + *) core: new util function: ap_parse_form_data(). Previously, + this capability was tucked away in mod_request. [Jim Jagielski] + *) core: new hook: ap_run_pre_read_request. [Jim Jagielski] *) mod_cache: When a request other than GET or HEAD arrives, we must diff --git a/include/ap_mmn.h b/include/ap_mmn.h index de64156afe..edaac77444 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -300,7 +300,8 @@ * 20110117.1 (2.3.11-dev) Add ap_pstr2_alnum() and ap_str2_alnum() * 20110203.0 (2.3.11-dev) Raise DYNAMIC_MODULE_LIMIT to 256 * 20110203.1 (2.3.11-dev) Add ap_state_query() - * 20110203.2 (2.3.11-dev) Add ap_run_pre_read_request() hook + * 20110203.2 (2.3.11-dev) Add ap_run_pre_read_request() hook and + * ap_parse_form_data() util */ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */ diff --git a/include/httpd.h b/include/httpd.h index 36faa74578..b2357b5918 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -1853,6 +1853,33 @@ AP_DECLARE(apr_status_t) ap_pstr2_alnum(apr_pool_t *p, const char *src, */ AP_DECLARE(apr_status_t) ap_str2_alnum(const char *src, char *dest); +/** + * 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. + * @param r request containing POSTed form data + * @param f filter + * @param ptr returned array of ap_form_pair_t + * @param num max num of params or -1 for unlimited + * @param size max size allowed for parsed data + * @return OK or HTTP error + */ +AP_DECLARE(int) ap_parse_form_data(request_rec *r, struct ap_filter_t *f, + apr_array_header_t **ptr, + apr_size_t num, apr_size_t size); + /* Misc system hackery */ /** * Given the name of an object in the file system determine if it is a directory diff --git a/include/mod_request.h b/include/mod_request.h index b65c4dcfef..ed28c05d20 100644 --- a/include/mod_request.h +++ b/include/mod_request.h @@ -52,56 +52,6 @@ typedef struct { int keep_body_set; } request_dir_conf; -/** - * 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. - */ - -APR_DECLARE_OPTIONAL_FN(int, ap_parse_request_form, (request_rec * r, ap_filter_t * f, - apr_array_header_t ** ptr, - apr_size_t num, apr_size_t size)); - APR_DECLARE_OPTIONAL_FN(void, ap_request_insert_filter, (request_rec * r)); APR_DECLARE_OPTIONAL_FN(void, ap_request_remove_filter, (request_rec * r)); diff --git a/modules/aaa/mod_auth_form.c b/modules/aaa/mod_auth_form.c index 05ac6c26cb..41e1f2fe36 100644 --- a/modules/aaa/mod_auth_form.c +++ b/modules/aaa/mod_auth_form.c @@ -46,9 +46,6 @@ static void (*ap_session_get_fn) (request_rec * r, session_rec * z, const char *key, const char **value) = NULL; static void (*ap_session_set_fn) (request_rec * r, session_rec * z, const char *key, const char *value) = NULL; -static int (*ap_parse_request_form_fn) (request_rec * r, ap_filter_t *f, - apr_array_header_t ** ptr, - apr_size_t num, apr_size_t size) = NULL; static void (*ap_request_insert_filter_fn) (request_rec * r) = NULL; static void (*ap_request_remove_filter_fn) (request_rec * r) = NULL; @@ -187,11 +184,10 @@ static const char *add_authn_provider(cmd_parms * cmd, void *config, } } - if (!ap_parse_request_form_fn || !ap_request_insert_filter_fn || !ap_request_remove_filter_fn) { - ap_parse_request_form_fn = APR_RETRIEVE_OPTIONAL_FN(ap_parse_request_form); + if (!ap_request_insert_filter_fn || !ap_request_remove_filter_fn) { ap_request_insert_filter_fn = APR_RETRIEVE_OPTIONAL_FN(ap_request_insert_filter); ap_request_remove_filter_fn = APR_RETRIEVE_OPTIONAL_FN(ap_request_remove_filter); - if (!ap_parse_request_form_fn || !ap_request_insert_filter_fn || !ap_request_remove_filter_fn) { + if (!ap_request_insert_filter_fn || !ap_request_remove_filter_fn) { return "You must load mod_request to enable the mod_auth_form " "functions"; } @@ -607,7 +603,7 @@ static int get_form_auth(request_rec * r, return OK; } - res = ap_parse_request_form_fn(r, NULL, &pairs, -1, conf->form_size); + res = ap_parse_form_data(r, NULL, &pairs, -1, conf->form_size); if (res != OK) { return res; } diff --git a/modules/filters/mod_request.c b/modules/filters/mod_request.c index 3693db8d25..cebe539a19 100644 --- a/modules/filters/mod_request.c +++ b/modules/filters/mod_request.c @@ -267,221 +267,6 @@ static apr_status_t kept_body_filter(ap_filter_t *f, apr_bucket_brigade *b, } -/* 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. - */ -static int ap_parse_request_form(request_rec * r, ap_filter_t * f, - apr_array_header_t ** ptr, - apr_size_t num, apr_size_t usize) -{ - apr_bucket_brigade *bb = NULL; - int seen_eos = 0; - char buffer[HUGE_STRING_LEN + 1]; - const char *ct; - apr_size_t offset = 0; - apr_ssize_t size; - 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); - } - - if (usize > APR_SIZE_MAX >> 1) - size = APR_SIZE_MAX >> 1; - else - size = usize; - - if (!f) { - f = r->input_filters; - } - - bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); - do { - apr_bucket *bucket = NULL, *last = NULL; - - int rv = ap_get_brigade(f, 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; - } - } - - } - - 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); - } - - return OK; - -} - /** * Check whether this filter is not already present. */ @@ -596,7 +381,6 @@ static void register_hooks(apr_pool_t *p) ap_register_input_filter(KEPT_BODY_FILTER, kept_body_filter, kept_body_filter_init, AP_FTYPE_RESOURCE); ap_hook_insert_filter(ap_request_insert_filter, NULL, NULL, APR_HOOK_LAST); - APR_REGISTER_OPTIONAL_FN(ap_parse_request_form); APR_REGISTER_OPTIONAL_FN(ap_request_insert_filter); APR_REGISTER_OPTIONAL_FN(ap_request_remove_filter); } diff --git a/server/util.c b/server/util.c index b61aa8eb80..c443c942a3 100644 --- a/server/util.c +++ b/server/util.c @@ -2147,7 +2147,7 @@ AP_DECLARE(int) ap_request_has_body(request_rec *r) char *estr; const char *cls; int has_body; - + has_body = (!r->header_only && (r->kept_body || apr_table_get(r->headers_in, "Transfer-Encoding") @@ -2168,7 +2168,7 @@ AP_DECLARE_NONSTD(apr_status_t) ap_pool_cleanup_set_null(void *data_) } AP_DECLARE(apr_status_t) ap_str2_alnum(const char *src, char *dest) { - + for ( ; *src; src++, dest++) { if (!apr_isprint(*src)) @@ -2180,7 +2180,7 @@ AP_DECLARE(apr_status_t) ap_str2_alnum(const char *src, char *dest) { } *dest = '\0'; return APR_SUCCESS; - + } AP_DECLARE(apr_status_t) ap_pstr2_alnum(apr_pool_t *p, const char *src, @@ -2192,3 +2192,219 @@ AP_DECLARE(apr_status_t) ap_pstr2_alnum(apr_pool_t *p, const char *src, *dest = new; return ap_str2_alnum(src, new); } + +/** + * 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. + */ + +/* form parsing stuff */ +typedef enum { + FORM_NORMAL, + FORM_AMP, + FORM_NAME, + FORM_VALUE, + FORM_PERCENTA, + FORM_PERCENTB, + FORM_ABORT +} ap_form_type_t; + +AP_DECLARE(int) ap_parse_form_data(request_rec *r, ap_filter_t *f, + apr_array_header_t **ptr, + apr_size_t num, apr_size_t usize) +{ + apr_bucket_brigade *bb = NULL; + int seen_eos = 0; + char buffer[HUGE_STRING_LEN + 1]; + const char *ct; + apr_size_t offset = 0; + apr_ssize_t size; + 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); + } + + if (usize > APR_SIZE_MAX >> 1) + size = APR_SIZE_MAX >> 1; + else + size = usize; + + if (!f) { + f = r->input_filters; + } + + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + do { + apr_bucket *bucket = NULL, *last = NULL; + + int rv = ap_get_brigade(f, 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; + } + } + + } + + 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); + } + + return OK; + +} |