You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by wr...@apache.org on 2015/06/09 17:39:25 UTC

svn commit: r1684455 - in /httpd/httpd/branches/2.2.x: STATUS modules/http/http_filters.c

Author: wrowe
Date: Tue Jun  9 15:39:25 2015
New Revision: 1684455

URL: http://svn.apache.org/r1684455
Log:
Revert mis-commit, re-fixing STATUS in a moment

Modified:
    httpd/httpd/branches/2.2.x/STATUS
    httpd/httpd/branches/2.2.x/modules/http/http_filters.c

Modified: httpd/httpd/branches/2.2.x/STATUS
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/STATUS?rev=1684455&r1=1684454&r2=1684455&view=diff
==============================================================================
--- httpd/httpd/branches/2.2.x/STATUS (original)
+++ httpd/httpd/branches/2.2.x/STATUS Tue Jun  9 15:39:25 2015
@@ -101,18 +101,18 @@ RELEASE SHOWSTOPPERS:
 PATCHES ACCEPTED TO BACKPORT FROM TRUNK:
   [ start all new proposals below, under PATCHES PROPOSED. ]
 
+
+PATCHES PROPOSED TO BACKPORT FROM TRUNK:
+  [ New proposals should be added at the end of the list ]
+
    * mod_ssl: bring SNI behavior into better conformance with RFC 6066
      (also addresses PR 56241)
      trunk patch: https://svn.apache.org/r1585090
                   (partial, w/o startup warnings changes)
-     2.4.x patch: https://svn.apache.org/r1588424
+     2.4.x patch: https://svn.apache.org/1588424
                   (backported to 2.4.10)
      2.2.x patch: http://people.apache.org/~ylavic/httpd-2.2.x-no_sni_warning.patch
-     +1: ylavic, jorton, wrowe
-
-
-PATCHES PROPOSED TO BACKPORT FROM TRUNK:
-  [ New proposals should be added at the end of the list ]
+     +1: ylavic, jorton
 
 
 PATCHES/ISSUES THAT ARE STALLED

Modified: httpd/httpd/branches/2.2.x/modules/http/http_filters.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/modules/http/http_filters.c?rev=1684455&r1=1684454&r2=1684455&view=diff
==============================================================================
--- httpd/httpd/branches/2.2.x/modules/http/http_filters.c (original)
+++ httpd/httpd/branches/2.2.x/modules/http/http_filters.c Tue Jun  9 15:39:25 2015
@@ -56,31 +56,27 @@
 #include <unistd.h>
 #endif
 
-typedef struct http_filter_ctx
-{
+#define INVALID_CHAR -2
+
+static long get_chunk_size(char *);
+
+typedef struct http_filter_ctx {
     apr_off_t remaining;
     apr_off_t limit;
     apr_off_t limit_used;
-    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 */
+    enum {
+        BODY_NONE,
+        BODY_LENGTH,
+        BODY_CHUNK,
+        BODY_CHUNK_PART
     } state;
-    unsigned int eos_sent :1;
+    int eos_sent;
+    char chunk_ln[32];
+    char *pos;
+    apr_off_t linesize;
     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)
@@ -113,148 +109,120 @@ static apr_status_t bail_out_on_error(ht
     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);
 }
 
-/**
- * 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)
+static apr_status_t get_remaining_chunk_line(http_ctx_t *ctx,
+                                             apr_bucket_brigade *b,
+                                             int linelimit)
 {
-    apr_size_t i = 0;
-
-    while (i < len) {
-        char c = buffer[i];
+    apr_status_t rv;
+    apr_off_t brigade_length;
+    apr_bucket *e;
+    const char *lineend;
+    apr_size_t len;
 
-        ap_xlate_proto_from_ascii(&c, 1);
+    /*
+     * 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)) {
 
-        /* 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++;
+        if (APR_BUCKET_IS_METADATA(e)) {
             continue;
         }
-
-        /* 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;
-            }
-        }
-        else if (ctx->state == BODY_CHUNK_LF) {
-            /*
-             * LF expected.
-             */
-            return APR_EINVAL;
+        rv = apr_bucket_read(e, &lineend, &len, APR_BLOCK_READ);
+        if (rv != APR_SUCCESS) {
+            return rv;
         }
-        else if (c == CR) {
-            ctx->state = BODY_CHUNK_LF;
+        if (len > 0) {
+            break;  /* we got the data we want */
         }
-        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;
-
-            /* ignore leading zeros */
-            if (!ctx->remaining && c == '0') {
-                i++;
-                continue;
-            }
-
-            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;
-            }
+        /* 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;
+}
 
-            ctx->remaining = (ctx->remaining << 4) | xvalue;
-            if (ctx->remaining < 0) {
-                /* overflow */
-                return APR_ENOSPC;
-            }
-        }
-        else {
-            /* Should not happen */
-            return APR_EGENERAL;
-        }
+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;
 
-        i++;
+    tmp_len = sizeof(ctx->chunk_ln) - (ctx->pos - ctx->chunk_ln) - 1;
+    /* Saveguard ourselves against underflows */
+    if (tmp_len < 0) {
+        len = 0;
     }
-
-    /* sanity check */
-    ctx->chunk_used += len;
-    if (ctx->chunk_used < 0 || ctx->chunk_used > linelimit) {
-        return APR_ENOSPC;
+    else {
+        len = (apr_size_t) tmp_len;
     }
-
-    return APR_SUCCESS;
+    /*
+     * 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->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);
+        }
+        /* Line is complete. So reset ctx->pos for next round. */
+        ctx->pos = ctx->chunk_ln;
+        return APR_SUCCESS;
+    }
+    return get_remaining_chunk_line(ctx, b, linelimit);
 }
 
+
 static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f,
                                           apr_bucket_brigade *b, int merge)
 {
@@ -267,6 +235,7 @@ static apr_status_t read_chunked_trailer
     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) {
@@ -313,7 +282,6 @@ apr_status_t ap_http_filter(ap_filter_t
     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);
@@ -327,6 +295,7 @@ apr_status_t ap_http_filter(ap_filter_t
         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;
 
@@ -368,7 +337,7 @@ apr_status_t ap_http_filter(ap_filter_t
                  */
                 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r,
                               "Unknown Transfer-Encoding: %s", tenc);
-                return bail_out_on_error(ctx, f, HTTP_BAD_REQUEST);
+                return bail_out_on_error(ctx, f, HTTP_NOT_IMPLEMENTED);
             }
             lenp = NULL;
         }
@@ -388,7 +357,7 @@ apr_status_t ap_http_filter(ap_filter_t
                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r,
                               "Invalid Content-Length");
 
-                return bail_out_on_error(ctx, f, HTTP_BAD_REQUEST);
+                return bail_out_on_error(ctx, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
             }
 
             /* If we have a limit in effect and we know the C-L ahead of
@@ -430,8 +399,7 @@ apr_status_t ap_http_filter(ap_filter_t
             if (!ap_is_HTTP_SUCCESS(f->r->status)) {
                 ctx->state = BODY_NONE;
                 ctx->eos_sent = 1;
-            }
-            else {
+            } else {
                 char *tmp;
                 int len;
 
@@ -456,194 +424,285 @@ apr_status_t ap_http_filter(ap_filter_t
                 }
             }
         }
-    }
 
-    /* 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: {
+        /* 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, b, AP_MODE_GETLINE, block, 0);
+            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(b))
-                            || (APR_STATUS_IS_EAGAIN(rv)))) {
+            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_EOF) {
-                return APR_INCOMPLETE;
+            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);
 
-            if (rv != APR_SUCCESS) {
-                return rv;
+            /* 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);
             }
 
-            e = APR_BRIGADE_FIRST(b);
-            while (e != APR_BRIGADE_SENTINEL(b)) {
-                const char *buffer;
-                apr_size_t len;
+            if (!ctx->remaining) {
+                return read_chunked_trailers(ctx, f, b,
+                        conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE);
+            }
+        }
+    }
+    else {
+        bb = ctx->bb;
+    }
 
-                if (!APR_BUCKET_IS_METADATA(e)) {
-                    int parsing = 0;
+    if (ctx->eos_sent) {
+        e = apr_bucket_eos_create(f->c->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(b, e);
+        return APR_SUCCESS;
+    }
 
-                    rv = apr_bucket_read(e, &buffer, &len, APR_BLOCK_READ);
+    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 (rv == APR_SUCCESS) {
-                        parsing = 1;
-                        rv = parse_chunk_size(ctx, buffer, len,
-                                f->r->server->limit_req_fieldsize);
+                /* 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) {
-                        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 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;
+                }
+
+                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;
                             }
-                            return bail_out_on_error(ctx, f, http_error);
                         }
-                        return rv;
                     }
+                    apr_brigade_cleanup(bb);
                 }
 
-                apr_bucket_delete(e);
-                e = APR_BRIGADE_FIRST(b);
-            }
-            again = 1; /* come around again */
+                /* 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);
+                }
 
-            if (ctx->state == BODY_CHUNK_TRAILER) {
-                /* Treat UNSET as DISABLE - trailers aren't merged by default */
-                return read_chunked_trailers(ctx, f, b,
+                if (!ctx->remaining) {
+                    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_NONE && ctx->remaining < readbytes) {
-                readbytes = ctx->remaining;
-            }
-            if (readbytes > 0) {
-
-                rv = ap_get_brigade(f->next, b, mode, block, readbytes);
-
-                /* 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 && ctx->state != BODY_NONE
-                        && ctx->remaining > 0) {
-                    return APR_INCOMPLETE;
-                }
+    }
 
-                if (rv != APR_SUCCESS) {
-                    return rv;
-                }
+    /* 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);
+    }
 
-                /* How many bytes did we just read? */
-                apr_brigade_length(b, 0, &totalread);
+    rv = ap_get_brigade(f->next, b, mode, block, readbytes);
 
-                /* If this happens, we have a bucket of unknown length.  Die because
-                 * it means our assumptions have changed. */
-                AP_DEBUG_ASSERT(totalread >= 0);
-
-                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;
-                    }
-                }
+    if (rv == APR_EOF && ctx->state != BODY_NONE &&
+            ctx->remaining > 0) {
+        return APR_INCOMPLETE;
+    }
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
 
-            }
+    /* 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 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;
+    /* If this happens, we have a bucket of unknown length.  Die because
+     * it means our assumptions have changed. */
+    AP_DEBUG_ASSERT(totalread >= 0);
+
+    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;
             }
+        }
+    }
 
-            /* 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 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);
+    }
 
-            break;
+    /* 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);
         }
-        case BODY_CHUNK_TRAILER: {
+    }
 
-            rv = ap_get_brigade(f->next, b, mode, block, readbytes);
+    return APR_SUCCESS;
+}
 
-            /* for timeout */
-            if (block == APR_NONBLOCK_READ
-                    && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b))
-                            || (APR_STATUS_IS_EAGAIN(rv)))) {
-                return APR_EAGAIN;
-            }
+/**
+ * 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 (rv != APR_SUCCESS) {
-                return rv;
-            }
+    ap_xlate_proto_from_ascii(b, strlen(b));
 
-            break;
+    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;
+    }
+
+    while (apr_isxdigit(*b) && (chunkbits > 0)) {
+        int xvalue = 0;
+
+        if (*b >= '0' && *b <= '9') {
+            xvalue = *b - '0';
         }
-        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;
         }
+        else if (*b >= 'a' && *b <= 'f') {
+            xvalue = *b - 'a' + 0xa;
         }
 
-    } while (again);
+        chunksize = (chunksize << 4) | xvalue;
+        chunkbits -= 4;
+        ++b;
+    }
+    if (apr_isxdigit(*b) && (chunkbits <= 0)) {
+        /* overflow */
+        return -1;
+    }
 
-    return APR_SUCCESS;
+    return chunksize;
 }
 
 typedef struct header_struct {