| diff --git a/include/http_protocol.h b/include/http_protocol.h |
| index 415270b..67fa02f 100644 |
| |
| |
| @@ -502,6 +502,23 @@ AP_DECLARE(int) ap_should_client_block(request_rec *r); |
| */ |
| AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer, apr_size_t bufsiz); |
| |
| +/* |
| + * Map specific APR codes returned by the filter stack to HTTP error |
| + * codes, or the default status code provided. Use it as follows: |
| + * |
| + * return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); |
| + * |
| + * If the filter has already handled the error, AP_FILTER_ERROR will |
| + * be returned, which is cleanly passed through. |
| + * |
| + * These mappings imply that the filter stack is reading from the |
| + * downstream client, the proxy will map these codes differently. |
| + * @param rv APR status code |
| + * @param status Default HTTP code should the APR code not be recognised |
| + * @return Mapped HTTP status code |
| + */ |
| +AP_DECLARE(int) ap_map_http_request_error(apr_status_t rv, int status); |
| + |
| /** |
| * In HTTP/1.1, any method can have a body. However, most GET handlers |
| * wouldn't know what to do with a request body if they received one. |
| diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c |
| index 1dde402..ed8749f 100644 |
| |
| |
| @@ -57,24 +57,29 @@ |
| |
| APLOG_USE_MODULE(http); |
| |
| -#define INVALID_CHAR -2 |
| - |
| -static long get_chunk_size(char *); |
| - |
| -typedef struct http_filter_ctx { |
| +typedef struct http_filter_ctx |
| +{ |
| apr_off_t remaining; |
| apr_off_t limit; |
| apr_off_t limit_used; |
| - enum { |
| - BODY_NONE, |
| - BODY_LENGTH, |
| - BODY_CHUNK, |
| - BODY_CHUNK_PART |
| + apr_int32_t chunk_used; |
| + apr_int32_t chunk_bws; |
| + apr_int32_t chunkbits; |
| + enum |
| + { |
| + BODY_NONE, /* streamed data */ |
| + BODY_LENGTH, /* data constrained by content length */ |
| + BODY_CHUNK, /* chunk expected */ |
| + BODY_CHUNK_PART, /* chunk digits */ |
| + BODY_CHUNK_EXT, /* chunk extension */ |
| + BODY_CHUNK_CR, /* got space(s) after digits, expect [CR]LF or ext */ |
| + BODY_CHUNK_LF, /* got CR after digits or ext, expect LF */ |
| + BODY_CHUNK_DATA, /* data constrained by chunked encoding */ |
| + BODY_CHUNK_END, /* chunked data terminating CRLF */ |
| + BODY_CHUNK_END_LF, /* got CR after data, expect LF */ |
| + BODY_CHUNK_TRAILER /* trailers */ |
| } state; |
| - int eos_sent; |
| - char chunk_ln[32]; |
| - char *pos; |
| - apr_off_t linesize; |
| + unsigned int eos_sent :1; |
| apr_bucket_brigade *bb; |
| } http_ctx_t; |
| |
| @@ -87,6 +92,23 @@ static apr_status_t bail_out_on_error(http_ctx_t *ctx, |
| apr_bucket_brigade *bb = ctx->bb; |
| |
| apr_brigade_cleanup(bb); |
| + |
| + if (f->r->proxyreq == PROXYREQ_RESPONSE) { |
| + switch (http_error) { |
| + case HTTP_REQUEST_ENTITY_TOO_LARGE: |
| + return APR_ENOSPC; |
| + |
| + case HTTP_REQUEST_TIME_OUT: |
| + return APR_INCOMPLETE; |
| + |
| + case HTTP_NOT_IMPLEMENTED: |
| + return APR_ENOTIMPL; |
| + |
| + default: |
| + return APR_EGENERAL; |
| + } |
| + } |
| + |
| e = ap_bucket_error_create(http_error, |
| NULL, f->r->pool, |
| f->c->bucket_alloc); |
| @@ -102,117 +124,154 @@ static apr_status_t bail_out_on_error(http_ctx_t *ctx, |
| return ap_pass_brigade(f->r->output_filters, bb); |
| } |
| |
| -static apr_status_t get_remaining_chunk_line(http_ctx_t *ctx, |
| - apr_bucket_brigade *b, |
| - int linelimit) |
| +/** |
| + * Parse a chunk line with optional extension, detect overflow. |
| + * There are two error cases: |
| + * 1) If the conversion would require too many bits, APR_EGENERAL is returned. |
| + * 2) If the conversion used the correct number of bits, but an overflow |
| + * caused only the sign bit to flip, then APR_ENOSPC is returned. |
| + * In general, any negative number can be considered an overflow error. |
| + */ |
| +static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, |
| + apr_size_t len, int linelimit) |
| { |
| - apr_status_t rv; |
| - apr_off_t brigade_length; |
| - apr_bucket *e; |
| - const char *lineend; |
| - apr_size_t len = 0; |
| + apr_size_t i = 0; |
| |
| - /* |
| - * As the brigade b should have been requested in mode AP_MODE_GETLINE |
| - * all buckets in this brigade are already some type of memory |
| - * buckets (due to the needed scanning for LF in mode AP_MODE_GETLINE) |
| - * or META buckets. |
| - */ |
| - rv = apr_brigade_length(b, 0, &brigade_length); |
| - if (rv != APR_SUCCESS) { |
| - return rv; |
| - } |
| - /* Sanity check. Should never happen. See above. */ |
| - if (brigade_length == -1) { |
| - return APR_EGENERAL; |
| - } |
| - if (!brigade_length) { |
| - return APR_EAGAIN; |
| - } |
| - ctx->linesize += brigade_length; |
| - if (ctx->linesize > linelimit) { |
| - return APR_ENOSPC; |
| - } |
| - /* |
| - * As all buckets are already some type of memory buckets or META buckets |
| - * (see above), we only need to check the last byte in the last data bucket. |
| - */ |
| - for (e = APR_BRIGADE_LAST(b); |
| - e != APR_BRIGADE_SENTINEL(b); |
| - e = APR_BUCKET_PREV(e)) { |
| + while (i < len) { |
| + char c = buffer[i]; |
| + |
| + ap_xlate_proto_from_ascii(&c, 1); |
| |
| - if (APR_BUCKET_IS_METADATA(e)) { |
| + /* handle CRLF after the chunk */ |
| + if (ctx->state == BODY_CHUNK_END |
| + || ctx->state == BODY_CHUNK_END_LF) { |
| + if (c == LF) { |
| + ctx->state = BODY_CHUNK; |
| + } |
| + else if (c == CR && ctx->state == BODY_CHUNK_END) { |
| + ctx->state = BODY_CHUNK_END_LF; |
| + } |
| + else { |
| + /* |
| + * LF expected. |
| + */ |
| + return APR_EINVAL; |
| + } |
| + i++; |
| continue; |
| } |
| - rv = apr_bucket_read(e, &lineend, &len, APR_BLOCK_READ); |
| - if (rv != APR_SUCCESS) { |
| - return rv; |
| + |
| + /* handle start of the chunk */ |
| + if (ctx->state == BODY_CHUNK) { |
| + if (!apr_isxdigit(c)) { |
| + /* |
| + * Detect invalid character at beginning. This also works for |
| + * empty chunk size lines. |
| + */ |
| + return APR_EINVAL; |
| + } |
| + else { |
| + ctx->state = BODY_CHUNK_PART; |
| + } |
| + ctx->remaining = 0; |
| + ctx->chunkbits = sizeof(apr_off_t) * 8; |
| + ctx->chunk_used = 0; |
| + ctx->chunk_bws = 0; |
| } |
| - if (len > 0) { |
| - break; /* we got the data we want */ |
| + |
| + if (c == LF) { |
| + if (ctx->remaining) { |
| + ctx->state = BODY_CHUNK_DATA; |
| + } |
| + else { |
| + ctx->state = BODY_CHUNK_TRAILER; |
| + } |
| } |
| - /* If we got a zero-length data bucket, we try the next one */ |
| - } |
| - /* We had no data in this brigade */ |
| - if (!len || e == APR_BRIGADE_SENTINEL(b)) { |
| - return APR_EAGAIN; |
| - } |
| - if (lineend[len - 1] != APR_ASCII_LF) { |
| - return APR_EAGAIN; |
| - } |
| - /* Line is complete. So reset ctx for next round. */ |
| - ctx->linesize = 0; |
| - ctx->pos = ctx->chunk_ln; |
| - return APR_SUCCESS; |
| -} |
| + else if (ctx->state == BODY_CHUNK_LF) { |
| + /* |
| + * LF expected. |
| + */ |
| + return APR_EINVAL; |
| + } |
| + else if (c == CR) { |
| + ctx->state = BODY_CHUNK_LF; |
| + } |
| + else if (c == ';') { |
| + ctx->state = BODY_CHUNK_EXT; |
| + } |
| + else if (ctx->state == BODY_CHUNK_EXT) { |
| + /* |
| + * Control chars (but tabs) are invalid. |
| + */ |
| + if (c != '\t' && apr_iscntrl(c)) { |
| + return APR_EINVAL; |
| + } |
| + } |
| + else if (c == ' ' || c == '\t') { |
| + /* Be lenient up to 10 BWS (term from rfc7230 - 3.2.3). |
| + */ |
| + ctx->state = BODY_CHUNK_CR; |
| + if (++ctx->chunk_bws > 10) { |
| + return APR_EINVAL; |
| + } |
| + } |
| + else if (ctx->state == BODY_CHUNK_CR) { |
| + /* |
| + * ';', CR or LF expected. |
| + */ |
| + return APR_EINVAL; |
| + } |
| + else if (ctx->state == BODY_CHUNK_PART) { |
| + int xvalue; |
| |
| -static apr_status_t get_chunk_line(http_ctx_t *ctx, apr_bucket_brigade *b, |
| - int linelimit) |
| -{ |
| - apr_size_t len; |
| - int tmp_len; |
| - apr_status_t rv; |
| + /* ignore leading zeros */ |
| + if (!ctx->remaining && c == '0') { |
| + i++; |
| + continue; |
| + } |
| |
| - tmp_len = sizeof(ctx->chunk_ln) - (ctx->pos - ctx->chunk_ln) - 1; |
| - /* Saveguard ourselves against underflows */ |
| - if (tmp_len < 0) { |
| - len = 0; |
| - } |
| - else { |
| - len = (apr_size_t) tmp_len; |
| - } |
| - /* |
| - * Check if there is space left in ctx->chunk_ln. If not, then either |
| - * the chunk size is insane or we have chunk-extensions. Ignore both |
| - * by discarding the remaining part of the line via |
| - * get_remaining_chunk_line. Only bail out if the line is too long. |
| - */ |
| - if (len > 0) { |
| - rv = apr_brigade_flatten(b, ctx->pos, &len); |
| - if (rv != APR_SUCCESS) { |
| - return rv; |
| + ctx->chunkbits -= 4; |
| + if (ctx->chunkbits < 0) { |
| + /* overflow */ |
| + return APR_ENOSPC; |
| + } |
| + |
| + if (c >= '0' && c <= '9') { |
| + xvalue = c - '0'; |
| + } |
| + else if (c >= 'A' && c <= 'F') { |
| + xvalue = c - 'A' + 0xa; |
| + } |
| + else if (c >= 'a' && c <= 'f') { |
| + xvalue = c - 'a' + 0xa; |
| + } |
| + else { |
| + /* bogus character */ |
| + return APR_EINVAL; |
| + } |
| + |
| + ctx->remaining = (ctx->remaining << 4) | xvalue; |
| + if (ctx->remaining < 0) { |
| + /* overflow */ |
| + return APR_ENOSPC; |
| + } |
| } |
| - ctx->pos += len; |
| - ctx->linesize += len; |
| - *(ctx->pos) = '\0'; |
| - /* |
| - * Check if we really got a full line. If yes the |
| - * last char in the just read buffer must be LF. |
| - * If not advance the buffer and return APR_EAGAIN. |
| - * We do not start processing until we have the |
| - * full line. |
| - */ |
| - if (ctx->pos[-1] != APR_ASCII_LF) { |
| - /* Check if the remaining data in the brigade has the LF */ |
| - return get_remaining_chunk_line(ctx, b, linelimit); |
| + else { |
| + /* Should not happen */ |
| + return APR_EGENERAL; |
| } |
| - /* Line is complete. So reset ctx->pos for next round. */ |
| - ctx->pos = ctx->chunk_ln; |
| - return APR_SUCCESS; |
| + |
| + i++; |
| } |
| - return get_remaining_chunk_line(ctx, b, linelimit); |
| -} |
| |
| + /* sanity check */ |
| + ctx->chunk_used += len; |
| + if (ctx->chunk_used < 0 || ctx->chunk_used > linelimit) { |
| + return APR_ENOSPC; |
| + } |
| + |
| + return APR_SUCCESS; |
| +} |
| |
| static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f, |
| apr_bucket_brigade *b, int merge) |
| @@ -226,7 +285,6 @@ static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f, |
| r->status = HTTP_OK; |
| r->headers_in = r->trailers_in; |
| apr_table_clear(r->headers_in); |
| - ctx->state = BODY_NONE; |
| ap_get_mime_headers(r); |
| |
| if(r->status == HTTP_OK) { |
| @@ -239,7 +297,7 @@ static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f, |
| else { |
| const char *error_notes = apr_table_get(r->notes, |
| "error-notes"); |
| - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, |
| + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02656) |
| "Error while reading HTTP trailer: %i%s%s", |
| r->status, error_notes ? ": " : "", |
| error_notes ? error_notes : ""); |
| @@ -270,9 +328,9 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, |
| apr_bucket *e; |
| http_ctx_t *ctx = f->ctx; |
| apr_status_t rv; |
| - apr_off_t totalread; |
| int http_error = HTTP_REQUEST_ENTITY_TOO_LARGE; |
| apr_bucket_brigade *bb; |
| + int again; |
| |
| conf = (core_server_config *) |
| ap_get_module_config(f->r->server->module_config, &core_module); |
| @@ -286,7 +344,6 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, |
| const char *tenc, *lenp; |
| f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); |
| ctx->state = BODY_NONE; |
| - ctx->pos = ctx->chunk_ln; |
| ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); |
| bb = ctx->bb; |
| |
| @@ -306,25 +363,33 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, |
| lenp = apr_table_get(f->r->headers_in, "Content-Length"); |
| |
| if (tenc) { |
| - if (!strcasecmp(tenc, "chunked")) { |
| + if (strcasecmp(tenc, "chunked") == 0 /* fast path */ |
| + || ap_find_last_token(f->r->pool, tenc, "chunked")) { |
| ctx->state = BODY_CHUNK; |
| } |
| - /* test lenp, because it gives another case we can handle */ |
| - else if (!lenp) { |
| - /* Something that isn't in HTTP, unless some future |
| + else if (f->r->proxyreq == PROXYREQ_RESPONSE) { |
| + /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23 |
| + * Section 3.3.3.3: "If a Transfer-Encoding header field is |
| + * present in a response and the chunked transfer coding is not |
| + * the final encoding, the message body length is determined by |
| + * reading the connection until it is closed by the server." |
| + */ |
| + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(02555) |
| + "Unknown Transfer-Encoding: %s; " |
| + "using read-until-close", tenc); |
| + tenc = NULL; |
| + } |
| + else { |
| + /* Something that isn't a HTTP request, unless some future |
| * edition defines new transfer encodings, is unsupported. |
| */ |
| ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01585) |
| "Unknown Transfer-Encoding: %s", tenc); |
| - return bail_out_on_error(ctx, f, HTTP_NOT_IMPLEMENTED); |
| - } |
| - else { |
| - ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r, APLOGNO(01586) |
| - "Unknown Transfer-Encoding: %s; using Content-Length", tenc); |
| - tenc = NULL; |
| + return bail_out_on_error(ctx, f, HTTP_BAD_REQUEST); |
| } |
| + lenp = NULL; |
| } |
| - if (lenp && !tenc) { |
| + if (lenp) { |
| char *endstr; |
| |
| ctx->state = BODY_LENGTH; |
| @@ -339,7 +404,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, |
| ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01587) |
| "Invalid Content-Length"); |
| |
| - return bail_out_on_error(ctx, f, HTTP_REQUEST_ENTITY_TOO_LARGE); |
| + return bail_out_on_error(ctx, f, HTTP_BAD_REQUEST); |
| } |
| |
| /* If we have a limit in effect and we know the C-L ahead of |
| @@ -381,7 +446,8 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, |
| if (!ap_is_HTTP_SUCCESS(f->r->status)) { |
| ctx->state = BODY_NONE; |
| ctx->eos_sent = 1; |
| - } else { |
| + } |
| + else { |
| char *tmp; |
| int len; |
| |
| @@ -389,7 +455,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, |
| * in a state of expecting one. |
| */ |
| f->r->expecting_100 = 0; |
| - tmp = apr_pstrcat(f->r->pool, AP_SERVER_PROTOCOL, " ", |
| + tmp = apr_pstrcat(f->r->pool, AP_SERVER_PROTOCOL " ", |
| ap_get_status_line(HTTP_CONTINUE), CRLF CRLF, |
| NULL); |
| len = strlen(tmp); |
| @@ -401,279 +467,205 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, |
| e = apr_bucket_flush_create(f->c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, e); |
| |
| - ap_pass_brigade(f->c->output_filters, bb); |
| - } |
| - } |
| - |
| - /* We can't read the chunk until after sending 100 if required. */ |
| - if (ctx->state == BODY_CHUNK) { |
| - apr_brigade_cleanup(bb); |
| - |
| - rv = ap_get_brigade(f->next, bb, AP_MODE_GETLINE, |
| - block, 0); |
| - |
| - /* for timeout */ |
| - if (block == APR_NONBLOCK_READ && |
| - ( (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)) || |
| - (APR_STATUS_IS_EAGAIN(rv)) )) { |
| - ctx->state = BODY_CHUNK_PART; |
| - return APR_EAGAIN; |
| - } |
| - |
| - if (rv == APR_SUCCESS) { |
| - rv = get_chunk_line(ctx, bb, f->r->server->limit_req_line); |
| - if (APR_STATUS_IS_EAGAIN(rv)) { |
| - apr_brigade_cleanup(bb); |
| - ctx->state = BODY_CHUNK_PART; |
| - return rv; |
| - } |
| - if (rv == APR_SUCCESS) { |
| - ctx->remaining = get_chunk_size(ctx->chunk_ln); |
| - if (ctx->remaining == INVALID_CHAR) { |
| - rv = APR_EGENERAL; |
| - http_error = HTTP_BAD_REQUEST; |
| - } |
| + rv = ap_pass_brigade(f->c->output_filters, bb); |
| + if (rv != APR_SUCCESS) { |
| + return AP_FILTER_ERROR; |
| } |
| } |
| - apr_brigade_cleanup(bb); |
| - |
| - /* Detect chunksize error (such as overflow) */ |
| - if (rv != APR_SUCCESS || ctx->remaining < 0) { |
| - ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, APLOGNO(01589) "Error reading first chunk %s ", |
| - (ctx->remaining < 0) ? "(overflow)" : ""); |
| - ctx->remaining = 0; /* Reset it in case we have to |
| - * come back here later */ |
| - if (APR_STATUS_IS_TIMEUP(rv)) { |
| - http_error = HTTP_REQUEST_TIME_OUT; |
| - } |
| - return bail_out_on_error(ctx, f, http_error); |
| - } |
| - |
| - if (!ctx->remaining) { |
| - return read_chunked_trailers(ctx, f, b, |
| - conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE); |
| - } |
| } |
| } |
| - else { |
| - bb = ctx->bb; |
| - } |
| |
| + /* sanity check in case we're read twice */ |
| if (ctx->eos_sent) { |
| e = apr_bucket_eos_create(f->c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(b, e); |
| return APR_SUCCESS; |
| } |
| |
| - if (!ctx->remaining) { |
| + do { |
| + apr_brigade_cleanup(b); |
| + again = 0; /* until further notice */ |
| + |
| + /* read and handle the brigade */ |
| switch (ctx->state) { |
| - case BODY_NONE: |
| - break; |
| - case BODY_LENGTH: |
| - e = apr_bucket_eos_create(f->c->bucket_alloc); |
| - APR_BRIGADE_INSERT_TAIL(b, e); |
| - ctx->eos_sent = 1; |
| - return APR_SUCCESS; |
| case BODY_CHUNK: |
| case BODY_CHUNK_PART: |
| - { |
| - apr_brigade_cleanup(bb); |
| + case BODY_CHUNK_EXT: |
| + case BODY_CHUNK_CR: |
| + case BODY_CHUNK_LF: |
| + case BODY_CHUNK_END: |
| + case BODY_CHUNK_END_LF: { |
| |
| - /* We need to read the CRLF after the chunk. */ |
| - if (ctx->state == BODY_CHUNK) { |
| - rv = ap_get_brigade(f->next, bb, AP_MODE_GETLINE, |
| - block, 0); |
| - if (block == APR_NONBLOCK_READ && |
| - ( (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)) || |
| - (APR_STATUS_IS_EAGAIN(rv)) )) { |
| - return APR_EAGAIN; |
| - } |
| - /* If we get an error, then leave */ |
| - if (rv != APR_SUCCESS) { |
| - return rv; |
| - } |
| - /* |
| - * We really don't care whats on this line. If it is RFC |
| - * compliant it should be only \r\n. If there is more |
| - * before we just ignore it as long as we do not get over |
| - * the limit for request lines. |
| - */ |
| - rv = get_remaining_chunk_line(ctx, bb, |
| - f->r->server->limit_req_line); |
| - apr_brigade_cleanup(bb); |
| - if (APR_STATUS_IS_EAGAIN(rv)) { |
| - return rv; |
| - } |
| - } else { |
| - rv = APR_SUCCESS; |
| - } |
| + rv = ap_get_brigade(f->next, b, AP_MODE_GETLINE, block, 0); |
| + |
| + /* for timeout */ |
| + if (block == APR_NONBLOCK_READ |
| + && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b)) |
| + || (APR_STATUS_IS_EAGAIN(rv)))) { |
| + return APR_EAGAIN; |
| + } |
| + |
| + if (rv == APR_EOF) { |
| + return APR_INCOMPLETE; |
| + } |
| + |
| + if (rv != APR_SUCCESS) { |
| + return rv; |
| + } |
| + |
| + e = APR_BRIGADE_FIRST(b); |
| + while (e != APR_BRIGADE_SENTINEL(b)) { |
| + const char *buffer; |
| + apr_size_t len; |
| + |
| + if (!APR_BUCKET_IS_METADATA(e)) { |
| + int parsing = 0; |
| + |
| + rv = apr_bucket_read(e, &buffer, &len, APR_BLOCK_READ); |
| |
| - if (rv == APR_SUCCESS) { |
| - /* Read the real chunk line. */ |
| - rv = ap_get_brigade(f->next, bb, AP_MODE_GETLINE, |
| - block, 0); |
| - /* Test timeout */ |
| - if (block == APR_NONBLOCK_READ && |
| - ( (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)) || |
| - (APR_STATUS_IS_EAGAIN(rv)) )) { |
| - ctx->state = BODY_CHUNK_PART; |
| - return APR_EAGAIN; |
| - } |
| - ctx->state = BODY_CHUNK; |
| if (rv == APR_SUCCESS) { |
| - rv = get_chunk_line(ctx, bb, f->r->server->limit_req_line); |
| - if (APR_STATUS_IS_EAGAIN(rv)) { |
| - ctx->state = BODY_CHUNK_PART; |
| - apr_brigade_cleanup(bb); |
| - return rv; |
| - } |
| - if (rv == APR_SUCCESS) { |
| - ctx->remaining = get_chunk_size(ctx->chunk_ln); |
| - if (ctx->remaining == INVALID_CHAR) { |
| - rv = APR_EGENERAL; |
| + parsing = 1; |
| + rv = parse_chunk_size(ctx, buffer, len, |
| + f->r->server->limit_req_fieldsize); |
| + } |
| + if (rv != APR_SUCCESS) { |
| + ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, APLOGNO(01590) |
| + "Error reading/parsing chunk %s ", |
| + (APR_ENOSPC == rv) ? "(overflow)" : ""); |
| + if (parsing) { |
| + if (rv != APR_ENOSPC) { |
| http_error = HTTP_BAD_REQUEST; |
| } |
| + return bail_out_on_error(ctx, f, http_error); |
| } |
| + return rv; |
| } |
| - apr_brigade_cleanup(bb); |
| } |
| |
| - /* Detect chunksize error (such as overflow) */ |
| - if (rv != APR_SUCCESS || ctx->remaining < 0) { |
| - ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, APLOGNO(01590) "Error reading chunk %s ", |
| - (ctx->remaining < 0) ? "(overflow)" : ""); |
| - ctx->remaining = 0; /* Reset it in case we have to |
| - * come back here later */ |
| - if (APR_STATUS_IS_TIMEUP(rv)) { |
| - http_error = HTTP_REQUEST_TIME_OUT; |
| - } |
| - return bail_out_on_error(ctx, f, http_error); |
| - } |
| + apr_bucket_delete(e); |
| + e = APR_BRIGADE_FIRST(b); |
| + } |
| + again = 1; /* come around again */ |
| |
| - if (!ctx->remaining) { |
| - return read_chunked_trailers(ctx, f, b, |
| + if (ctx->state == BODY_CHUNK_TRAILER) { |
| + /* Treat UNSET as DISABLE - trailers aren't merged by default */ |
| + return read_chunked_trailers(ctx, f, b, |
| conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE); |
| - } |
| } |
| + |
| break; |
| } |
| - } |
| + case BODY_NONE: |
| + case BODY_LENGTH: |
| + case BODY_CHUNK_DATA: { |
| |
| - /* Ensure that the caller can not go over our boundary point. */ |
| - if (ctx->state == BODY_LENGTH || ctx->state == BODY_CHUNK) { |
| - if (ctx->remaining < readbytes) { |
| - readbytes = ctx->remaining; |
| - } |
| - AP_DEBUG_ASSERT(readbytes > 0); |
| - } |
| + /* Ensure that the caller can not go over our boundary point. */ |
| + if (ctx->state != BODY_NONE && ctx->remaining < readbytes) { |
| + readbytes = ctx->remaining; |
| + } |
| + if (readbytes > 0) { |
| + apr_off_t totalread; |
| |
| - rv = ap_get_brigade(f->next, b, mode, block, readbytes); |
| + rv = ap_get_brigade(f->next, b, mode, block, readbytes); |
| |
| - if (rv != APR_SUCCESS) { |
| - return rv; |
| - } |
| + /* for timeout */ |
| + if (block == APR_NONBLOCK_READ |
| + && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b)) |
| + || (APR_STATUS_IS_EAGAIN(rv)))) { |
| + return APR_EAGAIN; |
| + } |
| |
| - /* How many bytes did we just read? */ |
| - apr_brigade_length(b, 0, &totalread); |
| + if (rv == APR_EOF && ctx->state != BODY_NONE |
| + && ctx->remaining > 0) { |
| + return APR_INCOMPLETE; |
| + } |
| |
| - /* If this happens, we have a bucket of unknown length. Die because |
| - * it means our assumptions have changed. */ |
| - AP_DEBUG_ASSERT(totalread >= 0); |
| + if (rv != APR_SUCCESS) { |
| + return rv; |
| + } |
| |
| - if (ctx->state != BODY_NONE) { |
| - ctx->remaining -= totalread; |
| - if (ctx->remaining > 0) { |
| - e = APR_BRIGADE_LAST(b); |
| - if (APR_BUCKET_IS_EOS(e)) |
| - return APR_EOF; |
| - } |
| - } |
| + /* How many bytes did we just read? */ |
| + apr_brigade_length(b, 0, &totalread); |
| |
| - /* If we have no more bytes remaining on a C-L request, |
| - * save the callter a roundtrip to discover EOS. |
| - */ |
| - if (ctx->state == BODY_LENGTH && ctx->remaining == 0) { |
| - e = apr_bucket_eos_create(f->c->bucket_alloc); |
| - APR_BRIGADE_INSERT_TAIL(b, e); |
| - } |
| + /* If this happens, we have a bucket of unknown length. Die because |
| + * it means our assumptions have changed. */ |
| + AP_DEBUG_ASSERT(totalread >= 0); |
| |
| - /* We have a limit in effect. */ |
| - if (ctx->limit) { |
| - /* FIXME: Note that we might get slightly confused on chunked inputs |
| - * as we'd need to compensate for the chunk lengths which may not |
| - * really count. This seems to be up for interpretation. */ |
| - ctx->limit_used += totalread; |
| - if (ctx->limit < ctx->limit_used) { |
| - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01591) |
| - "Read content-length of %" APR_OFF_T_FMT |
| - " is larger than the configured limit" |
| - " of %" APR_OFF_T_FMT, ctx->limit_used, ctx->limit); |
| - apr_brigade_cleanup(bb); |
| - e = ap_bucket_error_create(HTTP_REQUEST_ENTITY_TOO_LARGE, NULL, |
| - f->r->pool, |
| - f->c->bucket_alloc); |
| - APR_BRIGADE_INSERT_TAIL(bb, e); |
| - e = apr_bucket_eos_create(f->c->bucket_alloc); |
| - APR_BRIGADE_INSERT_TAIL(bb, e); |
| - ctx->eos_sent = 1; |
| - return ap_pass_brigade(f->r->output_filters, bb); |
| - } |
| - } |
| + if (ctx->state != BODY_NONE) { |
| + ctx->remaining -= totalread; |
| + if (ctx->remaining > 0) { |
| + e = APR_BRIGADE_LAST(b); |
| + if (APR_BUCKET_IS_EOS(e)) { |
| + apr_bucket_delete(e); |
| + return APR_INCOMPLETE; |
| + } |
| + } |
| + else if (ctx->state == BODY_CHUNK_DATA) { |
| + /* next chunk please */ |
| + ctx->state = BODY_CHUNK_END; |
| + ctx->chunk_used = 0; |
| + } |
| + } |
| |
| - return APR_SUCCESS; |
| -} |
| + /* We have a limit in effect. */ |
| + if (ctx->limit) { |
| + /* FIXME: Note that we might get slightly confused on |
| + * chunked inputs as we'd need to compensate for the chunk |
| + * lengths which may not really count. This seems to be up |
| + * for interpretation. |
| + */ |
| + ctx->limit_used += totalread; |
| + if (ctx->limit < ctx->limit_used) { |
| + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, |
| + APLOGNO(01591) "Read content length of " |
| + "%" APR_OFF_T_FMT " is larger than the " |
| + "configured limit of %" APR_OFF_T_FMT, |
| + ctx->limit_used, ctx->limit); |
| + return bail_out_on_error(ctx, f, |
| + HTTP_REQUEST_ENTITY_TOO_LARGE); |
| + } |
| + } |
| + } |
| |
| -/** |
| - * Parse a chunk extension, detect overflow. |
| - * There are two error cases: |
| - * 1) If the conversion would require too many bits, a -1 is returned. |
| - * 2) If the conversion used the correct number of bits, but an overflow |
| - * caused only the sign bit to flip, then that negative number is |
| - * returned. |
| - * In general, any negative number can be considered an overflow error. |
| - */ |
| -static long get_chunk_size(char *b) |
| -{ |
| - long chunksize = 0; |
| - size_t chunkbits = sizeof(long) * 8; |
| + /* If we have no more bytes remaining on a C-L request, |
| + * save the caller a round trip to discover EOS. |
| + */ |
| + if (ctx->state == BODY_LENGTH && ctx->remaining == 0) { |
| + e = apr_bucket_eos_create(f->c->bucket_alloc); |
| + APR_BRIGADE_INSERT_TAIL(b, e); |
| + ctx->eos_sent = 1; |
| + } |
| |
| - ap_xlate_proto_from_ascii(b, strlen(b)); |
| + break; |
| + } |
| + case BODY_CHUNK_TRAILER: { |
| |
| - if (!apr_isxdigit(*b)) { |
| - /* |
| - * Detect invalid character at beginning. This also works for empty |
| - * chunk size lines. |
| - */ |
| - return INVALID_CHAR; |
| - } |
| - /* Skip leading zeros */ |
| - while (*b == '0') { |
| - ++b; |
| - } |
| + rv = ap_get_brigade(f->next, b, mode, block, readbytes); |
| |
| - while (apr_isxdigit(*b) && (chunkbits > 0)) { |
| - int xvalue = 0; |
| + /* for timeout */ |
| + if (block == APR_NONBLOCK_READ |
| + && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b)) |
| + || (APR_STATUS_IS_EAGAIN(rv)))) { |
| + return APR_EAGAIN; |
| + } |
| + |
| + if (rv != APR_SUCCESS) { |
| + return rv; |
| + } |
| |
| - if (*b >= '0' && *b <= '9') { |
| - xvalue = *b - '0'; |
| + break; |
| } |
| - else if (*b >= 'A' && *b <= 'F') { |
| - xvalue = *b - 'A' + 0xa; |
| + default: { |
| + /* Should not happen */ |
| + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(02901) |
| + "Unexpected body state (%i)", (int)ctx->state); |
| + return APR_EGENERAL; |
| } |
| - else if (*b >= 'a' && *b <= 'f') { |
| - xvalue = *b - 'a' + 0xa; |
| } |
| |
| - chunksize = (chunksize << 4) | xvalue; |
| - chunkbits -= 4; |
| - ++b; |
| - } |
| - if (apr_isxdigit(*b)) { |
| - /* overflow */ |
| - return -1; |
| - } |
| + } while (again); |
| |
| - return chunksize; |
| + return APR_SUCCESS; |
| } |
| |
| typedef struct header_struct { |
| @@ -1385,6 +1377,39 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, |
| return ap_pass_brigade(f->next, b); |
| } |
| |
| +/* |
| + * Map specific APR codes returned by the filter stack to HTTP error |
| + * codes, or the default status code provided. Use it as follows: |
| + * |
| + * return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); |
| + * |
| + * If the filter has already handled the error, AP_FILTER_ERROR will |
| + * be returned, which is cleanly passed through. |
| + * |
| + * These mappings imply that the filter stack is reading from the |
| + * downstream client, the proxy will map these codes differently. |
| + */ |
| +AP_DECLARE(int) ap_map_http_request_error(apr_status_t rv, int status) |
| +{ |
| + switch (rv) { |
| + case AP_FILTER_ERROR: { |
| + return AP_FILTER_ERROR; |
| + } |
| + case APR_ENOSPC: { |
| + return HTTP_REQUEST_ENTITY_TOO_LARGE; |
| + } |
| + case APR_ENOTIMPL: { |
| + return HTTP_NOT_IMPLEMENTED; |
| + } |
| + case APR_ETIMEDOUT: { |
| + return HTTP_REQUEST_TIME_OUT; |
| + } |
| + default: { |
| + return status; |
| + } |
| + } |
| +} |
| + |
| /* In HTTP/1.1, any method can have a body. However, most GET handlers |
| * wouldn't know what to do with a request body if they received one. |
| * This helper routine tests for and reads any message body in the request, |
| @@ -1402,7 +1427,8 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, |
| AP_DECLARE(int) ap_discard_request_body(request_rec *r) |
| { |
| apr_bucket_brigade *bb; |
| - int rv, seen_eos; |
| + int seen_eos; |
| + apr_status_t rv; |
| |
| /* Sometimes we'll get in a state where the input handling has |
| * detected an error where we want to drop the connection, so if |
| @@ -1425,21 +1451,8 @@ AP_DECLARE(int) ap_discard_request_body(request_rec *r) |
| APR_BLOCK_READ, HUGE_STRING_LEN); |
| |
| if (rv != APR_SUCCESS) { |
| - /* FIXME: If we ever have a mapping from filters (apr_status_t) |
| - * to HTTP error codes, this would be a good place for them. |
| - * |
| - * If we received the special case AP_FILTER_ERROR, it means |
| - * that the filters have already handled this error. |
| - * Otherwise, we should assume we have a bad request. |
| - */ |
| - if (rv == AP_FILTER_ERROR) { |
| - apr_brigade_destroy(bb); |
| - return rv; |
| - } |
| - else { |
| - apr_brigade_destroy(bb); |
| - return HTTP_BAD_REQUEST; |
| - } |
| + apr_brigade_destroy(bb); |
| + return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); |
| } |
| |
| for (bucket = APR_BRIGADE_FIRST(bb); |
| @@ -1608,6 +1621,13 @@ AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer, |
| /* We lose the failure code here. This is why ap_get_client_block should |
| * not be used. |
| */ |
| + if (rv == AP_FILTER_ERROR) { |
| + /* AP_FILTER_ERROR means a filter has responded already, |
| + * we are DONE. |
| + */ |
| + apr_brigade_destroy(bb); |
| + return -1; |
| + } |
| if (rv != APR_SUCCESS) { |
| /* if we actually fail here, we want to just return and |
| * stop trying to read data from the client. |