diff options
Diffstat (limited to 'src/lib-compression/compression.c')
-rw-r--r-- | src/lib-compression/compression.c | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/src/lib-compression/compression.c b/src/lib-compression/compression.c new file mode 100644 index 0000000..e562e73 --- /dev/null +++ b/src/lib-compression/compression.c @@ -0,0 +1,250 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "istream-zlib.h" +#include "ostream-zlib.h" +#include "iostream-lz4.h" +#include "compression.h" + +#ifndef HAVE_ZLIB +# define i_stream_create_gz NULL +# define o_stream_create_gz NULL +# define i_stream_create_deflate NULL +# define o_stream_create_deflate NULL +# define compression_get_min_level_gz NULL +# define compression_get_default_level_gz NULL +# define compression_get_max_level_gz NULL +#endif +#ifndef HAVE_BZLIB +# define i_stream_create_bz2 NULL +# define o_stream_create_bz2 NULL +# define compression_get_min_level_bz2 NULL +# define compression_get_default_level_bz2 NULL +# define compression_get_max_level_bz2 NULL +#endif +#ifndef HAVE_LZMA +# define i_stream_create_lzma NULL +#endif +#ifndef HAVE_LZ4 +# define i_stream_create_lz4 NULL +# define o_stream_create_lz4 NULL +# define compression_get_min_level_lz4 NULL +# define compression_get_default_level_lz4 NULL +# define compression_get_max_level_lz4 NULL +#endif +#ifndef HAVE_ZSTD +# define i_stream_create_zstd NULL +# define o_stream_create_zstd NULL +# define compression_get_min_level_zstd NULL +# define compression_get_default_level_zstd NULL +# define compression_get_max_level_zstd NULL +#endif + +static bool is_compressed_zlib(struct istream *input) +{ + const unsigned char *data; + size_t size; + + /* Peek in to the stream and see if it looks like it's compressed + (based on its header). This also means that users can try to exploit + security holes in the uncompression library by APPENDing a specially + crafted mail. So let's hope zlib is free of holes. */ + if (i_stream_read_bytes(input, &data, &size, 2) <= 0) + return FALSE; + i_assert(size >= 2); + + return data[0] == 31 && data[1] == 139; +} + +static bool is_compressed_bzlib(struct istream *input) +{ + const unsigned char *data; + size_t size; + + if (i_stream_read_bytes(input, &data, &size, 4) <= 0) + return FALSE; + if (memcmp(data, "BZh", 3) != 0) + return FALSE; + if (data[3] < '1' || data[3] > '9') + return FALSE; + /* The above is enough to be considered as the bzlib magic. + Normally it's followed by data header beginning with 0x31. However, + with empty compressed files it's followed by 0x17. */ + return TRUE; +} + +static bool is_compressed_xz(struct istream *input) +{ + const unsigned char *data; + size_t size; + + if (i_stream_read_bytes(input, &data, &size, 6) <= 0) + return FALSE; + return memcmp(data, "\xfd\x37\x7a\x58\x5a\x00", 6) == 0; +} + +static bool is_compressed_lz4(struct istream *input) +{ + const unsigned char *data; + size_t size; + + if (i_stream_read_bytes(input, &data, &size, IOSTREAM_LZ4_MAGIC_LEN) <= 0) + return FALSE; + /* there is no standard LZ4 header, so we've created our own */ + return memcmp(data, IOSTREAM_LZ4_MAGIC, IOSTREAM_LZ4_MAGIC_LEN) == 0; +} + +#define ZSTD_MAGICNUMBER 0xFD2FB528 /* valid since v0.8.0 */ +static bool is_compressed_zstd(struct istream *input) +{ + const unsigned char *data; + size_t size = 0; + + if (i_stream_read_bytes(input, &data, &size, sizeof(uint32_t)) <= 0) + return FALSE; + i_assert(size >= sizeof(uint32_t)); + + return le32_to_cpu_unaligned(data) == ZSTD_MAGICNUMBER; +} + +int compression_lookup_handler(const char *name, + const struct compression_handler **handler_r) +{ + unsigned int i; + + for (i = 0; compression_handlers[i].name != NULL; i++) { + if (strcmp(name, compression_handlers[i].name) == 0) { + if (compression_handlers[i].create_istream == NULL || + compression_handlers[i].create_ostream == NULL) { + /* Handler is known but not compiled in */ + return 0; + } + (*handler_r) = &compression_handlers[i]; + return 1; + } + } + return -1; +} + +const struct compression_handler * +compression_detect_handler(struct istream *input) +{ + unsigned int i; + + for (i = 0; compression_handlers[i].name != NULL; i++) { + if (compression_handlers[i].is_compressed != NULL && + compression_handlers[i].is_compressed(input)) + return &compression_handlers[i]; + } + return NULL; +} + +int compression_lookup_handler_from_ext(const char *path, + const struct compression_handler **handler_r) +{ + unsigned int i; + size_t len, path_len = strlen(path); + + for (i = 0; compression_handlers[i].name != NULL; i++) { + if (compression_handlers[i].ext == NULL) + continue; + + len = strlen(compression_handlers[i].ext); + if (path_len > len && + strcmp(path + path_len - len, compression_handlers[i].ext) == 0) { + if (compression_handlers[i].create_istream == NULL || + compression_handlers[i].create_ostream == NULL) { + /* Handler is known but not compiled in */ + return 0; + } + (*handler_r) = &compression_handlers[i]; + return 1; + } + } + return -1; +} + +static int compression_get_min_level_unsupported(void) +{ + return -1; +} + +static int compression_get_default_level_unsupported(void) +{ + return -1; +} + +static int compression_get_max_level_unsupported(void) +{ + return -1; +} + +const struct compression_handler compression_handlers[] = { + { + .name = "gz", + .ext = ".gz", + .is_compressed = is_compressed_zlib, + .create_istream = i_stream_create_gz, + .create_ostream = o_stream_create_gz, + .get_min_level = compression_get_min_level_gz, + .get_default_level = compression_get_default_level_gz, + .get_max_level = compression_get_max_level_gz, + }, + { + .name = "bz2", + .ext = ".bz2", + .is_compressed = is_compressed_bzlib, + .create_istream = i_stream_create_bz2, + .create_ostream = o_stream_create_bz2, + .get_min_level = compression_get_min_level_bz2, + .get_default_level = compression_get_default_level_bz2, + .get_max_level = compression_get_max_level_bz2, + }, + { + .name = "deflate", + .ext = NULL, + .is_compressed = NULL, + .create_istream = i_stream_create_deflate, + .create_ostream = o_stream_create_deflate, + .get_min_level = compression_get_min_level_gz, + .get_default_level = compression_get_default_level_gz, + .get_max_level = compression_get_max_level_gz, + }, + { + .name = "xz", + .ext = ".xz", + .is_compressed = is_compressed_xz, + .create_istream = i_stream_create_lzma, + .create_ostream = NULL, + .get_min_level = compression_get_min_level_unsupported, + .get_default_level = compression_get_default_level_unsupported, + .get_max_level = compression_get_max_level_unsupported, + }, + { + .name = "lz4", + .ext = ".lz4", + .is_compressed = is_compressed_lz4, + .create_istream = i_stream_create_lz4, + .create_ostream = o_stream_create_lz4, + .get_min_level = compression_get_min_level_lz4, /* does not actually support any of this */ + .get_default_level = compression_get_default_level_lz4, + .get_max_level = compression_get_max_level_lz4, + }, + { + .name = "zstd", + .ext = ".zstd", + .is_compressed = is_compressed_zstd, + .create_istream = i_stream_create_zstd, + .create_ostream = o_stream_create_zstd, + .get_min_level = compression_get_min_level_zstd, + .get_default_level = compression_get_default_level_zstd, + .get_max_level = compression_get_max_level_zstd, + }, + { + .name = "unsupported", + }, + { + .name = NULL, + } +}; |