On the ocsp-verification branch: sync with trunk up to r1828523.

git-svn-id: https://svn.apache.org/repos/asf/serf/branches/ocsp-verification@1828524 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/SConstruct b/SConstruct
index 967802a..68fda78 100644
--- a/SConstruct
+++ b/SConstruct
@@ -8,9 +8,9 @@
 #    to you under the Apache License, Version 2.0 (the
 #    "License"); you may not use this file except in compliance
 #    with the License.  You may obtain a copy of the License at
-# 
+#
 #      http://www.apache.org/licenses/LICENSE-2.0
-# 
+#
 #    Unless required by applicable law or agreed to in writing,
 #    software distributed under the License is distributed on an
 #    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -102,6 +102,10 @@
                "Path to GSSAPI's install area",
                None,
                None),
+  PathVariable('BROTLI',
+               "Path to Brotli's install area",
+               None,
+               PathVariable.PathIsDir),
   BoolVariable('DEBUG',
                "Enable debugging info and strict compile warnings",
                False),
@@ -119,7 +123,7 @@
   RawListVariable('LINKFLAGS', "Extra flags for the linker (space-separated)",
                   None),
   RawListVariable('CPPFLAGS', "Extra flags for the C preprocessor "
-                  "(space separated)", None), 
+                  "(space separated)", None),
   )
 
 if sys.platform == 'win32':
@@ -143,14 +147,15 @@
     EnumVariable('MSVC_VERSION',
                  "Visual C++ to use for building",
                  None,
-                 allowed_values=('15.0', '14.0', '12.0',
+                 allowed_values=('15.0', '14.1', '14.0', '12.0',
                                  '11.0', '10.0', '9.0', '8.0', '6.0'),
                  map={'2005' :  '8.0',
                       '2008' :  '9.0',
                       '2010' : '10.0',
                       '2012' : '11.0',
                       '2013' : '12.0',
-                      '2015' : '14.0'
+                      '2015' : '14.0',
+                      '2017' : '14.1',
                      }),
 
     # We always documented that we handle an install layout, but in fact we
@@ -169,15 +174,15 @@
 gen_def_script = env.File('build/gen_def.py').rstr()
 
 env.Append(BUILDERS = {
-    'GenDef' : 
-      Builder(action = sys.executable + ' %s $SOURCES > $TARGET' % (gen_def_script,),
+    'GenDef' :
+      Builder(action = '"%s" "%s" $SOURCES > $TARGET' % (sys.executable, gen_def_script,),
               suffix='.def', src_suffix='.h')
   })
 
 match = re.search('SERF_MAJOR_VERSION ([0-9]+).*'
                   'SERF_MINOR_VERSION ([0-9]+).*'
                   'SERF_PATCH_VERSION ([0-9]+)',
-                  env.File('serf.h').get_contents(),
+                  env.File('serf.h').get_contents().decode('utf-8'),
                   re.DOTALL)
 MAJOR, MINOR, PATCH = [int(x) for x in match.groups()]
 env.Append(MAJOR=str(MAJOR))
@@ -194,12 +199,13 @@
 
 unknown = opts.UnknownVariables()
 if unknown:
-  print 'Warning: Used unknown variables:', ', '.join(unknown.keys())
+  print('Warning: Used unknown variables:', ', '.join(unknown.keys()))
 
 apr = str(env['APR'])
 apu = str(env['APU'])
 zlib = str(env['ZLIB'])
 gssapi = env.get('GSSAPI', None)
+brotli = env.get('BROTLI', None)
 
 if gssapi and os.path.isdir(gssapi):
   krb5_config = os.path.join(gssapi, 'bin', 'krb5-config')
@@ -278,6 +284,10 @@
   if sys.platform == 'sunos5':
     env.Append(LIBS=['m'])
     env.Append(PLATFORM='posix')
+
+  if brotli:
+    env.Append(LIBS=['brotlicommon', 'brotlidec'])
+
 else:
   # Warning level 4, no unused argument warnings
   env.Append(CCFLAGS=['/W4',
@@ -356,13 +366,34 @@
                LIBPATH=['$ZLIB'])
 
   # openssl
-  env.Append(LIBS=['libeay32.lib', 'ssleay32.lib'])
   if not env.get('SOURCE_LAYOUT', None):
     env.Append(CPPPATH=['$OPENSSL/include/openssl'],
                LIBPATH=['$OPENSSL/lib'])
   else:
     env.Append(CPPPATH=['$OPENSSL/inc32'],
                LIBPATH=['$OPENSSL/out32dll'])
+  conf = Configure(env)
+  if conf.CheckLib('libcrypto'):
+    # OpenSSL 1.1.0+
+    env.Append(LIBS=['libcrypto.lib', 'libssl.lib'])
+  else:
+    # Legacy OpenSSL
+    env.Append(LIBS=['libeay32.lib', 'ssleay32.lib'])
+  conf.Finish()
+
+  # brotli
+  if brotli:
+    brotli_libs = 'brotlicommon.lib brotlidec.lib'
+    env.Append(LIBS=['brotlicommon.lib', 'brotlidec.lib'])
+    if not env.get('SOURCE_LAYOUT', None):
+      env.Append(CPPPATH=['$BROTLI/include'],
+                 LIBPATH=['$BROTLI/lib'])
+    else:
+      env.Append(CPPPATH=['$BROTLI/include'],
+                 LIBPATH=['$BROTLI/Release'])
+  else:
+    brotli_libs = ''
+
 else:
   if CALLOUT_OKAY:
     if os.path.isdir(apr):
@@ -415,6 +446,13 @@
     env.Append(CPPPATH=['$OPENSSL/include'])
     env.Append(LIBPATH=['$OPENSSL/lib'])
 
+  if brotli:
+    brotli_libs = '-lbrotlicommon -lbrotlienc'
+    env.Append(CPPPATH=['$BROTLI/include'],
+               LIBPATH=['$BROTLI/lib'])
+  else:
+    brotli_libs = ''
+
 # Check for OpenSSL functions which are only available in some of
 # the versions we support. Also handles forks like LibreSSL.
 conf = Configure(env)
@@ -424,10 +462,12 @@
   env.Append(CPPDEFINES=['SERF_NO_SSL_X509_STORE_WRAPPERS'])
 if conf.CheckFunc('CRYPTO_set_locking_callback'):
   env.Append(CPPDEFINES=['SERF_HAVE_SSL_LOCKING_CALLBACKS'])
-if conf.CheckFunc('OPENSSL_malloc_init'):
+if conf.CheckFunc('OPENSSL_malloc_init', '#include <openssl/crypto.h>'):
   env.Append(CPPDEFINES=['SERF_HAVE_OPENSSL_MALLOC_INIT'])
 if conf.CheckFunc('SSL_set_alpn_protos'):
   env.Append(CPPDEFINES=['SERF_HAVE_OPENSSL_ALPN'])
+if conf.CheckType('OSSL_HANDSHAKE_STATE', '#include <openssl/ssl.h>'):
+  env.Append(CPPDEFINES=['SERF_HAVE_OSSL_HANDSHAKE_STATE'])
 env = conf.Finish()
 
 # If build with gssapi, get its information and define SERF_HAVE_GSSAPI
@@ -441,6 +481,16 @@
 if sys.platform == 'win32':
   env.Append(CPPDEFINES=['SERF_HAVE_SSPI'])
 
+if brotli and CALLOUT_OKAY:
+  conf = Configure(env)
+  if conf.CheckCHeader('brotli/decode.h') and \
+     conf.CheckFunc('BrotliDecoderTakeOutput'):
+    env.Append(CPPDEFINES=['SERF_HAVE_BROTLI'])
+  else:
+    print("Cannot find Brotli library >= 1.0.0 in '%s'." % env.get('BROTLI'))
+    Exit(1)
+  env = conf.Finish()
+
 # Set preprocessor define to disable the logging framework
 if disablelogging:
     env.Append(CPPDEFINES=['SERF_DISABLE_LOGGING'])
@@ -456,12 +506,13 @@
                          env.File('build/serf.pc.in'),
                          SUBST_DICT = {
                            '@MAJOR@': str(MAJOR),
-                           '@PREFIX@': '$PREFIX',
-                           '@LIBDIR@': '$LIBDIR',
+                           '@PREFIX@': re.escape(str(env['PREFIX'])),
+                           '@LIBDIR@': re.escape(str(env['LIBDIR'])),
                            '@INCLUDE_SUBDIR@': 'serf-%d' % (MAJOR,),
                            '@VERSION@': '%d.%d.%d' % (MAJOR, MINOR, PATCH),
-                           '@LIBS@': '%s %s %s -lz' % (apu_libs, apr_libs,
-                                                       env.get('GSSAPI_LIBS', '')),
+                           '@LIBS@': '%s %s %s %s -lz' % (apu_libs, apr_libs,
+                                                          env.get('GSSAPI_LIBS', ''),
+                                                          brotli_libs),
                            })
 
 env.Default(lib_static, lib_shared, pkgconfig)
diff --git a/buckets/brotli_buckets.c b/buckets/brotli_buckets.c
new file mode 100644
index 0000000..0677911
--- /dev/null
+++ b/buckets/brotli_buckets.c
@@ -0,0 +1,235 @@
+/* ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ */
+
+#include "serf.h"
+#include "serf_bucket_util.h"
+#include "serf_private.h"
+
+#ifdef SERF_HAVE_BROTLI
+
+#include <brotli/decode.h>
+
+int serf_bucket_is_brotli_supported(void)
+{
+    return TRUE;
+}
+
+typedef struct brotli_decompress_context_t {
+    BrotliDecoderState *state;
+    serf_bucket_t *input;
+    serf_bucket_t *output;
+    const char *pending_data;
+    apr_size_t pending_len;
+    /* Did we see an APR_EOF for the input stream? */
+    int hit_eof;
+    /* Did the decoder report the end of the compressed data? */
+    int done;
+} brotli_decompress_context_t;
+
+static void *alloc_func(void *opaque, size_t size)
+{
+    serf_bucket_alloc_t *alloc = opaque;
+
+    return serf_bucket_mem_alloc(alloc, size);
+}
+
+static void free_func(void *opaque, void *block)
+{
+    serf_bucket_alloc_t *alloc = opaque;
+
+    if (block)
+        serf_bucket_mem_free(alloc, block);
+}
+
+/* Implements serf_bucket_aggregate_eof_t */
+static apr_status_t refill_output(void *baton, serf_bucket_t *aggregate_bkt)
+{
+    brotli_decompress_context_t *ctx = baton;
+
+    while (1) {
+        if (ctx->pending_len == 0 && !ctx->hit_eof) {
+            apr_status_t status;
+
+            status = serf_bucket_read(ctx->input, SERF_READ_ALL_AVAIL,
+                                      &ctx->pending_data, &ctx->pending_len);
+            if (APR_STATUS_IS_EOF(status))
+                ctx->hit_eof = TRUE;
+            else if (status)
+                return status;
+        }
+
+        if (ctx->done && ctx->hit_eof && ctx->pending_len == 0) {
+            return APR_EOF;
+        }
+        else if (ctx->done) {
+            /* Finished with some input still there in the bucket, that's
+             * an error. */
+            return SERF_ERROR_DECOMPRESSION_FAILED;
+        }
+        else {
+            BrotliDecoderResult result;
+            apr_size_t avail_out = 0;
+
+            result = BrotliDecoderDecompressStream(
+                         ctx->state, &ctx->pending_len,
+                         (const uint8_t **)&ctx->pending_data, &avail_out,
+                         NULL, NULL);
+
+            if (result == BROTLI_DECODER_RESULT_ERROR) {
+                return SERF_ERROR_DECOMPRESSION_FAILED;
+            }
+            else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
+                     && ctx->hit_eof) {
+                /* The decoder says it requires more data, but we don't have
+                 * it.  This could happen either if the input is truncated or
+                 * corrupted, but as we don't know for sure, return a generic
+                 * error. */
+                return SERF_ERROR_DECOMPRESSION_FAILED;
+            }
+            else if (result == BROTLI_DECODER_RESULT_SUCCESS
+                     && !BrotliDecoderHasMoreOutput(ctx->state)) {
+                ctx->done = TRUE;
+            }
+
+            if (BrotliDecoderHasMoreOutput(ctx->state)) {
+                serf_bucket_t *output_bkt;
+                const uint8_t *output;
+                apr_size_t output_len = 0;
+
+                /* There is some output for us.  Place it into the aggregate
+                 * bucket, and avoid making a copy by wrapping a pointer to
+                 * the internal output buffer.  This data is valid until the
+                 * next call to BrotliDecoderDecompressStream(), which won't
+                 * happen until this bucket is read. */
+                output = BrotliDecoderTakeOutput(ctx->state, &output_len);
+                output_bkt = serf_bucket_simple_create((const char *)output,
+                                                       output_len, NULL, NULL,
+                                                       aggregate_bkt->allocator);
+                serf_bucket_aggregate_append(aggregate_bkt, output_bkt);
+
+                return APR_SUCCESS;
+            }
+        }
+    }
+}
+
+serf_bucket_t *
+serf_bucket_brotli_decompress_create(serf_bucket_t *stream,
+                                     serf_bucket_alloc_t *alloc)
+{
+    brotli_decompress_context_t *ctx =
+        serf_bucket_mem_calloc(alloc, sizeof(*ctx));
+
+    ctx->state = BrotliDecoderCreateInstance(alloc_func, free_func, alloc);
+    ctx->input = stream;
+    ctx->output = serf_bucket_aggregate_create(alloc);
+    ctx->pending_data = NULL;
+    ctx->pending_len = 0;
+    ctx->hit_eof = FALSE;
+    ctx->done = FALSE;
+
+    serf_bucket_aggregate_hold_open(ctx->output, refill_output, ctx);
+
+    return serf_bucket_create(&serf_bucket_type_brotli_decompress, alloc, ctx);
+}
+
+static apr_status_t serf_brotli_decompress_read(serf_bucket_t *bucket,
+                                                apr_size_t requested,
+                                                const char **data,
+                                                apr_size_t *len)
+{
+    brotli_decompress_context_t *ctx = bucket->data;
+
+    return serf_bucket_read(ctx->output, requested, data, len);
+}
+
+static apr_status_t serf_brotli_decompress_readline(serf_bucket_t *bucket,
+                                                    int acceptable, int *found,
+                                                    const char **data,
+                                                    apr_size_t *len)
+{
+    brotli_decompress_context_t *ctx = bucket->data;
+
+    return serf_bucket_readline(ctx->output, acceptable, found, data, len);
+}
+
+static apr_status_t serf_brotli_decompress_peek(serf_bucket_t *bucket,
+                                                const char **data,
+                                                apr_size_t *len)
+{
+    brotli_decompress_context_t *ctx = bucket->data;
+
+    return serf_bucket_peek(ctx->output, data, len);
+}
+
+static void serf_brotli_decompress_destroy_and_data(serf_bucket_t *bucket)
+{
+    brotli_decompress_context_t *ctx = bucket->data;
+
+    BrotliDecoderDestroyInstance(ctx->state);
+    serf_bucket_destroy(ctx->input);
+    serf_bucket_destroy(ctx->output);
+    serf_default_destroy_and_data(bucket);
+}
+
+static apr_status_t serf_brotli_decompress_set_config(serf_bucket_t *bucket,
+                                                      serf_config_t *config)
+{
+    brotli_decompress_context_t *ctx = bucket->data;
+    apr_status_t status;
+
+    status = serf_bucket_set_config(ctx->input, config);
+    if (status)
+        return status;
+
+    return serf_bucket_set_config(ctx->output, config);
+}
+
+const serf_bucket_type_t serf_bucket_type_brotli_decompress = {
+    "BROTLI-DECOMPRESS",
+    serf_brotli_decompress_read,
+    serf_brotli_decompress_readline,
+    serf_default_read_iovec,
+    serf_default_read_for_sendfile,
+    serf_buckets_are_v2,
+    serf_brotli_decompress_peek,
+    serf_brotli_decompress_destroy_and_data,
+    serf_default_read_bucket,
+    serf_default_get_remaining,
+    serf_brotli_decompress_set_config,
+};
+
+#else /* SERF_HAVE_BROTLI */
+
+int serf_bucket_is_brotli_supported(void)
+{
+    return FALSE;
+}
+
+serf_bucket_t *
+serf_bucket_brotli_decompress_create(serf_bucket_t *stream,
+                                     serf_bucket_alloc_t *alloc)
+{
+    return NULL;
+}
+
+const serf_bucket_type_t serf_bucket_type_brotli_decompress = { 0 };
+
+#endif /* SERF_HAVE_BROTLI */
diff --git a/buckets/dechunk_buckets.c b/buckets/dechunk_buckets.c
index d242883..cea1974 100644
--- a/buckets/dechunk_buckets.c
+++ b/buckets/dechunk_buckets.c
@@ -82,12 +82,17 @@
 
             /* if a line was read, then parse it. */
             if (ctx->linebuf.state == SERF_LINEBUF_READY) {
+                char *end;
 
                 /* Convert from HEX digits. The linebuffer ensures a '\0' */
-                ctx->body_left = apr_strtoi64(ctx->linebuf.line, NULL, 16);
+                ctx->body_left = apr_strtoi64(ctx->linebuf.line, &end, 16);
                 if (errno == ERANGE) {
                     return APR_FROM_OS_ERROR(ERANGE);
                 }
+                else if (ctx->linebuf.line == end) {
+                    /* Invalid chunk length, bail out. */
+                    return SERF_ERROR_BAD_HTTP_RESPONSE;
+                }
 
                 if (ctx->body_left == 0) {
                     /* Just read the last-chunk marker. We're DONE. */
diff --git a/buckets/deflate_buckets.c b/buckets/deflate_buckets.c
index eefb158..eeba701 100644
--- a/buckets/deflate_buckets.c
+++ b/buckets/deflate_buckets.c
@@ -277,14 +277,24 @@
 
     while (1) {
 
-        if (ctx->memLevel < 0)
+        if (ctx->memLevel < 0) {
             zRC = inflate(&ctx->zstream, flush_v);
-        else
+            if (zRC == Z_BUF_ERROR && APR_STATUS_IS_EOF(ctx->stream_status) &&
+                ctx->zstream.avail_out > 0) {
+                /* Zlib can't continue, although there's still space in the
+                   output buffer.  This can happen either if the stream is
+                   truncated or corrupted.  As we don't know for sure,
+                   return a generic error. */
+                return SERF_ERROR_DECOMPRESSION_FAILED;
+            }
+        }
+        else {
             zRC = deflate(&ctx->zstream, flush_v);
+        }
 
-        /* We're full or zlib requires more space. Either case, clear
-            out our buffer, reset, and return. */
         if (zRC == Z_BUF_ERROR || ctx->zstream.avail_out == 0) {
+            /* We're full or zlib requires more space. Either case, clear
+               out our buffer, reset, and return. */
             apr_size_t private_len;
             serf_bucket_t *tmp;
 
diff --git a/buckets/response_buckets.c b/buckets/response_buckets.c
index 0e3ee9b..d5cae92 100644
--- a/buckets/response_buckets.c
+++ b/buckets/response_buckets.c
@@ -459,6 +459,14 @@
                                                    SERF_DEFLATE_DEFLATE);
                     serf_bucket_set_config(ctx->body, ctx->config);
                 }
+                else if (serf_bucket_is_brotli_supported()
+                         && v && strcasecmp("br", v) == 0)
+                {
+                    ctx->body =
+                        serf_bucket_brotli_decompress_create(ctx->body,
+                                                             bkt->allocator);
+                    serf_bucket_set_config(ctx->body, ctx->config);
+                }
             }
         }
         break;
diff --git a/buckets/ssl_buckets.c b/buckets/ssl_buckets.c
index 98234a3..2a3b586 100644
--- a/buckets/ssl_buckets.c
+++ b/buckets/ssl_buckets.c
@@ -299,12 +299,13 @@
 #endif
 
     /* The server asked to renegotiate the SSL session. */
-#ifdef TLS_ST_SW_HELLO_REQ
+#ifdef SERF_HAVE_OSSL_HANDSHAKE_STATE
     if (SSL_get_state(s) == TLS_ST_SW_HELLO_REQ) {
 #elif defined(SSL_ST_RENEGOTIATE)
     if (SSL_state(s) == SSL_ST_RENEGOTIATE) {
 #else
 #error "neither TLS_ST_SW_HELLO_REQ nor SSL_ST_RENEGOTIATE is available"
+    {
 #endif
         serf_ssl_context_t *ssl_ctx = SSL_get_app_data(s);
 
@@ -1154,7 +1155,7 @@
         /* Once we got through the initial handshake, we should have received
            the ALPN information if there is such information. */
         ctx->handshake_finished = SSL_is_init_finished(ctx->ssl)
-#ifdef TLS_ST_OK
+#ifdef SERF_HAVE_OSSL_HANDSHAKE_STATE
                                   || (SSL_get_state(ctx->ssl) == TLS_ST_OK);
 #elif defined(SSL_CB_HANDSHAKE_DONE)
                                   || (SSL_state(ctx->ssl)
diff --git a/dist.sh b/dist.sh
index 4f153ff..51e7491 100755
--- a/dist.sh
+++ b/dist.sh
@@ -87,9 +87,10 @@
 
 cd ${work}
 
-# allow md5sum and sha1sum tool names to be overridden
+# allow checksum tool names to be overridden
 [ -n "$MD5SUM" ] || MD5SUM=md5sum
 [ -n "$SHA1SUM" ] || SHA1SUM=sha1sum
+[ -n "$SHA256SUM" ] || SHA256SUM=sha1sum
 
 echo ""
 echo "Done:"
@@ -130,3 +131,6 @@
 echo "sha1sums:"
 $SHA1SUM "${release}.tar.bz2" "${release}.zip"
 echo ""
+echo "sha256sums:"
+$SHA256SUM "${release}.tar.bz2" "${release}.zip"
+echo ""
diff --git a/protocols/http2_protocol.c b/protocols/http2_protocol.c
index 6d1bec5..cbc86d0 100644
--- a/protocols/http2_protocol.c
+++ b/protocols/http2_protocol.c
@@ -824,7 +824,14 @@
               /* Sanitize? */
                 serf__log(LOGLVL_INFO, SERF_LOGHTTP2, h2->config,
                           "Setting Initial Window Size %u\n", value);
-                h2->lr_window += (value - h2->lr_default_window);
+                /* This only affects the default window size for new streams
+                   (the connection window size is left unchanged):
+
+                   Both endpoints can adjust the initial window size for new
+                   streams by including a value for SETTINGS_INITIAL_WINDOW_SIZE
+                   in the SETTINGS frame that forms part of the connection
+                   preface.  The connection flow-control window can only be
+                   changed using WINDOW_UPDATE frames. */
                 h2->lr_default_window = value;
                 break;
             case HTTP2_SETTING_MAX_FRAME_SIZE:
diff --git a/serf.h b/serf.h
index 70e59ef..164632f 100644
--- a/serf.h
+++ b/serf.h
@@ -1702,7 +1702,9 @@
 /** @} */
 
 
-/* Internal functions for bucket use and lifecycle tracking */
+/* Internal functions for bucket use and lifecycle tracking.
+   ### Some of these are directly or via Macros used by third party
+   ### applications, such as Apache Subversion */
 apr_status_t serf_debug__record_read(
     const serf_bucket_t *bucket,
     apr_status_t status);
diff --git a/serf_bucket_types.h b/serf_bucket_types.h
index ca663d8..ffa3fa7 100644
--- a/serf_bucket_types.h
+++ b/serf_bucket_types.h
@@ -1029,6 +1029,28 @@
                               apr_size_t max_chunk_size);
 
 
+/**
+ * Check if Serf bucket functions support Brotli (RFC 7932) format.
+ * Return non-zero if Brotli is supported and zero otherwise.  If Brotli
+ * is not supported, the behavior of all related bucket functions such
+ * as @a serf_bucket_brotli_decompress_create is undefined.
+ *
+ * @since New in 1.4.
+ */
+int serf_bucket_is_brotli_supported(void);
+
+/** @since New in 1.4. */
+extern const serf_bucket_type_t serf_bucket_type_brotli_decompress;
+/** @since New in 1.4. */
+#define SERF_BUCKET_IS_BROTLI_DECOMPRESS(b) \
+    SERF_BUCKET_CHECK((b), brotli_decompress)
+
+/** @since New in 1.4. */
+serf_bucket_t *
+serf_bucket_brotli_decompress_create(serf_bucket_t *stream,
+                                     serf_bucket_alloc_t *alloc);
+
+
 /* ### do we need a PIPE bucket type? they are simple apr_file_t objects */
 
 
diff --git a/src/pump.c b/src/pump.c
index aa08bd2..b1446db 100644
--- a/src/pump.c
+++ b/src/pump.c
@@ -35,7 +35,7 @@
 
     if (pump->ostream_head != NULL) {
 #ifdef SERF_DEBUG_BUCKET_USE
-        serf__bucket_drain(conn->ostream_head);
+        serf__bucket_drain(pump->ostream_head);
 #endif
         serf_bucket_destroy(pump->ostream_head);
         pump->ostream_head = NULL;
diff --git a/test/MockHTTPinC/MockHTTP_server.c b/test/MockHTTPinC/MockHTTP_server.c
index 848ca34..61fff2d 100644
--- a/test/MockHTTPinC/MockHTTP_server.c
+++ b/test/MockHTTPinC/MockHTTP_server.c
@@ -2239,10 +2239,6 @@
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 
-#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L
-#define USE_LEGACY_OPENSSL
-#endif
-
 struct sslCtx_t {
     bool handshake_done;
     bool renegotiate;
@@ -2278,7 +2274,7 @@
  */
 static int bio_apr_socket_create(BIO *bio)
 {
-#ifndef USE_LEGACY_OPENSSL
+#ifndef SERF_NO_SSL_BIO_WRAPPERS
     BIO_set_shutdown(bio, 1);
     BIO_set_init(bio, 1);
     BIO_set_data(bio, NULL);
@@ -2294,7 +2290,7 @@
 
 static void bio_set_data(BIO *bio, void *data)
 {
-#ifndef USE_LEGACY_OPENSSL
+#ifndef SERF_NO_SSL_BIO_WRAPPERS
     BIO_set_data(bio, data);
 #else
     bio->ptr = data;
@@ -2303,7 +2299,7 @@
 
 static void *bio_get_data(BIO *bio)
 {
-#ifndef USE_LEGACY_OPENSSL
+#ifndef SERF_NO_SSL_BIO_WRAPPERS
     return BIO_get_data(bio);
 #else
     return bio->ptr;
@@ -2404,7 +2400,7 @@
 }
 
 
-#ifdef USE_LEGACY_OPENSSL
+#ifdef SERF_NO_SSL_BIO_WRAPPERS
 static BIO_METHOD bio_apr_socket_method = {
     BIO_TYPE_SOCKET,
     "APR sockets",
@@ -2425,7 +2421,7 @@
 {
     BIO_METHOD *biom = NULL;
 
-#ifndef USE_LEGACY_OPENSSL
+#ifndef SERF_NO_SSL_BIO_WRAPPERS
     biom = BIO_meth_new(BIO_TYPE_SOCKET, "APR sockets");
     if (biom) {
         BIO_meth_set_write(biom, bio_apr_socket_write);
@@ -2443,7 +2439,7 @@
 
 static void bio_meth_free(BIO_METHOD *biom)
 {
-#ifndef USE_LEGACY_OPENSSL
+#ifndef SERF_NO_SSL_BIO_WRAPPERS
     BIO_meth_free(biom);
 #endif
 }
@@ -2672,7 +2668,7 @@
     /* Init OpenSSL globally */
     if (!init_done)
     {
-#ifndef USE_LEGACY_OPENSSL
+#ifdef SERF_HAVE_OPENSSL_MALLOC_INIT
         OPENSSL_malloc_init();
 #else
         CRYPTO_malloc_init();
@@ -2755,8 +2751,8 @@
                 X509 *ssl_cert = PEM_read_X509(fp, NULL, NULL, NULL);
                 fclose(fp);
 
-                SSL_CTX_add_extra_chain_cert(ssl_ctx->ctx, ssl_cert);
                 X509_STORE_add_cert(store, ssl_cert);
+                SSL_CTX_add_extra_chain_cert(ssl_ctx->ctx, ssl_cert);
             }
         }
 
diff --git a/test/serf_bwtp.c b/test/serf_bwtp.c
index da3d411..348281a 100644
--- a/test/serf_bwtp.c
+++ b/test/serf_bwtp.c
@@ -191,7 +191,10 @@
     serf_bucket_headers_setn(hdrs_bkt, "User-Agent",
                              "Serf/" SERF_VERSION_STRING);
     /* Shouldn't serf do this for us? */
-    serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
+    if (serf_bucket_is_brotli_supported())
+        serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip, br");
+    else
+        serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
 
     if (ctx->authn != NULL) {
         serf_bucket_headers_setn(hdrs_bkt, "Authorization", ctx->authn);
@@ -255,7 +258,10 @@
     serf_bucket_headers_setn(hdrs_bkt, "User-Agent",
                              "Serf/" SERF_VERSION_STRING);
     /* Shouldn't serf do this for us? */
-    serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
+    if (serf_bucket_is_brotli_supported())
+        serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip, br");
+    else
+        serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
 
     if (ctx->authn != NULL) {
         serf_bucket_headers_setn(hdrs_bkt, "Authorization", ctx->authn);
diff --git a/test/serf_get.c b/test/serf_get.c
index ab5ec07..7c9c49c 100644
--- a/test/serf_get.c
+++ b/test/serf_get.c
@@ -34,7 +34,7 @@
 /* #define CONNECTION_CLOSE_HDR */
 
 typedef struct app_baton_t {
-    const char *hostinfo;
+    const char *hostname;
     int using_ssl;
     int head_request;
     int negotiate_http2;
@@ -223,7 +223,7 @@
         serf_ssl_server_cert_chain_callback_set(conn_ctx->ssl_ctx, 
                                                 ignore_all_cert_errors, 
                                                 print_certs, NULL);
-        serf_ssl_set_hostname(conn_ctx->ssl_ctx, ctx->hostinfo);
+        serf_ssl_set_hostname(conn_ctx->ssl_ctx, ctx->hostname);
 
         *output_bkt = serf_bucket_ssl_encrypt_create(*output_bkt,
                                                      conn_ctx->ssl_ctx,
@@ -372,7 +372,10 @@
     serf_bucket_headers_setn(hdrs_bkt, "User-Agent",
                              "Serf/" SERF_VERSION_STRING);
     /* Shouldn't serf do this for us? */
-    serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
+    if (serf_bucket_is_brotli_supported())
+        serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip, br");
+    else
+        serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
 #ifdef CONNECTION_CLOSE_HDR
     serf_bucket_headers_setn(hdrs_bkt, "Connection", "close");
 #endif
@@ -725,7 +728,7 @@
         app_ctx.head_request = 0;
     }
 
-    app_ctx.hostinfo = url.hostinfo;
+    app_ctx.hostname = url.hostname;
     app_ctx.pem_path = pem_path;
     app_ctx.pem_pwd = pem_pwd;
 
diff --git a/test/serf_spider.c b/test/serf_spider.c
index d238100..725325b 100644
--- a/test/serf_spider.c
+++ b/test/serf_spider.c
@@ -323,7 +323,10 @@
                              "Serf/" SERF_VERSION_STRING);
 
     /* Shouldn't serf do this for us? */
-    serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
+    if (serf_bucket_is_brotli_supported())
+        serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip, br");
+    else
+        serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
 
     if (ctx->app_ctx->authn != NULL) {
         serf_bucket_headers_setn(hdrs_bkt, "Authorization",
diff --git a/test/test_buckets.c b/test/test_buckets.c
index 4f73291..fe58200 100644
--- a/test/test_buckets.c
+++ b/test/test_buckets.c
@@ -1122,6 +1122,104 @@
     serf_bucket_destroy(bkt);
 }
 
+/* Test for issue: the server aborts the connection and also sends
+   a bogus CRLF in place of the expected chunk size. Test that we get
+   a decent error code from the response bucket instead of APR_EOF. */
+static void test_response_body_chunked_bogus_crlf(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_bucket_t *bkt, *tmp;
+    serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool);
+
+    tmp = SERF_BUCKET_SIMPLE_STRING("HTTP/1.1 200 OK" CRLF
+                                    "Content-Type: text/plain" CRLF
+                                    "Transfer-Encoding: chunked" CRLF
+                                    CRLF
+                                    "2" CRLF
+                                    "AB" CRLF
+                                    CRLF,
+                                    alloc);
+
+    bkt = serf_bucket_response_create(tmp, alloc);
+
+    {
+        char buf[1024];
+        apr_size_t len;
+        apr_status_t status;
+
+        status = read_all(bkt, buf, sizeof(buf), &len);
+
+        CuAssertIntEquals(tc, SERF_ERROR_BAD_HTTP_RESPONSE, status);
+    }
+
+    /* This will also destroy response stream bucket. */
+    serf_bucket_destroy(bkt);
+}
+
+static void test_response_body_chunked_invalid_len(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_bucket_t *bkt, *tmp;
+    serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool);
+
+    tmp = SERF_BUCKET_SIMPLE_STRING("HTTP/1.1 200 OK" CRLF
+                                    "Content-Type: text/plain" CRLF
+                                    "Transfer-Encoding: chunked" CRLF
+                                    CRLF
+                                    "2" CRLF
+                                    "AB" CRLF
+                                    "invalid" CRLF
+                                    CRLF,
+                                    alloc);
+
+    bkt = serf_bucket_response_create(tmp, alloc);
+
+    {
+        char buf[1024];
+        apr_size_t len;
+        apr_status_t status;
+
+        status = read_all(bkt, buf, sizeof(buf), &len);
+
+        CuAssertIntEquals(tc, SERF_ERROR_BAD_HTTP_RESPONSE, status);
+    }
+
+    /* This will also destroy response stream bucket. */
+    serf_bucket_destroy(bkt);
+}
+
+static void test_response_body_chunked_overflow_len(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_bucket_t *bkt, *tmp;
+    serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool);
+
+    tmp = SERF_BUCKET_SIMPLE_STRING("HTTP/1.1 200 OK" CRLF
+                                    "Content-Type: text/plain" CRLF
+                                    "Transfer-Encoding: chunked" CRLF
+                                    CRLF
+                                    "2" CRLF
+                                    "AB" CRLF
+                                    "12345678901234567890123456789" CRLF
+                                    CRLF,
+                                    alloc);
+
+    bkt = serf_bucket_response_create(tmp, alloc);
+
+    {
+        char buf[1024];
+        apr_size_t len;
+        apr_status_t status;
+
+        status = read_all(bkt, buf, sizeof(buf), &len);
+
+        CuAssertIntEquals(tc, APR_FROM_OS_ERROR(ERANGE), status);
+    }
+
+    /* This will also destroy response stream bucket. */
+    serf_bucket_destroy(bkt);
+}
+
 static void test_response_bucket_peek_at_headers(CuTest *tc)
 {
     test_baton_t *tb = tc->testBaton;
@@ -1298,7 +1396,7 @@
              CRLF, APR_SUCCESS },
         { 1, "6" CR, APR_SUCCESS },
         { 1, "", APR_EAGAIN },
-        { 1,  LF "blabla" CRLF CRLF, APR_SUCCESS }, };
+        { 1,  LF "blabla" CRLF "0" CRLF CRLF, APR_SUCCESS }, };
     apr_status_t status;
 
     const char *expected = "blabla";
@@ -2968,6 +3066,234 @@
   serf_bucket_destroy(frame_out);
 }
 
+static void test_brotli_decompress_bucket_basic(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_bucket_t *input;
+    serf_bucket_t *bkt;
+    serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool);
+
+    {
+        const char input_data[] = {
+            "\x3B"
+        };
+
+        input = serf_bucket_simple_create(input_data, sizeof(input_data) - 1,
+                                          NULL, NULL, alloc);
+        bkt = serf_bucket_brotli_decompress_create(input, alloc);
+        read_and_check_bucket(tc, bkt, "");
+        serf_bucket_destroy(bkt);
+    }
+
+    {
+        const char input_data[] = {
+            "\x8B\x03\x80\x61\x62\x63\x64\x65\x66\x67\x68\x03"
+        };
+
+        input = serf_bucket_simple_create(input_data, sizeof(input_data) - 1,
+                                          NULL, NULL, alloc);
+        bkt = serf_bucket_brotli_decompress_create(input, alloc);
+        read_and_check_bucket(tc, bkt, "abcdefgh");
+        serf_bucket_destroy(bkt);
+    }
+
+    {
+        const char input_data[] = {
+            "\x1B\x0E\x00\x00\x84\x71\xC0\xC6\xDA\x50\x22\x80\x88\x26"
+            "\x81\x14\x35\x1F"
+        };
+
+        input = serf_bucket_simple_create(input_data, sizeof(input_data) - 1,
+                                          NULL, NULL, alloc);
+        bkt = serf_bucket_brotli_decompress_create(input, alloc);
+        read_and_check_bucket(tc, bkt, "aaabbbcccdddeee");
+        serf_bucket_destroy(bkt);
+    }
+}
+
+static void test_brotli_decompress_bucket_truncated_input(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_bucket_t *input;
+    serf_bucket_t *bkt;
+    serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool);
+    const char input_data[] = {
+        "\x8B\x03\x80\x61\x62\x63\x64\x65\x66\x67\x68\x03"
+    };
+
+    /* Truncate our otherwise valid input. */
+    input = serf_bucket_simple_create(input_data, 5, NULL, NULL, alloc);
+    bkt = serf_bucket_brotli_decompress_create(input, alloc);
+
+    {
+        char buf[1024];
+        apr_size_t len;
+        apr_status_t status;
+
+        status = read_all(bkt, buf, sizeof(buf), &len);
+        CuAssertIntEquals(tc, SERF_ERROR_DECOMPRESSION_FAILED, status);
+    }
+
+    serf_bucket_destroy(bkt);
+}
+
+static void test_brotli_decompress_bucket_read_bytewise(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_bucket_t *input;
+    serf_bucket_t *bkt;
+    serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool);
+    apr_status_t status;
+    apr_size_t total_read = 0;
+    /* Brotli-encoded sequence of 100,000 zeroes. */
+    const char input_data[] = {
+        "\x5B\x9F\x86\x01\x40\x02\x26\x1E\x0B\x24\xCB\x2F\x00"
+    };
+
+    input = serf_bucket_simple_create(input_data, sizeof(input_data) - 1,
+                                      NULL, NULL, alloc);
+    bkt = serf_bucket_brotli_decompress_create(input, alloc);
+
+    do {
+        const char *data;
+        apr_size_t len;
+
+        status = serf_bucket_read(bkt, 1, &data, &len);
+        if (SERF_BUCKET_READ_ERROR(status))
+            CuFail(tc, "Got error during bucket reading.");
+
+        if (len > 1) {
+            CuFail(tc, "Unexpected read with len > 1.");
+        }
+        else if (len == 1) {
+            CuAssertIntEquals(tc, '0', data[0]);
+            total_read += len;
+        }
+    } while (status != APR_EOF);
+
+    CuAssertIntEquals(tc, 100000, (int)total_read);
+
+    serf_bucket_destroy(bkt);
+}
+
+static void test_brotli_decompress_bucket_chunked_input(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_bucket_t *input;
+    serf_bucket_t *bkt;
+    serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool);
+
+    /* What if the encoded data spans over multiple chunks?
+     * (And let's throw in a couple of empty chunks as well...) */
+    input = serf_bucket_aggregate_create(alloc);
+    serf_bucket_aggregate_append(input,
+        SERF_BUCKET_SIMPLE_STRING("\x8B\x03", alloc));
+    serf_bucket_aggregate_append(input,
+        SERF_BUCKET_SIMPLE_STRING("\x80\x61\x62", alloc));
+    serf_bucket_aggregate_append(input,
+        SERF_BUCKET_SIMPLE_STRING("", alloc));
+    serf_bucket_aggregate_append(input,
+        SERF_BUCKET_SIMPLE_STRING("\x63\x64\x65\x66\x67\x68\x03", alloc));
+    serf_bucket_aggregate_append(input,
+        SERF_BUCKET_SIMPLE_STRING("", alloc));
+
+    bkt = serf_bucket_brotli_decompress_create(input, alloc);
+    read_and_check_bucket(tc, bkt, "abcdefgh");
+    serf_bucket_destroy(bkt);
+}
+
+static void test_brotli_decompress_bucket_chunked_input2(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_bucket_t *input;
+    serf_bucket_t *bkt;
+    serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool);
+
+    /* Try an edge case where the valid encoded data (empty string) is
+     * followed by an empty chunk. */
+    input = serf_bucket_aggregate_create(alloc);
+    serf_bucket_aggregate_append(input,
+        SERF_BUCKET_SIMPLE_STRING("\x3B", alloc));
+    serf_bucket_aggregate_append(input,
+        SERF_BUCKET_SIMPLE_STRING("", alloc));
+
+    bkt = serf_bucket_brotli_decompress_create(input, alloc);
+    read_and_check_bucket(tc, bkt, "");
+    serf_bucket_destroy(bkt);
+}
+
+static void test_brotli_decompress_bucket_garbage_at_end(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_bucket_t *input;
+    serf_bucket_t *bkt;
+    serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool);
+    const char input_data[] = {
+        "\x8B\x03\x80\x61\x62\x63\x64\x65\x66\x67\x68\x03garbage"
+    };
+
+    input = serf_bucket_simple_create(input_data, sizeof(input_data) - 1,
+                                      NULL, NULL, alloc);
+    bkt = serf_bucket_brotli_decompress_create(input, alloc);
+
+    {
+        char buf[1024];
+        apr_size_t len;
+        apr_status_t status;
+
+        status = read_all(bkt, buf, sizeof(buf), &len);
+        CuAssertIntEquals(tc, SERF_ERROR_DECOMPRESSION_FAILED, status);
+    }
+}
+
+static void test_brotli_decompress_response_body(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_bucket_t *input;
+    serf_bucket_t *bkt;
+    serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool);
+
+    input = SERF_BUCKET_SIMPLE_STRING(
+        "HTTP/1.1 200 OK" CRLF
+        "Content-Type: text/html" CRLF
+        "Content-Length: 12" CRLF
+        "Content-Encoding: br" CRLF
+        CRLF
+        "\x8B\x03\x80\x61\x62\x63\x64\x65\x66\x67\x68\x03",
+        alloc);
+
+    bkt = serf_bucket_response_create(input, alloc);
+    read_and_check_bucket(tc, bkt, "abcdefgh");
+    serf_bucket_destroy(bkt);
+}
+
+static void test_deflate_bucket_truncated_data(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_bucket_t *input;
+    serf_bucket_t *bkt;
+    serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool);
+
+    /* This is a valid, but truncated gzip data (in two chunks). */
+    input = serf_bucket_aggregate_create(alloc);
+    serf_bucket_aggregate_append(input,
+        SERF_BUCKET_SIMPLE_STRING_LEN("\x1F\x8B\x08\x00\x00", 5, alloc));
+    serf_bucket_aggregate_append(input,
+        SERF_BUCKET_SIMPLE_STRING_LEN("\x00\x00\x00\x00\x03", 5, alloc));
+
+    bkt = serf_bucket_deflate_create(input, alloc, SERF_DEFLATE_GZIP);
+    {
+        char buf[1024];
+        apr_size_t len;
+        apr_status_t status;
+
+        status = read_all(bkt, buf, sizeof(buf), &len);
+        CuAssertIntEquals(tc, SERF_ERROR_DECOMPRESSION_FAILED, status);
+    }
+
+    serf_bucket_destroy(bkt);
+}
+
 CuSuite *test_buckets(void)
 {
     CuSuite *suite = CuSuiteNew();
@@ -2983,6 +3309,9 @@
     SUITE_ADD_TEST(suite, test_response_body_chunked_no_crlf);
     SUITE_ADD_TEST(suite, test_response_body_chunked_incomplete_crlf);
     SUITE_ADD_TEST(suite, test_response_body_chunked_gzip_small);
+    SUITE_ADD_TEST(suite, test_response_body_chunked_bogus_crlf);
+    SUITE_ADD_TEST(suite, test_response_body_chunked_invalid_len);
+    SUITE_ADD_TEST(suite, test_response_body_chunked_overflow_len);
     SUITE_ADD_TEST(suite, test_response_bucket_peek_at_headers);
     SUITE_ADD_TEST(suite, test_response_bucket_iis_status_code);
     SUITE_ADD_TEST(suite, test_response_bucket_no_reason);
@@ -3010,11 +3339,21 @@
     SUITE_ADD_TEST(suite, test_hpack_huffman_encode);
     SUITE_ADD_TEST(suite, test_hpack_header_encode);
     SUITE_ADD_TEST(suite, test_http2_frame_bucket_basic);
+    if (serf_bucket_is_brotli_supported()) {
+        SUITE_ADD_TEST(suite, test_brotli_decompress_bucket_basic);
+        SUITE_ADD_TEST(suite, test_brotli_decompress_bucket_truncated_input);
+        SUITE_ADD_TEST(suite, test_brotli_decompress_bucket_read_bytewise);
+        SUITE_ADD_TEST(suite, test_brotli_decompress_bucket_chunked_input);
+        SUITE_ADD_TEST(suite, test_brotli_decompress_bucket_chunked_input2);
+        SUITE_ADD_TEST(suite, test_brotli_decompress_bucket_garbage_at_end);
+        SUITE_ADD_TEST(suite, test_brotli_decompress_response_body);
+    }
 #if 0
     /* This test for issue #152 takes a lot of time generating 4GB+ of random
        data so it's disabled by default. */
     SUITE_ADD_TEST(suite, test_deflate_4GBplus_buckets);
 #endif
+    SUITE_ADD_TEST(suite, test_deflate_bucket_truncated_data);
 
 
 
diff --git a/test/test_context.c b/test/test_context.c
index a9e1517..9b600eb 100644
--- a/test/test_context.c
+++ b/test/test_context.c
@@ -456,8 +456,8 @@
     EndGiven
 
     for (i = 0 ; i < SENT_REQUESTS ; i++) {
-        create_new_request_with_resp_hdlr(tb, &handler_ctx[i], "GET", "/", i+1,
-                                          handle_response_keepalive_limit);
+        create_new_request_ex(tb, &handler_ctx[i], "GET", "/", i+1,
+                              NULL, handle_response_keepalive_limit);
     }
 
     /* The two retries of request 1 both also have req_id=1, which means that
@@ -561,8 +561,8 @@
     EndGiven
 
     for (i = 0 ; i < SENT_REQUESTS ; i++) {
-        create_new_request_with_resp_hdlr(tb, &handler_ctx[i], "GET", "/", i+1,
-                                          handle_response_keepalive_limit_burst);
+        create_new_request_ex(tb, &handler_ctx[i], "GET", "/", i+1,
+                              NULL, handle_response_keepalive_limit_burst);
     }
 
     /* The two retries of request 1 both also have req_id=1, which means that
@@ -973,6 +973,50 @@
     CuAssertIntEquals(tc, num_requests, tb->handled_requests->nelts);
 }
 
+/* Implements test_request_setup_t */
+static apr_status_t setup_request_err(serf_request_t *request,
+                                      void *setup_baton,
+                                      serf_bucket_t **req_bkt,
+                                      apr_pool_t *pool)
+{
+    static mockbkt_action actions[] = {
+        { 1, "a", APR_SUCCESS },
+        /* Return an error after first successful read. */
+        { 1, "", APR_EINVAL }
+    };
+    handler_baton_t *ctx = setup_baton;
+    serf_bucket_alloc_t *alloc;
+    serf_bucket_t *mock_bkt;
+
+    alloc = serf_request_get_alloc(request);
+    mock_bkt = serf_bucket_mock_create(actions, 2, alloc);
+    *req_bkt = serf_request_bucket_request_create(request,
+                                                  ctx->method, ctx->path,
+                                                  mock_bkt, alloc);
+    return APR_SUCCESS;
+}
+
+static void test_outgoing_request_err(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    handler_baton_t handler_ctx[1];
+    apr_status_t status;
+
+    setup_test_mock_server(tb);
+    status = setup_test_client_context(tb, NULL, tb->pool);
+    CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+    /* Setup an outgoing request with the body bucket returning an error. */
+    create_new_request_ex(tb, &handler_ctx[0], "POST", "/", 1,
+                          setup_request_err, NULL);
+
+    status = run_client_and_mock_servers_loops(tb, 1, handler_ctx, tb->pool);
+    CuAssertIntEquals(tc, APR_EINVAL, status);
+    CuAssertIntEquals(tc, 1, tb->sent_requests->nelts);
+    CuAssertIntEquals(tc, 0, tb->accepted_requests->nelts);
+    CuAssertIntEquals(tc, 0, tb->handled_requests->nelts);
+}
+
 /*****************************************************************************/
 CuSuite *test_context(void)
 {
@@ -998,6 +1042,7 @@
     SUITE_ADD_TEST(suite, test_connection_large_response);
     SUITE_ADD_TEST(suite, test_connection_large_request);
     SUITE_ADD_TEST(suite, test_max_keepalive_requests);
+    SUITE_ADD_TEST(suite, test_outgoing_request_err);
 
     return suite;
 }
diff --git a/test/test_serf.h b/test/test_serf.h
index 7688514..d815a42 100644
--- a/test/test_serf.h
+++ b/test/test_serf.h
@@ -123,6 +123,13 @@
 void *test_setup(void *baton);
 void *test_teardown(void *baton);
 
+/* Simple variant of serf_request_setup_t for tests. */
+typedef apr_status_t (*test_request_setup_t)(
+    serf_request_t *request,
+    void *setup_baton,
+    serf_bucket_t **req_bkt,
+    apr_pool_t *pool);
+
 typedef struct handler_baton_t {
     serf_response_acceptor_t acceptor;
     void *acceptor_baton;
@@ -138,6 +145,8 @@
     const char *path;
     /* Use this for a raw request message */
     const char *request;
+    /* Or this, if more control is needed. */
+    test_request_setup_t request_setup;
     int done;
 
     test_baton_t *tb;
@@ -171,6 +180,7 @@
 void setup_handler(test_baton_t *tb, handler_baton_t *handler_ctx,
                    const char *method, const char *path,
                    int req_id,
+                   test_request_setup_t req_setup,
                    serf_response_handler_t handler);
 void create_new_prio_request(test_baton_t *tb,
                              handler_baton_t *handler_ctx,
@@ -181,11 +191,12 @@
                         const char *method, const char *path,
                         int req_id);
 void
-create_new_request_with_resp_hdlr(test_baton_t *tb,
-                                  handler_baton_t *handler_ctx,
-                                  const char *method, const char *path,
-                                  int req_id,
-                                  serf_response_handler_t handler);
+create_new_request_ex(test_baton_t *tb,
+                      handler_baton_t *handler_ctx,
+                      const char *method, const char *path,
+                      int req_id,
+                      test_request_setup_t req_setup,
+                      serf_response_handler_t handler);
 
 const char *create_large_response_message(apr_pool_t *pool);
 const char *create_large_request_message_body(apr_pool_t *pool);
diff --git a/test/test_util.c b/test/test_util.c
index f7d6046..877af40 100644
--- a/test/test_util.c
+++ b/test/test_util.c
@@ -191,8 +191,13 @@
 {
     handler_baton_t *ctx = setup_baton;
     serf_bucket_t *body_bkt;
+    apr_status_t status = APR_SUCCESS;
 
-    if (ctx->request)
+    if (ctx->request_setup)
+    {
+        status = ctx->request_setup(request, setup_baton, req_bkt, pool);
+    }
+    else if (ctx->request)
     {
         /* Create a raw request bucket. */
         *req_bkt = serf_bucket_simple_create(ctx->request, strlen(ctx->request),
@@ -226,7 +231,7 @@
     *handler = ctx->handler;
     *handler_baton = ctx;
 
-    return APR_SUCCESS;
+    return status;
 }
 
 apr_status_t handle_response(serf_request_t *request,
@@ -270,6 +275,7 @@
 void setup_handler(test_baton_t *tb, handler_baton_t *handler_ctx,
                    const char *method, const char *path,
                    int req_id,
+                   test_request_setup_t req_setup,
                    serf_response_handler_t handler)
 {
     handler_ctx->method = method;
@@ -285,6 +291,7 @@
     handler_ctx->handled_requests = tb->handled_requests;
     handler_ctx->tb = tb;
     handler_ctx->request = NULL;
+    handler_ctx->request_setup = req_setup;
 }
 
 void create_new_prio_request(test_baton_t *tb,
@@ -292,7 +299,7 @@
                              const char *method, const char *path,
                              int req_id)
 {
-    setup_handler(tb, handler_ctx, method, path, req_id, NULL);
+    setup_handler(tb, handler_ctx, method, path, req_id, NULL, NULL);
     serf_connection_priority_request_create(tb->connection,
                                             setup_request,
                                             handler_ctx);
@@ -303,20 +310,21 @@
                         const char *method, const char *path,
                         int req_id)
 {
-    setup_handler(tb, handler_ctx, method, path, req_id, NULL);
+    setup_handler(tb, handler_ctx, method, path, req_id, NULL, NULL);
     serf_connection_request_create(tb->connection,
                                    setup_request,
                                    handler_ctx);
 }
 
 void
-create_new_request_with_resp_hdlr(test_baton_t *tb,
-                                  handler_baton_t *handler_ctx,
-                                  const char *method, const char *path,
-                                  int req_id,
-                                  serf_response_handler_t handler)
+create_new_request_ex(test_baton_t *tb,
+                      handler_baton_t *handler_ctx,
+                      const char *method, const char *path,
+                      int req_id,
+                      test_request_setup_t req_setup,
+                      serf_response_handler_t handler)
 {
-    setup_handler(tb, handler_ctx, method, path, req_id, handler);
+    setup_handler(tb, handler_ctx, method, path, req_id, req_setup, handler);
     serf_connection_request_create(tb->connection,
                                    setup_request,
                                    handler_ctx);