/* compression_utils_portable.cc * * Copyright 2019 The Chromium Authors * Use of this source code is governed by a BSD-style license that can be * found in the Chromium source repository LICENSE file. */ #include "compression_utils_portable.h" #include #include #include namespace zlib_internal { // The difference in bytes between a zlib header and a gzip header. const size_t kGzipZlibHeaderDifferenceBytes = 16; // Pass an integer greater than the following get a gzip header instead of a // zlib header when calling deflateInit2() and inflateInit2(). const int kWindowBitsToGetGzipHeader = 16; // This describes the amount of memory zlib uses to compress data. It can go // from 1 to 9, with 8 being the default. For details, see: // http://www.zlib.net/manual.html (search for memLevel). const int kZlibMemoryLevel = 8; // The expected compressed size is based on the input size factored by // internal Zlib constants (e.g. window size, etc) plus the wrapper // header size. uLongf GzipExpectedCompressedSize(uLongf input_size) { return kGzipZlibHeaderDifferenceBytes + compressBound(input_size); } // The expected decompressed size is stored in the last // 4 bytes of |input| in LE. See https://tools.ietf.org/html/rfc1952#page-5 uint32_t GetGzipUncompressedSize(const Bytef* compressed_data, size_t length) { uint32_t size; if (length < sizeof(size)) return 0; memcpy(&size, &compressed_data[length - sizeof(size)], sizeof(size)); #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ return size; #else return __builtin_bswap32(size); #endif } // The number of window bits determines the type of wrapper to use - see // https://cs.chromium.org/chromium/src/third_party/zlib/zlib.h?l=566 inline int ZlibStreamWrapperType(WrapperType type) { if (type == ZLIB) // zlib DEFLATE stream wrapper return MAX_WBITS; if (type == GZIP) // gzip DEFLATE stream wrapper return MAX_WBITS + kWindowBitsToGetGzipHeader; if (type == ZRAW) // no wrapper, use raw DEFLATE return -MAX_WBITS; return 0; } int GzipCompressHelper(Bytef* dest, uLongf* dest_length, const Bytef* source, uLong source_length, void* (*malloc_fn)(size_t), void (*free_fn)(void*)) { return CompressHelper(GZIP, dest, dest_length, source, source_length, Z_DEFAULT_COMPRESSION, malloc_fn, free_fn); } // This code is taken almost verbatim from third_party/zlib/compress.c. The only // difference is deflateInit2() is called which allows different window bits to // be set. > 16 causes a gzip header to be emitted rather than a zlib header, // and negative causes no header to emitted. // // Compression level can be a number from 1-9, with 1 being the fastest, 9 being // the best compression. The default, which the GZIP helper uses, is 6. int CompressHelper(WrapperType wrapper_type, Bytef* dest, uLongf* dest_length, const Bytef* source, uLong source_length, int compression_level, void* (*malloc_fn)(size_t), void (*free_fn)(void*)) { if (compression_level < 0 || compression_level > 9) { compression_level = Z_DEFAULT_COMPRESSION; } z_stream stream; // FIXME(cavalcantii): z_const is not defined as 'const'. stream.next_in = static_cast(const_cast(source)); stream.avail_in = static_cast(source_length); stream.next_out = dest; stream.avail_out = static_cast(*dest_length); if (static_cast(stream.avail_out) != *dest_length) return Z_BUF_ERROR; // Cannot convert capturing lambdas to function pointers directly, hence the // structure. struct MallocFreeFunctions { void* (*malloc_fn)(size_t); void (*free_fn)(void*); } malloc_free = {malloc_fn, free_fn}; if (malloc_fn) { if (!free_fn) return Z_BUF_ERROR; auto zalloc = [](void* opaque, uInt items, uInt size) { return reinterpret_cast(opaque)->malloc_fn(items * size); }; auto zfree = [](void* opaque, void* address) { return reinterpret_cast(opaque)->free_fn(address); }; stream.zalloc = static_cast(zalloc); stream.zfree = static_cast(zfree); stream.opaque = static_cast(&malloc_free); } else { stream.zalloc = static_cast(0); stream.zfree = static_cast(0); stream.opaque = static_cast(0); } int err = deflateInit2(&stream, compression_level, Z_DEFLATED, ZlibStreamWrapperType(wrapper_type), kZlibMemoryLevel, Z_DEFAULT_STRATEGY); if (err != Z_OK) return err; // This has to exist outside of the if statement to prevent it going off the // stack before deflate(), which will use this object. gz_header gzip_header; if (wrapper_type == GZIP) { memset(&gzip_header, 0, sizeof(gzip_header)); err = deflateSetHeader(&stream, &gzip_header); if (err != Z_OK) return err; } err = deflate(&stream, Z_FINISH); if (err != Z_STREAM_END) { deflateEnd(&stream); return err == Z_OK ? Z_BUF_ERROR : err; } *dest_length = stream.total_out; err = deflateEnd(&stream); return err; } int GzipUncompressHelper(Bytef* dest, uLongf* dest_length, const Bytef* source, uLong source_length) { return UncompressHelper(GZIP, dest, dest_length, source, source_length); } // This code is taken almost verbatim from third_party/zlib/uncompr.c. The only // difference is inflateInit2() is called which allows different window bits to // be set. > 16 causes a gzip header to be emitted rather than a zlib header, // and negative causes no header to emitted. int UncompressHelper(WrapperType wrapper_type, Bytef* dest, uLongf* dest_length, const Bytef* source, uLong source_length) { z_stream stream; // FIXME(cavalcantii): z_const is not defined as 'const'. stream.next_in = static_cast(const_cast(source)); stream.avail_in = static_cast(source_length); if (static_cast(stream.avail_in) != source_length) return Z_BUF_ERROR; stream.next_out = dest; stream.avail_out = static_cast(*dest_length); if (static_cast(stream.avail_out) != *dest_length) return Z_BUF_ERROR; stream.zalloc = static_cast(0); stream.zfree = static_cast(0); int err = inflateInit2(&stream, ZlibStreamWrapperType(wrapper_type)); if (err != Z_OK) return err; err = inflate(&stream, Z_FINISH); if (err != Z_STREAM_END) { inflateEnd(&stream); if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) return Z_DATA_ERROR; return err; } *dest_length = stream.total_out; err = inflateEnd(&stream); return err; } } // namespace zlib_internal