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 2014/08/22 13:37:20 UTC

svn commit: r1619753 - in /httpd/httpd/branches/2.2.x: ./ CHANGES modules/filters/mod_deflate.c

Author: wrowe
Date: Fri Aug 22 11:37:19 2014
New Revision: 1619753

URL: http://svn.apache.org/r1619753
Log:
mod_deflate: Handle Zlib header and validation bytes received in multiple
chunks.

PR: 46146, 55666
Submitted by: Yann Ylavic
Reviewed by: ylavic, wrowe, rpluem
Backports: r1572655, r1572663, r1572668, r1572669, r1572670, r1572671, r1573224, r1586745, r1587594, r1587639, r1590509, r1603156, r1604353


Modified:
    httpd/httpd/branches/2.2.x/   (props changed)
    httpd/httpd/branches/2.2.x/CHANGES
    httpd/httpd/branches/2.2.x/modules/filters/mod_deflate.c

Propchange: httpd/httpd/branches/2.2.x/
------------------------------------------------------------------------------
  Merged /httpd/httpd/trunk:r1572655,1572663,1572668-1572671,1573224,1586745,1587594,1587639,1590509,1604353

Modified: httpd/httpd/branches/2.2.x/CHANGES
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/CHANGES?rev=1619753&r1=1619752&r2=1619753&view=diff
==============================================================================
--- httpd/httpd/branches/2.2.x/CHANGES [utf-8] (original)
+++ httpd/httpd/branches/2.2.x/CHANGES [utf-8] Fri Aug 22 11:37:19 2014
@@ -20,6 +20,9 @@ Changes with Apache 2.2.28
   *) SECURITY: CVE-2014-0226 (cve.mitre.org)
      Fix a race condition in scoreboard handling, which could lead to
      a heap buffer overflow.  [Joe Orton, Eric Covener, Jeff Trawick]
+ 
+  *) mod_deflate: Handle Zlib header and validation bytes received in multiple
+     chunks. PR 46146. [Yann Ylavic]
 
   *) SECURITY: CVE-2013-5704 (cve.mitre.org)
      core: HTTP trailers could be used to replace HTTP headers

Modified: httpd/httpd/branches/2.2.x/modules/filters/mod_deflate.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/modules/filters/mod_deflate.c?rev=1619753&r1=1619752&r2=1619753&view=diff
==============================================================================
--- httpd/httpd/branches/2.2.x/modules/filters/mod_deflate.c (original)
+++ httpd/httpd/branches/2.2.x/modules/filters/mod_deflate.c Fri Aug 22 11:37:19 2014
@@ -360,9 +360,15 @@ typedef struct deflate_ctx_t
     int (*libz_end_func)(z_streamp);
     unsigned char *validation_buffer;
     apr_size_t validation_buffer_length;
-    int inflate_init;
+    char header[10]; /* sizeof(gzip_header) */
+    apr_size_t header_len;
+    int zlib_flags;
     int ratio_hits;
     apr_off_t inflate_total;
+    unsigned int consume_pos,
+                 consume_len;
+    unsigned int filter_init:1;
+    unsigned int done:1;
 } deflate_ctx;
 
 /* Check whether the (inflate) ratio exceeds the configured limit/burst. */
@@ -749,6 +755,9 @@ static apr_status_t deflate_out_filter(a
             zRC = flush_libz_buffer(ctx, c, f->c->bucket_alloc, deflate,
                                     Z_SYNC_FLUSH, NO_UPDATE_CRC);
             if (zRC != Z_OK) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                              "Zlib error %d flushing zlib output buffer (%s)",
+                              zRC, ctx->stream.msg);
                 return APR_EGENERAL;
             }
 
@@ -774,6 +783,14 @@ static apr_status_t deflate_out_filter(a
 
         /* read */
         apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+        if (!len) {
+            apr_bucket_delete(e);
+            continue;
+        }
+        if (len > APR_INT32_MAX) {
+            apr_bucket_split(e, APR_INT32_MAX);
+            apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+        }
 
         /* This crc32 function is from zlib. */
         ctx->crc = crc32(ctx->crc, (const Bytef *)data, len);
@@ -805,6 +822,9 @@ static apr_status_t deflate_out_filter(a
             zRC = deflate(&(ctx->stream), Z_NO_FLUSH);
 
             if (zRC != Z_OK) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                              "Zlib error %d deflating data (%s)", zRC,
+                              ctx->stream.msg);
                 return APR_EGENERAL;
             }
         }
@@ -816,6 +836,96 @@ static apr_status_t deflate_out_filter(a
     return APR_SUCCESS;
 }
 
+static apr_status_t consume_zlib_flags(deflate_ctx *ctx,
+                                       const char **data, apr_size_t *len)
+{
+    if ((ctx->zlib_flags & EXTRA_FIELD)) {
+        /* Consume 2 bytes length prefixed data. */
+        if (ctx->consume_pos == 0) {
+            if (!*len) {
+                return APR_INCOMPLETE;
+            }
+            ctx->consume_len = (unsigned int)**data;
+            ctx->consume_pos++;
+            ++*data;
+            --*len;
+        }
+        if (ctx->consume_pos == 1) {
+            if (!*len) {
+                return APR_INCOMPLETE;
+            }
+            ctx->consume_len += ((unsigned int)**data) << 8;
+            ctx->consume_pos++;
+            ++*data;
+            --*len;
+        }
+        if (*len < ctx->consume_len) {
+            ctx->consume_len -= *len;
+            *len = 0;
+            return APR_INCOMPLETE;
+        }
+        *data += ctx->consume_len;
+        *len -= ctx->consume_len;
+
+        ctx->consume_len = ctx->consume_pos = 0;
+        ctx->zlib_flags &= ~EXTRA_FIELD;
+    }
+
+    if ((ctx->zlib_flags & ORIG_NAME)) {
+        /* Consume nul terminated string. */
+        while (*len && **data) {
+            ++*data;
+            --*len;
+        }
+        if (!*len) {
+            return APR_INCOMPLETE;
+        }
+        /* .. and nul. */
+        ++*data;
+        --*len;
+
+        ctx->zlib_flags &= ~ORIG_NAME;
+    }
+
+    if ((ctx->zlib_flags & COMMENT)) {
+        /* Consume nul terminated string. */
+        while (*len && **data) {
+            ++*data;
+            --*len;
+        }
+        if (!*len) {
+            return APR_INCOMPLETE;
+        }
+        /* .. and nul. */
+        ++*data;
+        --*len;
+
+        ctx->zlib_flags &= ~COMMENT;
+    }
+
+    if ((ctx->zlib_flags & HEAD_CRC)) {
+        /* Consume CRC16 (2 octets). */
+        if (ctx->consume_pos == 0) {
+            if (!*len) {
+                return APR_INCOMPLETE;
+            }
+            ctx->consume_pos++;
+            ++*data;
+            --*len;
+        }
+        if (!*len) {
+            return APR_INCOMPLETE;
+        }
+        ++*data;
+        --*len;
+        
+        ctx->consume_pos = 0;
+        ctx->zlib_flags &= ~HEAD_CRC;
+    }
+
+    return APR_SUCCESS;
+}
+
 /* This is the deflate input filter (inflates).  */
 static apr_status_t deflate_in_filter(ap_filter_t *f,
                                       apr_bucket_brigade *bb,
@@ -840,64 +950,98 @@ static apr_status_t deflate_in_filter(ap
     c = ap_get_module_config(r->server->module_config, &deflate_module);
     dc = ap_get_module_config(r->per_dir_config, &deflate_module);
 
-    if (!ctx) {
-        char deflate_hdr[10];
+    if (!ctx || ctx->header_len < sizeof(ctx->header)) {
         apr_size_t len;
 
-        /* only work on main request/no subrequests */
-        if (!ap_is_initial_req(r)) {
-            ap_remove_input_filter(f);
-            return ap_get_brigade(f->next, bb, mode, block, readbytes);
-        }
+        if (!ctx) {
+            /* only work on main request/no subrequests */
+            if (!ap_is_initial_req(r)) {
+                ap_remove_input_filter(f);
+                return ap_get_brigade(f->next, bb, mode, block, readbytes);
+            }
+
+            /* We can't operate on Content-Ranges */
+            if (apr_table_get(r->headers_in, "Content-Range") != NULL) {
+                ap_remove_input_filter(f);
+                return ap_get_brigade(f->next, bb, mode, block, readbytes);
+            }
+
+            /* Check whether request body is gzipped.
+             *
+             * If it is, we're transforming the contents, invalidating
+             * some request headers including Content-Encoding.
+             *
+             * If not, we just remove ourself.
+             */
+            if (check_gzip(r, r->headers_in, NULL) == 0) {
+                ap_remove_input_filter(f);
+                return ap_get_brigade(f->next, bb, mode, block, readbytes);
+            }
 
-        /* We can't operate on Content-Ranges */
-        if (apr_table_get(r->headers_in, "Content-Range") != NULL) {
-            ap_remove_input_filter(f);
-            return ap_get_brigade(f->next, bb, mode, block, readbytes);
+            f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
+            ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
+            ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
+            ctx->buffer = apr_palloc(r->pool, c->bufferSize);
         }
 
-        /* Check whether request body is gzipped.
-         *
-         * If it is, we're transforming the contents, invalidating
-         * some request headers including Content-Encoding.
-         *
-         * If not, we just remove ourself.
-         */
-        if (check_gzip(r, r->headers_in, NULL) == 0) {
-            ap_remove_input_filter(f);
-            return ap_get_brigade(f->next, bb, mode, block, readbytes);
-        }
+        do {
+            apr_brigade_cleanup(ctx->bb);
 
-        f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
-        ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
-        ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
-        ctx->buffer = apr_palloc(r->pool, c->bufferSize);
+            len = sizeof(ctx->header) - ctx->header_len;
+            rv = ap_get_brigade(f->next, ctx->bb, AP_MODE_READBYTES, block,
+                                len);
 
-        rv = ap_get_brigade(f->next, ctx->bb, AP_MODE_READBYTES, block, 10);
-        if (rv != APR_SUCCESS) {
-            return rv;
-        }
+            /* ap_get_brigade may return success with an empty brigade for
+             * a non-blocking read which would block (an empty brigade for
+             * a blocking read is an issue which is simply forwarded here).
+             */
+            if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(ctx->bb)) {
+                return rv;
+            }
 
-        apr_table_unset(r->headers_in, "Content-Length");
-        apr_table_unset(r->headers_in, "Content-MD5");
+            /* zero length body? step aside */
+            bkt = APR_BRIGADE_FIRST(ctx->bb);
+            if (APR_BUCKET_IS_EOS(bkt)) {
+                if (ctx->header_len) {
+                    /* If the header was (partially) read it's an error, this
+                     * is not a gzip Content-Encoding, as claimed.
+                     */
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                                  "Encountered premature end-of-stream while "
+                                  "reading inflate header");
+                    return APR_EGENERAL;
+                }
+                APR_BUCKET_REMOVE(bkt);
+                APR_BRIGADE_INSERT_TAIL(bb, bkt);
+                ap_remove_input_filter(f);
+                return APR_SUCCESS;
+            }
 
-        len = 10;
-        rv = apr_brigade_flatten(ctx->bb, deflate_hdr, &len);
-        if (rv != APR_SUCCESS) {
-            return rv;
-        }
+            rv = apr_brigade_flatten(ctx->bb,
+                                     ctx->header + ctx->header_len, &len);
+            if (rv != APR_SUCCESS) {
+                return rv;
+            }
+            if (len && !ctx->header_len) {
+                apr_table_unset(r->headers_in, "Content-Length");
+                apr_table_unset(r->headers_in, "Content-MD5");
+            }
+            ctx->header_len += len;
+
+        } while (ctx->header_len < sizeof(ctx->header));
 
         /* We didn't get the magic bytes. */
-        if (len != 10 ||
-            deflate_hdr[0] != deflate_magic[0] ||
-            deflate_hdr[1] != deflate_magic[1]) {
-            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Failed to inflate input: wrong/partial magic bytes");
+        if (ctx->header[0] != deflate_magic[0] ||
+            ctx->header[1] != deflate_magic[1]) {
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                          "Zlib: Invalid header");
             return APR_EGENERAL;
         }
 
-        /* We can't handle flags for now. */
-        if (deflate_hdr[3] != 0) {
-            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Failed to inflate input: cannot handle deflate flags");
+        ctx->zlib_flags = ctx->header[3];
+        if ((ctx->zlib_flags & RESERVED)) {
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                          "Zlib: Invalid flags %02x", ctx->zlib_flags);
             return APR_EGENERAL;
         }
 
@@ -930,8 +1074,15 @@ static apr_status_t deflate_in_filter(ap
     if (APR_BRIGADE_EMPTY(ctx->proc_bb)) {
         rv = ap_get_brigade(f->next, ctx->bb, mode, block, readbytes);
 
+        /* Don't terminate on EAGAIN (or success with an empty brigade in
+         * non-blocking mode), just return focus.
+         */
+        if (block == APR_NONBLOCK_READ
+                && (APR_STATUS_IS_EAGAIN(rv)
+                    || (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(ctx->bb)))) {
+            return rv;
+        }
         if (rv != APR_SUCCESS) {
-            /* What about APR_EAGAIN errors? */
             inflateEnd(&ctx->stream);
             return rv;
         }
@@ -945,14 +1096,26 @@ static apr_status_t deflate_in_filter(ap
 
             /* If we actually see the EOS, that means we screwed up! */
             if (APR_BUCKET_IS_EOS(bkt)) {
-                inflateEnd(&ctx->stream);
-                return APR_EGENERAL;
+                if (!ctx->done) {
+                    inflateEnd(&ctx->stream);
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                                  "Encountered premature end-of-stream while inflating");
+                    return APR_EGENERAL;
+                }
+
+                /* Move everything to the returning brigade. */
+                APR_BUCKET_REMOVE(bkt);
+                APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, bkt);
+                break;
             }
 
             if (APR_BUCKET_IS_FLUSH(bkt)) {
-                apr_bucket *tmp_heap;
+                apr_bucket *tmp_b;
                 zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH);
                 if (zRC != Z_OK) {
+                    ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                                  "Zlib error %d inflating data (%s)", zRC,
+                                  ctx->stream.msg);
                     inflateEnd(&ctx->stream);
                     return APR_EGENERAL;
                 }
@@ -972,72 +1135,130 @@ static apr_status_t deflate_in_filter(ap
                 }
 
                 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
-                tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
-                                                 NULL, f->c->bucket_alloc);
-                APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
+                tmp_b = apr_bucket_heap_create((char *)ctx->buffer, len,
+                                                NULL, f->c->bucket_alloc);
+                APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_b);
                 ctx->stream.avail_out = c->bufferSize;
 
-                /* Move everything to the returning brigade. */
+                /* Flush everything so far in the returning brigade, but continue
+                 * reading should EOS/more follow (don't lose them).
+                 */
+                tmp_b = APR_BUCKET_PREV(bkt);
                 APR_BUCKET_REMOVE(bkt);
-                APR_BRIGADE_CONCAT(bb, ctx->bb);
-                break;
+                APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, bkt);
+                bkt = tmp_b;
+                continue;
+            }
+
+            /* sanity check - data after completed compressed body and before eos? */
+            if (ctx->done) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                              "Encountered extra data after compressed data");
+                return APR_EGENERAL;
             }
 
             /* read */
             apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
+            if (!len) {
+                continue;
+            }
+            if (len > APR_INT32_MAX) {
+                apr_bucket_split(bkt, APR_INT32_MAX);
+                apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
+            }
+
+            if (ctx->zlib_flags) {
+                rv = consume_zlib_flags(ctx, &data, &len);
+                if (rv == APR_SUCCESS) {
+                    ctx->zlib_flags = 0;
+                }
+                if (!len) {
+                    continue;
+                }
+            }
 
             /* pass through zlib inflate. */
             ctx->stream.next_in = (unsigned char *)data;
-            ctx->stream.avail_in = len;
+            ctx->stream.avail_in = (int)len;
 
             zRC = Z_OK;
 
-            while (ctx->stream.avail_in != 0) {
-                if (ctx->stream.avail_out == 0) {
-                    apr_bucket *tmp_heap;
-                    ctx->stream.next_out = ctx->buffer;
-                    len = c->bufferSize - ctx->stream.avail_out;
-
-                      ctx->inflate_total += len;
-                      if (inflate_limit && ctx->inflate_total > inflate_limit) { 
-                          inflateEnd(&ctx->stream);
-                          ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
-                                  "Inflated content length of %" APR_OFF_T_FMT
-                                  " is larger than the configured limit"
-                                  " of %" APR_OFF_T_FMT, 
-                                  ctx->inflate_total, inflate_limit);
-                          return APR_ENOSPC;
-                      }
-
-                      if (!check_ratio(r, ctx, dc)) {
-                          inflateEnd(&ctx->stream);
-                          ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, 
-                                  "Inflated content ratio is larger than the "
-                                  "configured limit %i by %i time(s)",
-                                  dc->ratio_limit, dc->ratio_burst);
-                          return APR_EINVAL;
-                      }
-
-                    ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
-                    tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
-                                                      NULL, f->c->bucket_alloc);
-                    APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
-                    ctx->stream.avail_out = c->bufferSize;
-                }
+            if (!ctx->validation_buffer) {
+                while (ctx->stream.avail_in != 0) {
+                    if (ctx->stream.avail_out == 0) {
+                        apr_bucket *tmp_heap;
+                        ctx->stream.next_out = ctx->buffer;
+                        len = c->bufferSize - ctx->stream.avail_out;
+
+                        ctx->inflate_total += len;
+                        if (inflate_limit && ctx->inflate_total > inflate_limit) { 
+                            inflateEnd(&ctx->stream);
+                            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                                    "Inflated content length of %" APR_OFF_T_FMT
+                                    " is larger than the configured limit"
+                                    " of %" APR_OFF_T_FMT, 
+                                    ctx->inflate_total, inflate_limit);
+                            return APR_ENOSPC;
+                        }
+
+                        if (!check_ratio(r, ctx, dc)) {
+                            inflateEnd(&ctx->stream);
+                            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, 
+                                    "Inflated content ratio is larger than the "
+                                    "configured limit %i by %i time(s)",
+                                    dc->ratio_limit, dc->ratio_burst);
+                            return APR_EINVAL;
+                        }
+
+                        ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
+                        tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
+                                                          NULL, f->c->bucket_alloc);
+                        APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
+                        ctx->stream.avail_out = c->bufferSize;
+                    }
 
-                zRC = inflate(&ctx->stream, Z_NO_FLUSH);
+                    zRC = inflate(&ctx->stream, Z_NO_FLUSH);
 
-                if (zRC == Z_STREAM_END) {
-                    break;
-                }
+                    if (zRC == Z_STREAM_END) {
+                        ctx->validation_buffer = apr_pcalloc(r->pool,
+                                                             VALIDATION_SIZE);
+                        ctx->validation_buffer_length = 0;
+                        break;
+                    }
 
-                if (zRC != Z_OK) {
-                    inflateEnd(&ctx->stream);
-                    return APR_EGENERAL;
+                    if (zRC != Z_OK) {
+                        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                                      "Zlib error %d inflating data (%s)", zRC,
+                                      ctx->stream.msg);
+                        inflateEnd(&ctx->stream);
+                        return APR_EGENERAL;
+                    }
                 }
             }
-            if (zRC == Z_STREAM_END) {
-                apr_bucket *tmp_heap, *eos;
+
+            if (ctx->validation_buffer) {
+                apr_bucket *tmp_heap;
+                apr_size_t avail, valid;
+                unsigned char *buf = ctx->validation_buffer;
+
+                avail = ctx->stream.avail_in;
+                valid = (apr_size_t)VALIDATION_SIZE -
+                         ctx->validation_buffer_length;
+
+                /*
+                 * We have inflated all data. Now try to capture the
+                 * validation bytes. We may not have them all available
+                 * right now, but capture what is there.
+                 */
+                if (avail < valid) {
+                    memcpy(buf + ctx->validation_buffer_length,
+                           ctx->stream.next_in, avail);
+                    ctx->validation_buffer_length += avail;
+                    continue;
+                }
+                memcpy(buf + ctx->validation_buffer_length,
+                       ctx->stream.next_in, valid);
+                ctx->validation_buffer_length += valid;
 
                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                               "Zlib: Inflated %ld to %ld : URL %s",
@@ -1052,34 +1273,37 @@ static apr_status_t deflate_in_filter(ap
                 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
                 ctx->stream.avail_out = c->bufferSize;
 
-                /* Is the remaining 8 bytes already in the avail stream? */
-                if (ctx->stream.avail_in >= 8) {
+                {
                     unsigned long compCRC, compLen;
-                    compCRC = getLong(ctx->stream.next_in);
+                    compCRC = getLong(buf);
                     if (ctx->crc != compCRC) {
                         inflateEnd(&ctx->stream);
+                        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                                      "Zlib: CRC error inflating data");
                         return APR_EGENERAL;
                     }
-                    ctx->stream.next_in += 4;
-                    compLen = getLong(ctx->stream.next_in);
+                    compLen = getLong(buf + VALIDATION_SIZE / 2);
                     /* gzip stores original size only as 4 byte value */
                     if ((ctx->stream.total_out & 0xFFFFFFFF) != compLen) {
                         inflateEnd(&ctx->stream);
+                        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                                      "Zlib: Length %ld of inflated data does "
+                                      "not match expected value %ld",
+                                      ctx->stream.total_out, compLen);
                         return APR_EGENERAL;
                     }
                 }
-                else {
-                    /* FIXME: We need to grab the 8 verification bytes
-                     * from the wire! */
-                    inflateEnd(&ctx->stream);
-                    return APR_EGENERAL;
-                }
 
                 inflateEnd(&ctx->stream);
 
-                eos = apr_bucket_eos_create(f->c->bucket_alloc);
-                APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, eos);
-                break;
+                ctx->done = 1;
+
+                /* Did we have trailing data behind the closing 8 bytes? */
+                if (avail > valid) {
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                                  "Encountered extra data after compressed data");
+                    return APR_EGENERAL;
+                }
             }
 
         }
@@ -1090,8 +1314,8 @@ static apr_status_t deflate_in_filter(ap
      * some data in our zlib buffer, flush it out so we can return something.
      */
     if (block == APR_BLOCK_READ &&
-        APR_BRIGADE_EMPTY(ctx->proc_bb) &&
-        ctx->stream.avail_out < c->bufferSize) {
+            APR_BRIGADE_EMPTY(ctx->proc_bb) &&
+            ctx->stream.avail_out < c->bufferSize) {
         apr_bucket *tmp_heap;
         apr_size_t len;
         ctx->stream.next_out = ctx->buffer;
@@ -1113,6 +1337,9 @@ static apr_status_t deflate_in_filter(ap
         newbb = apr_brigade_split(ctx->proc_bb, bkt);
         APR_BRIGADE_CONCAT(bb, ctx->proc_bb);
         APR_BRIGADE_CONCAT(ctx->proc_bb, newbb);
+        if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
+            ap_remove_input_filter(f);
+        }
     }
 
     return APR_SUCCESS;
@@ -1123,8 +1350,6 @@ static apr_status_t deflate_in_filter(ap
 static apr_status_t inflate_out_filter(ap_filter_t *f,
                                       apr_bucket_brigade *bb)
 {
-    int zlib_method;
-    int zlib_flags;
     apr_bucket *e;
     request_rec *r = f->r;
     deflate_ctx *ctx = f->ctx;
@@ -1209,8 +1434,6 @@ static apr_status_t inflate_out_filter(a
         /* initialize inflate output buffer */
         ctx->stream.next_out = ctx->buffer;
         ctx->stream.avail_out = c->bufferSize;
-
-        ctx->inflate_init = 0;
     }
 
     while (!APR_BRIGADE_EMPTY(bb))
@@ -1320,56 +1543,68 @@ static apr_status_t inflate_out_filter(a
 
         /* read */
         apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+        if (!len) {
+            apr_bucket_delete(e);
+            continue;
+        }
+        if (len > APR_INT32_MAX) {
+            apr_bucket_split(e, APR_INT32_MAX);
+            apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+        }
 
         /* first bucket contains zlib header */
-        if (!ctx->inflate_init++) {
-            if (len < 10) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                              "Insufficient data for inflate");
-                return APR_EGENERAL;
-            }
-            else  {
-                zlib_method = data[2];
-                zlib_flags = data[3];
+        if (ctx->header_len < sizeof(ctx->header)) {
+            apr_size_t rem;
+
+            rem = sizeof(ctx->header) - ctx->header_len;
+            if (len < rem) {
+                memcpy(ctx->header + ctx->header_len, data, len);
+                ctx->header_len += len;
+                apr_bucket_delete(e);
+                continue;
+            }
+            memcpy(ctx->header + ctx->header_len, data, rem);
+            ctx->header_len += rem;
+            {
+                int zlib_method;
+                zlib_method = ctx->header[2];
                 if (zlib_method != Z_DEFLATED) {
                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                                   "inflate: data not deflated!");
                     ap_remove_output_filter(f);
                     return ap_pass_brigade(f->next, bb);
                 }
-                if (data[0] != deflate_magic[0] ||
-                    data[1] != deflate_magic[1] ||
-                    (zlib_flags & RESERVED) != 0) {
+                if (ctx->header[0] != deflate_magic[0] ||
+                    ctx->header[1] != deflate_magic[1]) {
                         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                       "inflate: bad header");
                     return APR_EGENERAL ;
                 }
-                data += 10 ;
-                len -= 10 ;
-           }
-           if (zlib_flags & EXTRA_FIELD) {
-               unsigned int bytes = (unsigned int)(data[0]);
-               bytes += ((unsigned int)(data[1])) << 8;
-               bytes += 2;
-               if (len < bytes) {
-                   ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                                 "inflate: extra field too big (not "
-                                 "supported)");
-                   return APR_EGENERAL;
-               }
-               data += bytes;
-               len -= bytes;
-           }
-           if (zlib_flags & ORIG_NAME) {
-               while (len-- && *data++);
-           }
-           if (zlib_flags & COMMENT) {
-               while (len-- && *data++);
-           }
-           if (zlib_flags & HEAD_CRC) {
-                len -= 2;
-                data += 2;
-           }
+                ctx->zlib_flags = ctx->header[3];
+                if ((ctx->zlib_flags & RESERVED)) {
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                                  "inflate: bad flags %02x",
+                                  ctx->zlib_flags);
+                    return APR_EGENERAL;
+                }
+            }
+            if (len == rem) {
+                apr_bucket_delete(e);
+                continue;
+            }
+            data += rem;
+            len -= rem;
+        }
+
+        if (ctx->zlib_flags) {
+            rv = consume_zlib_flags(ctx, &data, &len);
+            if (rv == APR_SUCCESS) {
+                ctx->zlib_flags = 0;
+            }
+            if (!len) {
+                apr_bucket_delete(e);
+                continue;
+            }
         }
 
         /* pass through zlib inflate. */
@@ -1458,6 +1693,9 @@ static apr_status_t inflate_out_filter(a
             }
 
             if (zRC != Z_OK) {
+                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                              "Zlib error %d inflating data (%s)", zRC,
+                              ctx->stream.msg);
                 return APR_EGENERAL;
             }
         }