b022e3b
# ./pullrev.sh 1808230
b022e3b
http://svn.apache.org/viewvc?view=revision&revision=1808230
b022e3b
b022e3b
--- httpd-2.4.27/server/protocol.c
b022e3b
+++ httpd-2.4.27/server/protocol.c
b022e3b
@@ -1708,62 +1708,88 @@
b022e3b
         ctx->tmpbb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
b022e3b
     }
b022e3b
 
b022e3b
-    /* Loop through this set of buckets to compute their length
b022e3b
-     */
b022e3b
+    /* Loop through the brigade to count the length. To avoid
b022e3b
+     * arbitrary memory consumption with morphing bucket types, this
b022e3b
+     * loop will stop and pass on the brigade when necessary. */
b022e3b
     e = APR_BRIGADE_FIRST(b);
b022e3b
     while (e != APR_BRIGADE_SENTINEL(b)) {
b022e3b
+        apr_status_t rv;
b022e3b
+
b022e3b
         if (APR_BUCKET_IS_EOS(e)) {
b022e3b
             eos = 1;
b022e3b
             break;
b022e3b
         }
b022e3b
-        if (e->length == (apr_size_t)-1) {
b022e3b
+        /* For a flush bucket, fall through to pass the brigade and
b022e3b
+         * flush now. */
b022e3b
+        else if (APR_BUCKET_IS_FLUSH(e)) {
b022e3b
+            e = APR_BUCKET_NEXT(e);
b022e3b
+        }
b022e3b
+        /* For metadata bucket types other than FLUSH, loop. */
b022e3b
+        else if (APR_BUCKET_IS_METADATA(e)) {
b022e3b
+            e = APR_BUCKET_NEXT(e);
b022e3b
+            continue;
b022e3b
+        }
b022e3b
+        /* For determinate length data buckets, count the length and
b022e3b
+         * continue. */
b022e3b
+        else if (e->length != (apr_size_t)-1) {
b022e3b
+            r->bytes_sent += e->length;
b022e3b
+            e = APR_BUCKET_NEXT(e);
b022e3b
+            continue;
b022e3b
+        }
b022e3b
+        /* For indeterminate length data buckets, perform one read. */
b022e3b
+        else /* e->length == (apr_size_t)-1 */ {
b022e3b
             apr_size_t len;
b022e3b
             const char *ignored;
b022e3b
-            apr_status_t rv;
b022e3b
-
b022e3b
-            /* This is probably a pipe bucket.  Send everything
b022e3b
-             * prior to this, and then read the data for this bucket.
b022e3b
-             */
b022e3b
+        
b022e3b
             rv = apr_bucket_read(e, &ignored, &len, eblock);
b022e3b
+            if ((rv != APR_SUCCESS) && !APR_STATUS_IS_EAGAIN(rv)) {
b022e3b
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00574)
b022e3b
+                              "ap_content_length_filter: "
b022e3b
+                              "apr_bucket_read() failed");
b022e3b
+                return rv;
b022e3b
+            }
b022e3b
             if (rv == APR_SUCCESS) {
b022e3b
-                /* Attempt a nonblocking read next time through */
b022e3b
                 eblock = APR_NONBLOCK_READ;
b022e3b
+                e = APR_BUCKET_NEXT(e);
b022e3b
                 r->bytes_sent += len;
b022e3b
             }
b022e3b
             else if (APR_STATUS_IS_EAGAIN(rv)) {
b022e3b
-                /* Output everything prior to this bucket, and then
b022e3b
-                 * do a blocking read on the next batch.
b022e3b
-                 */
b022e3b
-                if (e != APR_BRIGADE_FIRST(b)) {
b022e3b
-                    apr_bucket *flush;
b022e3b
-                    apr_brigade_split_ex(b, e, ctx->tmpbb);
b022e3b
-                    flush = apr_bucket_flush_create(r->connection->bucket_alloc);
b022e3b
+                apr_bucket *flush;
b022e3b
 
b022e3b
-                    APR_BRIGADE_INSERT_TAIL(b, flush);
b022e3b
-                    rv = ap_pass_brigade(f->next, b);
b022e3b
-                    if (rv != APR_SUCCESS || f->c->aborted) {
b022e3b
-                        return rv;
b022e3b
-                    }
b022e3b
-                    apr_brigade_cleanup(b);
b022e3b
-                    APR_BRIGADE_CONCAT(b, ctx->tmpbb);
b022e3b
-                    e = APR_BRIGADE_FIRST(b);
b022e3b
+                /* Next read must block. */
b022e3b
+                eblock = APR_BLOCK_READ;
b022e3b
 
b022e3b
-                    ctx->data_sent = 1;
b022e3b
-                }
b022e3b
-                eblock = APR_BLOCK_READ;
b022e3b
-                continue;
b022e3b
+                /* Ensure the last bucket to pass down is a flush if
b022e3b
+                 * the next read will block. */
b022e3b
+                flush = apr_bucket_flush_create(f->c->bucket_alloc);
b022e3b
+                APR_BUCKET_INSERT_BEFORE(e, flush);
b022e3b
             }
b022e3b
-            else {
b022e3b
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00574)
b022e3b
-                              "ap_content_length_filter: "
b022e3b
-                              "apr_bucket_read() failed");
b022e3b
-                return rv;
b022e3b
-            }
b022e3b
         }
b022e3b
-        else {
b022e3b
-            r->bytes_sent += e->length;
b022e3b
+
b022e3b
+        /* Optimization: if the next bucket is EOS (directly after a
b022e3b
+         * bucket morphed to the heap, or a flush), short-cut to
b022e3b
+         * handle EOS straight away - allowing C-L to be determined
b022e3b
+         * for content which is already entirely in memory. */
b022e3b
+        if (e != APR_BRIGADE_SENTINEL(b) && APR_BUCKET_IS_EOS(e)) {
b022e3b
+            continue;
b022e3b
         }
b022e3b
-        e = APR_BUCKET_NEXT(e);
b022e3b
+
b022e3b
+        /* On reaching here, pass on everything in the brigade up to
b022e3b
+         * this point. */
b022e3b
+        apr_brigade_split_ex(b, e, ctx->tmpbb);
b022e3b
+        
b022e3b
+        rv = ap_pass_brigade(f->next, b);
b022e3b
+        if (rv != APR_SUCCESS) {
b022e3b
+            return rv;
b022e3b
+        }
b022e3b
+        else if (f->c->aborted) {
b022e3b
+            return APR_ECONNABORTED;
b022e3b
+        }
b022e3b
+        apr_brigade_cleanup(b);
b022e3b
+        APR_BRIGADE_CONCAT(b, ctx->tmpbb);
b022e3b
+        e = APR_BRIGADE_FIRST(b);
b022e3b
+        
b022e3b
+        ctx->data_sent = 1;
b022e3b
     }
b022e3b
 
b022e3b
     /* If we've now seen the entire response and it's otherwise