From owner-svn-ports-head@freebsd.org Mon Jul 20 16:37:51 2015 Return-Path: Delivered-To: svn-ports-head@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id A62509A6B07; Mon, 20 Jul 2015 16:37:51 +0000 (UTC) (envelope-from feld@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 9629A15C7; Mon, 20 Jul 2015 16:37:51 +0000 (UTC) (envelope-from feld@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.70]) by repo.freebsd.org (8.14.9/8.14.9) with ESMTP id t6KGbpgc048759; Mon, 20 Jul 2015 16:37:51 GMT (envelope-from feld@FreeBSD.org) Received: (from feld@localhost) by repo.freebsd.org (8.14.9/8.14.9/Submit) id t6KGbph1048757; Mon, 20 Jul 2015 16:37:51 GMT (envelope-from feld@FreeBSD.org) Message-Id: <201507201637.t6KGbph1048757@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: feld set sender to feld@FreeBSD.org using -f From: Mark Felder Date: Mon, 20 Jul 2015 16:37:51 +0000 (UTC) To: ports-committers@freebsd.org, svn-ports-all@freebsd.org, svn-ports-head@freebsd.org Subject: svn commit: r392591 - in head/www/apache22: . files X-SVN-Group: ports-head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-ports-head@freebsd.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: SVN commit messages for the ports tree for head List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 20 Jul 2015 16:37:51 -0000 Author: feld Date: Mon Jul 20 16:37:50 2015 New Revision: 392591 URL: https://svnweb.freebsd.org/changeset/ports/392591 Log: Backport patch for CVE and bump PORTREVISION Approved by: pgollucci MFH: 2015Q3 Security: CVE-2015-3183 Security: 29083f8e-2ca8-11e5-86ff-14dae9d210b8 Added: head/www/apache22/files/patch-CVE-2015-3183 (contents, props changed) Modified: head/www/apache22/Makefile Modified: head/www/apache22/Makefile ============================================================================== --- head/www/apache22/Makefile Mon Jul 20 16:35:13 2015 (r392590) +++ head/www/apache22/Makefile Mon Jul 20 16:37:50 2015 (r392591) @@ -2,7 +2,7 @@ PORTNAME= apache22 PORTVERSION= 2.2.29 -PORTREVISION?= 5 +PORTREVISION?= 6 CATEGORIES= www ipv6 MASTER_SITES= APACHE_HTTPD DISTNAME= httpd-${PORTVERSION} Added: head/www/apache22/files/patch-CVE-2015-3183 ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/www/apache22/files/patch-CVE-2015-3183 Mon Jul 20 16:37:50 2015 (r392591) @@ -0,0 +1,777 @@ +diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c +index 347df85..5e190cb 100644 +--- modules/http/http_filters.c ++++ modules/http/http_filters.c +@@ -56,27 +56,31 @@ + #include + #endif + +-#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 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_LF, /* got CR, expect LF after digits/extension */ ++ BODY_CHUNK_DATA, /* data constrained by chunked encoding */ ++ BODY_CHUNK_END, /* chunked data terminating CRLF */ ++ BODY_CHUNK_END_LF, /* got CR, expect LF after data */ ++ 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; + ++/* bail out if some error in the HTTP input filter happens */ + static apr_status_t bail_out_on_error(http_ctx_t *ctx, + ap_filter_t *f, + int http_error) +@@ -109,119 +113,147 @@ static apr_status_t bail_out_on_error(http_ctx_t *ctx, + e = apr_bucket_eos_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + ctx->eos_sent = 1; ++ /* If chunked encoding / content-length are corrupt, we may treat parts ++ * of this request's body as the next one's headers. ++ * To be safe, disable keep-alive. ++ */ ++ f->r->connection->keepalive = AP_CONN_CLOSE; + 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; ++ 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]; + +- if (APR_BUCKET_IS_METADATA(e)) { ++ ap_xlate_proto_from_ascii(&c, 1); ++ ++ /* 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; ++ } ++ ++ if (c == LF) { ++ if (ctx->remaining) { ++ ctx->state = BODY_CHUNK_DATA; ++ } ++ else { ++ ctx->state = BODY_CHUNK_TRAILER; ++ } + } +- if (len > 0) { +- break; /* we got the data we want */ ++ else if (ctx->state == BODY_CHUNK_LF) { ++ /* ++ * LF expected. ++ */ ++ return APR_EINVAL; + } +- /* 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->linesize for next round. */ +- ctx->linesize = 0; +- return APR_SUCCESS; +-} ++ 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 (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) +@@ -235,7 +267,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) { +@@ -282,6 +313,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + 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); +@@ -295,7 +327,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; + +@@ -337,7 +368,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, + "Unknown Transfer-Encoding: %s", tenc); +- return bail_out_on_error(ctx, f, HTTP_NOT_IMPLEMENTED); ++ return bail_out_on_error(ctx, f, HTTP_BAD_REQUEST); + } + lenp = NULL; + } +@@ -357,7 +388,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, + "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 +@@ -399,7 +430,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; + +@@ -424,285 +456,194 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + } + } + } ++ } + +- /* We can't read the chunk until after sending 100 if required. */ +- if (ctx->state == BODY_CHUNK) { +- apr_brigade_cleanup(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; ++ } ++ ++ do { ++ apr_brigade_cleanup(b); ++ again = 0; /* until further notice */ ++ ++ /* read and handle the brigade */ ++ switch (ctx->state) { ++ case BODY_CHUNK: ++ case BODY_CHUNK_PART: ++ case BODY_CHUNK_EXT: ++ case BODY_CHUNK_LF: ++ case BODY_CHUNK_END: ++ case BODY_CHUNK_END_LF: { + +- rv = ap_get_brigade(f->next, bb, AP_MODE_GETLINE, +- block, 0); ++ 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(bb)) || +- (APR_STATUS_IS_EAGAIN(rv)) )) { +- ctx->state = BODY_CHUNK_PART; ++ if (block == APR_NONBLOCK_READ ++ && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b)) ++ || (APR_STATUS_IS_EAGAIN(rv)))) { + 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_SERVICE_UNAVAILABLE; +- } +- } +- } +- apr_brigade_cleanup(bb); +- +- /* Detect chunksize error (such as overflow) */ +- if (rv != APR_SUCCESS || ctx->remaining < 0) { +- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, "Error reading first chunk %s ", +- (ctx->remaining < 0) ? "(overflow)" : ""); +- if (APR_STATUS_IS_TIMEUP(rv) || ctx->remaining > 0) { +- http_error = HTTP_REQUEST_TIME_OUT; +- } +- ctx->remaining = 0; /* Reset it in case we have to +- * come back here later */ +- return bail_out_on_error(ctx, f, http_error); ++ if (rv == APR_EOF) { ++ return APR_INCOMPLETE; + } + +- if (!ctx->remaining) { +- return read_chunked_trailers(ctx, f, b, +- conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE); ++ if (rv != APR_SUCCESS) { ++ return rv; + } +- } +- } +- else { +- bb = ctx->bb; +- } + +- if (ctx->eos_sent) { +- e = apr_bucket_eos_create(f->c->bucket_alloc); +- APR_BRIGADE_INSERT_TAIL(b, e); +- return APR_SUCCESS; +- } ++ e = APR_BRIGADE_FIRST(b); ++ while (e != APR_BRIGADE_SENTINEL(b)) { ++ const char *buffer; ++ apr_size_t len; + +- if (!ctx->remaining) { +- 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); ++ if (!APR_BUCKET_IS_METADATA(e)) { ++ int parsing = 0; + +- /* 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_EOF) { +- return APR_INCOMPLETE; +- } +- 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 = 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; +- http_error = HTTP_SERVICE_UNAVAILABLE; ++ 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, ++ "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_ERR, rv, f->r, "Error reading chunk %s ", +- (ctx->remaining < 0) ? "(overflow)" : ""); +- if (APR_STATUS_IS_TIMEUP(rv) || ctx->remaining > 0) { +- http_error = HTTP_REQUEST_TIME_OUT; +- } +- ctx->remaining = 0; /* Reset it in case we have to +- * come back here later */ +- 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) { + +- rv = ap_get_brigade(f->next, b, mode, block, readbytes); ++ rv = ap_get_brigade(f->next, b, mode, block, readbytes); + +- if (rv == APR_EOF && ctx->state != BODY_NONE && +- ctx->remaining > 0) { +- return APR_INCOMPLETE; +- } +- 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)) { +- apr_bucket_delete(e); +- return APR_INCOMPLETE; +- } +- } +- } ++ /* 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_ERR, 0, f->r, +- "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; +-} ++ } + +-/** +- * 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)); ++ /* 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, ++ "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); ++ } ++ } + +- 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; +- } ++ break; ++ } ++ case BODY_CHUNK_TRAILER: { ++ ++ 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, ++ "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) && (chunkbits <= 0)) { +- /* overflow */ +- return -1; +- } ++ } while (again); + +- return chunksize; ++ return APR_SUCCESS; + } + + typedef struct header_struct {