--- /dev/null
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream-private.h"
+#include "compression.h"
+
+struct decompress_istream {
+ struct istream_private istream;
+ struct istream *compressed_input;
+ struct istream *decompressed_input;
+ enum istream_decompress_flags flags;
+};
+
+static void copy_compressed_input_error(struct decompress_istream *zstream)
+{
+ struct istream_private *stream = &zstream->istream;
+
+ stream->istream.stream_errno = zstream->compressed_input->stream_errno;
+ stream->istream.eof = zstream->compressed_input->eof;
+ if (zstream->compressed_input->real_stream->iostream.error != NULL) {
+ io_stream_set_error(&stream->iostream, "%s",
+ zstream->compressed_input->real_stream->iostream.error);
+ }
+}
+
+static void copy_decompressed_input_error(struct decompress_istream *zstream)
+{
+ struct istream_private *stream = &zstream->istream;
+
+ stream->istream.stream_errno = zstream->decompressed_input->stream_errno;
+ stream->istream.eof = zstream->decompressed_input->eof;
+ if (zstream->decompressed_input->real_stream->iostream.error != NULL) {
+ io_stream_set_error(&stream->iostream, "%s",
+ zstream->decompressed_input->real_stream->iostream.error);
+ }
+}
+
+static void
+i_stream_decompress_close(struct iostream_private *_stream, bool close_parent)
+{
+ struct istream_private *stream =
+ container_of(_stream, struct istream_private, iostream);
+ struct decompress_istream *zstream =
+ container_of(stream, struct decompress_istream, istream);
+
+ if (zstream->decompressed_input != NULL)
+ i_stream_close(zstream->decompressed_input);
+ if (close_parent)
+ i_stream_close(zstream->compressed_input);
+}
+
+static void
+i_stream_decompress_destroy(struct iostream_private *_stream)
+{
+ struct istream_private *stream =
+ container_of(_stream, struct istream_private, iostream);
+ struct decompress_istream *zstream =
+ container_of(stream, struct decompress_istream, istream);
+
+ i_stream_unref(&zstream->decompressed_input);
+ i_stream_unref(&zstream->compressed_input);
+}
+
+static int
+i_stream_decompress_not_compressed(struct decompress_istream *zstream)
+{
+ if ((zstream->flags & ISTREAM_DECOMPRESS_FLAG_TRY) == 0) {
+ zstream->istream.istream.stream_errno = EINVAL;
+ io_stream_set_error(&zstream->istream.iostream,
+ "Stream isn't compressed");
+ return -1;
+ } else {
+ zstream->decompressed_input = zstream->compressed_input;
+ i_stream_ref(zstream->decompressed_input);
+ return 1;
+ }
+}
+
+static int i_stream_decompress_detect(struct decompress_istream *zstream)
+{
+ const struct compression_handler *handler;
+ ssize_t ret;
+
+ ret = i_stream_read(zstream->compressed_input);
+ handler = compression_detect_handler(zstream->compressed_input);
+ if (handler == NULL) {
+ switch (ret) {
+ case -1:
+ if (zstream->compressed_input->stream_errno != 0) {
+ copy_compressed_input_error(zstream);
+ return -1;
+ }
+ /* fall through */
+ case -2:
+ /* we've read a full buffer or we reached EOF -
+ the stream isn't compressed */
+ return i_stream_decompress_not_compressed(zstream);
+ case 0:
+ return 0;
+ default:
+ if (!zstream->istream.istream.blocking)
+ return 0;
+ return i_stream_decompress_detect(zstream);
+ }
+ }
+ if (handler->create_istream == NULL) {
+ zstream->istream.istream.stream_errno = EINVAL;
+ io_stream_set_error(&zstream->istream.iostream,
+ "Compression handler %s not supported", handler->name);
+ return -1;
+ }
+
+ zstream->decompressed_input =
+ handler->create_istream(zstream->compressed_input, FALSE);
+ return 1;
+}
+
+static ssize_t i_stream_decompress_read(struct istream_private *stream)
+{
+ struct decompress_istream *zstream =
+ container_of(stream, struct decompress_istream, istream);
+ ssize_t ret;
+ size_t pos;
+
+ if (zstream->decompressed_input == NULL) {
+ if ((ret = i_stream_decompress_detect(zstream)) <= 0)
+ return ret;
+ }
+
+ i_stream_seek(zstream->decompressed_input, stream->istream.v_offset);
+ stream->pos -= stream->skip;
+ stream->skip = 0;
+
+ stream->buffer = i_stream_get_data(zstream->decompressed_input, &pos);
+ if (pos > stream->pos)
+ ret = 0;
+ else do {
+ ret = i_stream_read_memarea(zstream->decompressed_input);
+ copy_decompressed_input_error(zstream);
+ stream->buffer = i_stream_get_data(zstream->decompressed_input,
+ &pos);
+ } while (pos <= stream->pos && ret > 0);
+ if (ret == -2)
+ return -2;
+
+ if (pos <= stream->pos)
+ ret = ret == 0 ? 0 : -1;
+ else
+ ret = (ssize_t)(pos - stream->pos);
+ stream->pos = pos;
+ i_assert(ret != -1 || stream->istream.eof ||
+ stream->istream.stream_errno != 0);
+ return ret;
+}
+
+static void i_stream_decompress_reset(struct istream_private *stream)
+{
+ stream->skip = stream->pos = 0;
+ stream->istream.v_offset = 0;
+ stream->istream.eof = FALSE;
+}
+
+static void
+i_stream_decompress_seek(struct istream_private *stream,
+ uoff_t v_offset, bool mark)
+{
+ struct decompress_istream *zstream =
+ container_of(stream, struct decompress_istream, istream);
+
+ if (zstream->decompressed_input == NULL) {
+ if (!i_stream_nonseekable_try_seek(stream, v_offset))
+ i_panic("seeking backwards before detecting compression format");
+ } else {
+ i_stream_decompress_reset(stream);
+ stream->istream.v_offset = v_offset;
+ if (mark)
+ i_stream_seek_mark(zstream->decompressed_input, v_offset);
+ else
+ i_stream_seek(zstream->decompressed_input, v_offset);
+ copy_decompressed_input_error(zstream);
+ }
+}
+
+static void i_stream_decompress_sync(struct istream_private *stream)
+{
+ struct decompress_istream *zstream =
+ container_of(stream, struct decompress_istream, istream);
+
+ i_stream_decompress_reset(stream);
+ if (zstream->decompressed_input != NULL)
+ i_stream_sync(zstream->decompressed_input);
+}
+
+static int i_stream_decompress_stat(struct istream_private *stream, bool exact)
+{
+ struct decompress_istream *zstream =
+ container_of(stream, struct decompress_istream, istream);
+ const struct stat *st;
+
+ if (!exact) {
+ if (i_stream_stat(zstream->compressed_input, exact, &st) < 0) {
+ copy_compressed_input_error(zstream);
+ return -1;
+ }
+ stream->statbuf = *st;
+ return 0;
+ }
+ if (zstream->decompressed_input == NULL) {
+ (void)i_stream_read(&stream->istream);
+ if (zstream->decompressed_input == NULL) {
+ if (stream->istream.stream_errno == 0) {
+ zstream->istream.istream.stream_errno = EINVAL;
+ io_stream_set_error(&zstream->istream.iostream,
+ "Stream compression couldn't be detected during stat");
+ }
+ return -1;
+ }
+ }
+
+ if (i_stream_stat(zstream->decompressed_input, exact, &st) < 0) {
+ copy_decompressed_input_error(zstream);
+ return -1;
+ }
+ i_stream_decompress_reset(stream);
+ stream->statbuf = *st;
+ return 0;
+}
+
+struct istream *
+i_stream_create_decompress(struct istream *input,
+ enum istream_decompress_flags flags)
+{
+ struct decompress_istream *zstream;
+
+ zstream = i_new(struct decompress_istream, 1);
+ zstream->compressed_input = input;
+ zstream->flags = flags;
+ i_stream_ref(input);
+
+ zstream->istream.iostream.close = i_stream_decompress_close;
+ zstream->istream.iostream.destroy = i_stream_decompress_destroy;
+ zstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
+ zstream->istream.read = i_stream_decompress_read;
+ zstream->istream.seek = i_stream_decompress_seek;
+ zstream->istream.sync = i_stream_decompress_sync;
+ zstream->istream.stat = i_stream_decompress_stat;
+
+ zstream->istream.istream.readable_fd = FALSE;
+ zstream->istream.istream.blocking = input->blocking;
+ zstream->istream.istream.seekable = input->seekable;
+
+ return i_stream_create(&zstream->istream, NULL,
+ i_stream_get_fd(input), 0);
+}
test_end();
}
-static void test_compression_handler_short(const struct compression_handler *handler)
+static void
+test_compression_handler_short(const struct compression_handler *handler,
+ bool autodetect)
{
const unsigned char *data;
size_t len, size;
struct istream *input;
/* write some amount of data */
- test_begin(t_strdup_printf("compression handler %s (small)", handler->name));
+ test_begin(t_strdup_printf("compression handler %s (small, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
len = i_rand_minmax(1, 1024);
test_data = buffer_create_dynamic(default_pool, len);
random_fill(buffer_append_space_unsafe(test_data, len), len);
/* read data at once */
test_input = test_istream_create_data(buffer->data, buffer->used);
- input = handler->create_istream(test_input, TRUE);
+ input = !autodetect ? handler->create_istream(test_input, TRUE) :
+ i_stream_create_decompress(test_input, 0);
i_stream_unref(&test_input);
test_assert(i_stream_read_more(input, &data, &size) > 0);
test_end();
}
-static void test_compression_handler_seek(const struct compression_handler *handler)
+static void
+test_compression_handler_seek(const struct compression_handler *handler,
+ bool autodetect)
{
const unsigned char *data,*ptr;
size_t len, size, pos;
struct istream *input;
/* write some amount of data */
- test_begin(t_strdup_printf("compression handler %s (seek)", handler->name));
+ test_begin(t_strdup_printf("compression handler %s (seek, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
len = i_rand_minmax(1024, 2048);
test_data = buffer_create_dynamic(default_pool, len);
random_fill(buffer_append_space_unsafe(test_data, len), len);
o_stream_unref(&output);
test_input = test_istream_create_data(buffer->data, buffer->used);
- input = handler->create_istream(test_input, TRUE);
+ input = !autodetect ? handler->create_istream(test_input, TRUE) :
+ i_stream_create_decompress(test_input, 0);
i_stream_unref(&test_input);
/* seek forward */
test_end();
}
-static void test_compression_handler_reset(const struct compression_handler *handler)
+static void
+test_compression_handler_reset(const struct compression_handler *handler,
+ bool autodetect)
{
const unsigned char *data;
size_t len, size;
struct istream *input;
/* write some amount of data */
- test_begin(t_strdup_printf("compression handler %s (reset)", handler->name));
+ test_begin(t_strdup_printf("compression handler %s (reset, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
len = i_rand_minmax(1024, 2048);
test_data = buffer_create_dynamic(default_pool, len);
random_fill(buffer_append_space_unsafe(test_data, len), len);
o_stream_unref(&output);
test_input = test_istream_create_data(buffer->data, buffer->used);
- input = handler->create_istream(test_input, TRUE);
+ input = !autodetect ? handler->create_istream(test_input, TRUE) :
+ i_stream_create_decompress(test_input, 0);
i_stream_unref(&test_input);
/* seek forward */
test_end();
}
-static void test_compression_handler(const struct compression_handler *handler)
+static void
+test_compression_handler(const struct compression_handler *handler,
+ bool autodetect)
{
const char *path = "test-compression.tmp";
struct istream *file_input, *input;
int fd;
ssize_t ret;
- test_begin(t_strdup_printf("compression handler %s", handler->name));
+ test_begin(t_strdup_printf("compression handler %s (autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
/* write compressed data */
fd = open(path, O_TRUNC | O_CREAT | O_RDWR, 0600);
/* read and uncompress the data */
file_input = i_stream_create_fd(fd, IO_BLOCK_SIZE);
- input = handler->create_istream(file_input, TRUE);
+ input = !autodetect ? handler->create_istream(file_input, TRUE) :
+ i_stream_create_decompress(file_input, 0);
test_assert(i_stream_get_size(input, FALSE, &stream_size) == 1);
test_assert(stream_size == compressed_size);
test_end();
}
-static void test_compression_handler_partial_parent_write(const struct compression_handler *handler)
+static void
+test_compression_handler_partial_parent_write(const struct compression_handler *handler,
+ bool autodetect)
{
- test_begin(t_strdup_printf("compression handler %s (partial parent writes)", handler->name));
+ test_begin(t_strdup_printf("compression handler %s (partial parent writes, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
int ret;
buffer_t *buffer = t_buffer_create(64);
buffer_append(compressed_data, buffer->data, buffer->used);
struct istream *is = test_istream_create_data(compressed_data->data, compressed_data->used);
- struct istream *is_decompressed = handler->create_istream(is, TRUE);
+ struct istream *is_decompressed =
+ !autodetect ? handler->create_istream(is, TRUE) :
+ i_stream_create_decompress(is, 0);
i_stream_unref(&is);
const unsigned char *data;
}
static void
-test_compression_handler_random_io(const struct compression_handler *handler)
+test_compression_handler_random_io(const struct compression_handler *handler,
+ bool autodetect)
{
unsigned char in_buf[8192];
size_t in_buf_size;
enc_buf = buffer_create_dynamic(default_pool, sizeof(in_buf));
dec_buf = buffer_create_dynamic(default_pool, sizeof(in_buf));
- test_begin(t_strdup_printf("compression handler %s (random I/O)",
- handler->name));
+ test_begin(t_strdup_printf("compression handler %s (random I/O, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
for (i = 0; !test_has_failed() && i < 300; i++) {
struct istream *input1, *input2;
i_stream_set_name(input1, "[compressed-data]");
/* Create decompressor stream */
- input2 = handler->create_istream(input1, TRUE);
+ input2 = !autodetect ? handler->create_istream(input1, TRUE) :
+ i_stream_create_decompress(input1, 0);
i_stream_set_name(input2, "[decompressor]");
/* Assign random buffer sizes */
}
static void
-test_compression_handler_large_random_io(const struct compression_handler *handler)
+test_compression_handler_large_random_io(const struct compression_handler *handler,
+ bool autodetect)
{
#define RANDOMNESS_SIZE (1024*1024)
unsigned char *randomness;
size_t size;
int ret;
- test_begin(t_strdup_printf("compression handler %s (large random io)", handler->name));
+ test_begin(t_strdup_printf("compression handler %s (large random io, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
randomness = i_malloc(RANDOMNESS_SIZE);
random_fill(randomness, RANDOMNESS_SIZE);
/* verify that reading the input works */
- dec_input = handler->create_istream(input, FALSE);
+ dec_input = !autodetect ? handler->create_istream(input, TRUE) :
+ i_stream_create_decompress(input, 0);
while ((ret = i_stream_read_more(dec_input, &data, &size)) > 0) {
test_assert(memcmp(data, randomness + dec_input->v_offset, size) == 0);
test_end();
}
-static void test_compression_handler_errors(const struct compression_handler *handler)
+static void
+test_compression_handler_errors(const struct compression_handler *handler,
+ bool autodetect)
{
- test_begin(t_strdup_printf("compression handler %s (errors)", handler->name));
+ test_begin(t_strdup_printf("compression handler %s (errors, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
/* test that zero stream reading errors out */
struct istream *is = test_istream_create("");
- struct istream *input = handler->create_istream(is, FALSE);
+ struct istream *input =
+ !autodetect ? handler->create_istream(is, FALSE) :
+ i_stream_create_decompress(is, 0);
i_stream_unref(&is);
test_assert(i_stream_read(input) == -1 && input->eof);
i_stream_unref(&input);
is = test_istream_create("dedededededededededededededede"
"dedededeededdedededededededede"
"dedededededededededededededede");
- input = handler->create_istream(is, FALSE);
+ is->blocking = TRUE;
+ input = !autodetect ? handler->create_istream(is, FALSE) :
+ i_stream_create_decompress(is, 0);
i_stream_unref(&is);
test_assert(i_stream_read(input) == -1 && input->eof);
i_stream_unref(&input);
/* truncate buffer */
is = test_istream_create_data(odata->data, odata->used - sizeof(buf)*2 - 1);
- input = handler->create_istream(is, FALSE);
+ input = !autodetect ? handler->create_istream(is, FALSE) :
+ i_stream_create_decompress(is, 0);
i_stream_unref(&is);
const unsigned char *data ATTR_UNUSED;
that should not match any handlers' header */
for (size_t i = 0; i < 32; i++) {
is = test_istream_create_data("dededededededededededededededede", i);
- input = handler->create_istream(is, FALSE);
+ input = !autodetect ? handler->create_istream(is, FALSE) :
+ i_stream_create_decompress(is, 0);
i_stream_unref(&is);
while (i_stream_read_more(input, &data, &size) >= 0) {
test_assert_idx(size == 0, i);
test_end();
}
-static void test_compression(void)
+static void test_compression_int(bool autodetect)
{
unsigned int i;
for (i = 0; compression_handlers[i].name != NULL; i++) {
- if (compression_handlers[i].create_istream != NULL) T_BEGIN {
- test_compression_handler_short(&compression_handlers[i]);
- test_compression_handler(&compression_handlers[i]);
- if (compression_handlers[i].is_compressed != NULL)
+ if (compression_handlers[i].create_istream != NULL &&
+ (!autodetect ||
+ compression_handlers[i].is_compressed != NULL)) T_BEGIN {
+ if (compression_handlers[i].is_compressed != NULL &&
+ !autodetect)
test_compression_handler_detect(&compression_handlers[i]);
- test_compression_handler_seek(&compression_handlers[i]);
- test_compression_handler_reset(&compression_handlers[i]);
- test_compression_handler_partial_parent_write(&compression_handlers[i]);
- test_compression_handler_random_io(&compression_handlers[i]);
- test_compression_handler_large_random_io(&compression_handlers[i]);
- test_compression_handler_errors(&compression_handlers[i]);
+ test_compression_handler_short(&compression_handlers[i], autodetect);
+ test_compression_handler(&compression_handlers[i], autodetect);
+ test_compression_handler_seek(&compression_handlers[i], autodetect);
+ test_compression_handler_reset(&compression_handlers[i], autodetect);
+ test_compression_handler_partial_parent_write(&compression_handlers[i], autodetect);
+ test_compression_handler_random_io(&compression_handlers[i], autodetect);
+ test_compression_handler_large_random_io(&compression_handlers[i], autodetect);
+ test_compression_handler_errors(&compression_handlers[i], autodetect);
} T_END;
}
}
-static void test_gz(const char *str1, const char *str2)
+static void test_compression(void)
+{
+ test_compression_int(FALSE);
+ test_compression_int(TRUE);
+}
+
+static void test_istream_decompression_try(void)
+{
+ const char *tests[] = {
+ "",
+ "1",
+ "12",
+ "12345678901234567890123456789012345678901234567890",
+ };
+ struct istream *is, *input;
+ const unsigned char *data;
+ size_t size;
+
+ test_begin("istream-decompression try");
+
+ for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
+ size_t test_len = strlen(tests[i]);
+ is = i_stream_create_from_data(tests[i], test_len);
+ input = i_stream_create_decompress(is, ISTREAM_DECOMPRESS_FLAG_TRY);
+ i_stream_unref(&is);
+
+ ssize_t ret = i_stream_read(input);
+ test_assert_idx((test_len == 0 && ret == -1) ||
+ (test_len > 0 && ret == (ssize_t)test_len), i);
+ data = i_stream_get_data(input, &size);
+ test_assert_idx(size == test_len &&
+ memcmp(data, tests[i], size) == 0, i);
+
+ i_stream_skip(input, size);
+ test_assert_idx(i_stream_read(input) == -1, i);
+ test_assert_idx(input->stream_errno == 0, i);
+ i_stream_unref(&input);
+ }
+ test_end();
+}
+
+static void test_gz(const char *str1, const char *str2, bool autodetect)
{
const struct compression_handler *gz;
struct ostream *buf_output, *output;
size_t size;
test_input = test_istream_create_data(buf->data, buf->used);
test_istream_set_allow_eof(test_input, FALSE);
- input = gz->create_istream(test_input, TRUE);
+ input = !autodetect ? gz->create_istream(test_input, TRUE) :
+ i_stream_create_decompress(test_input, 0);
for (size_t i = 0; i <= buf->used; i++) {
test_istream_set_size(test_input, i);
test_assert(i_stream_read(input) >= 0);
static void test_gz_concat(void)
{
- test_begin("gz concat");
- test_gz("hello", "world");
+ test_begin("gz concat (autodetect=no)");
+ test_gz("hello", "world", FALSE);
+ test_end();
+
+ test_begin("gz concat (autodetect=yes)");
+ test_gz("hello", "world", TRUE);
test_end();
}
static void test_gz_no_concat(void)
{
- test_begin("gz no concat");
- test_gz("hello", "");
+ test_begin("gz no concat (autodetect=no)");
+ test_gz("hello", "", FALSE);
+ test_end();
+
+ test_begin("gz no concat (autodetect=yes)");
+ test_gz("hello", "", TRUE);
test_end();
}
-static void test_gz_header(void)
+static void test_gz_header_int(bool autodetect)
{
const struct compression_handler *gz;
const char *input_strings[] = {
if (compression_lookup_handler("gz", &gz) <= 0 )
return; /* not compiled in or unkown*/
- test_begin("gz header");
+ test_begin(t_strdup_printf(
+ "gz header (autodetect=%s)", autodetect ? "yes" : "no"));
for (unsigned int i = 0; i < N_ELEMENTS(input_strings); i++) {
file_input = test_istream_create_data(input_strings[i],
strlen(input_strings[i]));
file_input->blocking = TRUE;
- input = gz->create_istream(file_input, FALSE);
+ input = !autodetect ? gz->create_istream(file_input, FALSE) :
+ i_stream_create_decompress(file_input, 0);
test_assert_idx(i_stream_read(input) == -1, i);
test_assert_idx(input->stream_errno == EINVAL, i);
i_stream_unref(&input);
test_end();
}
-static void test_gz_large_header(void)
+static void test_gz_header(void)
+{
+ test_gz_header_int(FALSE);
+ test_gz_header_int(TRUE);
+}
+
+static void test_gz_large_header_int(bool autodetect)
{
const struct compression_handler *gz;
static const unsigned char gz_input[] = {
if (compression_lookup_handler("gz", &gz) <= 0 )
return; /* not compiled in or unkown*/
- test_begin("gz large header");
+ test_begin(t_strdup_printf(
+ "gz large header (autodetect=%s)", autodetect ? "yes" : "no"));
/* max buffer size smaller than gz header */
for (i = 1; i < sizeof(gz_input); i++) {
test_istream_set_size(file_input, i);
test_istream_set_max_buffer_size(file_input, i);
- input = gz->create_istream(file_input, FALSE);
+ input = !autodetect ? gz->create_istream(file_input, FALSE) :
+ i_stream_create_decompress(file_input, 0);
test_assert_idx(i_stream_read(input) == 0, i);
test_assert_idx(i_stream_read(input) == -1 &&
input->stream_errno == EINVAL, i);
/* max buffer size is exactly the gz header */
file_input = test_istream_create_data(gz_input, sizeof(gz_input));
- input = gz->create_istream(file_input, TRUE);
- test_istream_set_size(input, i);
- test_istream_set_allow_eof(input, FALSE);
- test_istream_set_max_buffer_size(input, i);
+ input = !autodetect ? gz->create_istream(file_input, TRUE) :
+ i_stream_create_decompress(file_input, 0);
+ test_istream_set_size(file_input, i);
+ test_istream_set_allow_eof(file_input, FALSE);
+ test_istream_set_max_buffer_size(file_input, i);
test_assert(i_stream_read(input) == 0);
i_stream_unref(&input);
i_stream_unref(&file_input);
test_end();
}
+static void test_gz_large_header(void)
+{
+ test_gz_large_header_int(FALSE);
+ test_gz_large_header_int(TRUE);
+}
+
static void test_uncompress_file(const char *path)
{
const struct compression_handler *handler;
{
static void (*const test_functions[])(void) = {
test_compression,
+ test_istream_decompression_try,
test_gz_concat,
test_gz_no_concat,
test_gz_header,