/* * Copyright (C) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "Compression.h" #include "CheckedArithmetic.h" #if USE(ZLIB) #include namespace WTF { static void* zAlloc(void*, uint32_t count, uint32_t size) { CheckedSize allocSize = count; allocSize *= size; if (allocSize.hasOverflowed()) return Z_NULL; void* result = 0; if (tryFastMalloc(allocSize.unsafeGet()).getValue(result)) return result; return Z_NULL; } static void zFree(void*, void* data) { fastFree(data); } PassOwnPtr GenericCompressedData::create(const uint8_t* data, size_t dataLength) { enum { MinimumSize = sizeof(GenericCompressedData) * 8 }; if (!data || dataLength < MinimumSize) return nullptr; z_stream stream; memset(&stream, 0, sizeof(stream)); stream.zalloc = zAlloc; stream.zfree = zFree; stream.data_type = Z_BINARY; stream.opaque = Z_NULL; stream.avail_in = dataLength; stream.next_in = const_cast(data); size_t currentOffset = OBJECT_OFFSETOF(GenericCompressedData, m_data); size_t currentCapacity = fastMallocGoodSize(MinimumSize); Bytef* compressedData = static_cast(fastMalloc(currentCapacity)); memset(compressedData, 0, sizeof(GenericCompressedData)); stream.next_out = compressedData + currentOffset; stream.avail_out = currentCapacity - currentOffset; deflateInit(&stream, Z_BEST_COMPRESSION); while (true) { int deflateResult = deflate(&stream, Z_FINISH); if (deflateResult == Z_OK || !stream.avail_out) { size_t newCapacity = 0; currentCapacity -= stream.avail_out; if (!stream.avail_in) newCapacity = currentCapacity + 8; else { // Determine average capacity size_t compressedContent = stream.next_in - data; double expectedSize = static_cast(dataLength) * compressedContent / currentCapacity; // Expand capacity by at least 8 bytes so we're always growing, and to // compensate for any exaggerated ideas of how effectively we'll compress // data in the future. newCapacity = std::max(static_cast(expectedSize + 8), currentCapacity + 8); } newCapacity = fastMallocGoodSize(newCapacity); if (newCapacity >= dataLength) goto fail; compressedData = static_cast(fastRealloc(compressedData, newCapacity)); currentOffset = currentCapacity - stream.avail_out; stream.next_out = compressedData + currentOffset; stream.avail_out = newCapacity - currentCapacity; currentCapacity = newCapacity; continue; } if (deflateResult == Z_STREAM_END) { ASSERT(!stream.avail_in); break; } ASSERT_NOT_REACHED(); fail: deflateEnd(&stream); fastFree(compressedData); return nullptr; } deflateEnd(&stream); static int64_t totalCompressed = 0; static int64_t totalInput = 0; totalCompressed += currentCapacity; totalInput += dataLength; GenericCompressedData* result = new (compressedData) GenericCompressedData(dataLength, stream.total_out); return adoptPtr(result); } bool GenericCompressedData::decompress(uint8_t* destination, size_t bufferSize, size_t* decompressedByteCount) { if (decompressedByteCount) *decompressedByteCount = 0; z_stream stream; memset(&stream, 0, sizeof(stream)); stream.zalloc = zAlloc; stream.zfree = zFree; stream.data_type = Z_BINARY; stream.opaque = Z_NULL; stream.next_out = destination; stream.avail_out = bufferSize; stream.next_in = m_data; stream.avail_in = compressedSize(); if (inflateInit(&stream) != Z_OK) { ASSERT_NOT_REACHED(); return false; } int inflateResult = inflate(&stream, Z_FINISH); inflateEnd(&stream); ASSERT(stream.total_out <= bufferSize); if (decompressedByteCount) *decompressedByteCount = stream.total_out; if (inflateResult != Z_STREAM_END) { ASSERT_NOT_REACHED(); return false; } return true; } } #else namespace WTF { PassOwnPtr GenericCompressedData::create(const uint8_t*, size_t) { return nullptr; } bool GenericCompressedData::decompress(uint8_t*, size_t) { return false; } } #endif