Brotli: fix handling of upstream read timeout.

Output filters receive only special buffer with "flush" flag set
in case when upstream sent headers, but timed out before sending
request body.

Previously, this case wasn't handled correctly, because of the wrong
assumption that Brotli compressor always produces output when called
with "flush" parameter, which isn't the case if there is no input to
compress.

Reported by Tomas Kvasnicka on GitHub (issue #20).

Change-Id: I96dacc625c9c2509276800d7ac80a2d622a95463
Signed-off-by: Piotr Sikora <piotrsikora@google.com>
Reviewed-on: https://nginx-review.googlesource.com/1110
Reviewed-by: Martin Maly <mmaly@google.com>
diff --git a/src/ngx_http_brotli_filter_module.cc b/src/ngx_http_brotli_filter_module.cc
index 43d0a2b..02280a1 100644
--- a/src/ngx_http_brotli_filter_module.cc
+++ b/src/ngx_http_brotli_filter_module.cc
@@ -69,6 +69,7 @@
     ngx_http_brotli_ctx_t *ctx);
 static ngx_int_t ngx_http_brotli_filter_get_buf(ngx_http_request_t *r,
     ngx_http_brotli_ctx_t *ctx);
+static void ngx_http_brotli_cleanup(void *data);
 
 static ngx_int_t ngx_http_brotli_ok(ngx_http_request_t *r);
 static ngx_int_t ngx_http_brotli_accept_encoding(ngx_str_t *ae);
@@ -248,6 +249,7 @@
     int                     rc;
     ngx_uint_t              flush;
     ngx_chain_t            *cl;
+    ngx_pool_cleanup_t     *cln;
     ngx_http_brotli_ctx_t  *ctx;
 
     ctx = reinterpret_cast<ngx_http_brotli_ctx_t *>(
@@ -272,6 +274,14 @@
                        "brotli compressor: lvl:%d win:%d blk:%uz",
                        ctx->params.quality, (1 << ctx->params.lgwin),
                        ctx->brotli_ring);
+
+        cln = ngx_pool_cleanup_add(r->pool, 0);
+        if (cln == NULL) {
+            goto failed;
+        }
+
+        cln->handler = ngx_http_brotli_cleanup;
+        cln->data = ctx;
     }
 
     if (in) {
@@ -486,6 +496,8 @@
                    "brotli process: size:%uz l:%d f:%d",
                    ctx->brotli_in, ctx->last, ctx->flush);
 
+    out = NULL;
+
     if (!ctx->compressor->WriteBrotliData(ctx->last, ctx->flush, &size, &out)) {
         ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                       "brotli failed: size:%uz l:%d f:%d",
@@ -498,7 +510,7 @@
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "brotli data: %p, size:%uz", out, size);
 
-    if (size == 0) {
+    if (size == 0 && !ctx->flush && !ctx->last) {
         return NGX_AGAIN;
     }
 
@@ -517,13 +529,7 @@
     size_t        size;
     ngx_chain_t  *cl;
 
-    if (ctx->brotli_out == NULL) {
-        return NGX_AGAIN;
-    }
-
-#if (NGX_SUPPRESS_WARN)
     cl = NULL;
-#endif
 
     while (ctx->brotli_out < ctx->brotli_last) {
 
@@ -567,11 +573,17 @@
 
         if (ctx->last) {
             ctx->done = 1;
-            cl->buf->last_buf = 1;
+
+            if (cl) {
+                cl->buf->last_buf = 1;
+            }
 
         } else if (ctx->flush) {
             ctx->flush = 0;
-            cl->buf->flush = 1;
+
+            if (cl) {
+                cl->buf->flush = 1;
+            }
         }
 
         r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
@@ -617,6 +629,20 @@
 }
 
 
+static void
+ngx_http_brotli_cleanup(void *data)
+{
+    ngx_http_brotli_ctx_t  *ctx;
+
+    ctx = reinterpret_cast<ngx_http_brotli_ctx_t *>(data);
+
+    if (ctx->compressor) {
+        delete ctx->compressor;
+        ctx->compressor = NULL;
+    }
+}
+
+
 static ngx_int_t
 ngx_http_brotli_ok(ngx_http_request_t *r)
 {