/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2023 Osimis S.A., Belgium
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program. If not, see
* .
**/
#include "../PrecompiledHeaders.h"
#include "GzipCompressor.h"
#include
#include
#include
#include "../OrthancException.h"
#include "../Logging.h"
namespace Orthanc
{
uint64_t GzipCompressor::GuessUncompressedSize(const void* compressed,
size_t compressedSize)
{
/**
* "Is there a way to find out the size of the original file which
* is inside a GZIP file? [...] There is no truly reliable way,
* other than gunzipping the stream. You do not need to save the
* result of the decompression, so you can determine the size by
* simply reading and decoding the entire file without taking up
* space with the decompressed result.
*
* There is an unreliable way to determine the uncompressed size,
* which is to look at the last four bytes of the gzip file, which
* is the uncompressed length of that entry modulo 232 in little
* endian order.
*
* It is unreliable because a) the uncompressed data may be longer
* than 2^32 bytes, and b) the gzip file may consist of multiple
* gzip streams, in which case you would find the length of only
* the last of those streams.
*
* If you are in control of the source of the gzip files, you know
* that they consist of single gzip streams, and you know that
* they are less than 2^32 bytes uncompressed, then and only then
* can you use those last four bytes with confidence."
*
* http://stackoverflow.com/a/9727599/881731
**/
if (compressedSize < 4)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
const uint8_t* p = reinterpret_cast(compressed) + compressedSize - 4;
return ((static_cast(p[0]) << 0) +
(static_cast(p[1]) << 8) +
(static_cast(p[2]) << 16) +
(static_cast(p[3]) << 24));
}
GzipCompressor::GzipCompressor()
{
SetPrefixWithUncompressedSize(false);
}
void GzipCompressor::Compress(std::string& compressed,
const void* uncompressed,
size_t uncompressedSize)
{
uLongf compressedSize = compressBound(static_cast(uncompressedSize))
+ 1024 /* security margin */;
if (compressedSize == 0)
{
compressedSize = 1;
}
uint8_t* target;
if (HasPrefixWithUncompressedSize())
{
compressed.resize(compressedSize + sizeof(uint64_t));
target = reinterpret_cast(&compressed[0]) + sizeof(uint64_t);
}
else
{
compressed.resize(compressedSize);
target = reinterpret_cast(&compressed[0]);
}
z_stream stream;
memset(&stream, 0, sizeof(stream));
stream.next_in = const_cast(reinterpret_cast(uncompressed));
stream.next_out = reinterpret_cast(target);
stream.avail_in = static_cast(uncompressedSize);
stream.avail_out = static_cast(compressedSize);
// Ensure no overflow (if the buffer is too large for the current archicture)
if (static_cast(stream.avail_in) != uncompressedSize ||
static_cast(stream.avail_out) != compressedSize)
{
throw OrthancException(ErrorCode_NotEnoughMemory);
}
// Initialize the compression engine
int error = deflateInit2(&stream,
GetCompressionLevel(),
Z_DEFLATED,
MAX_WBITS + 16, // ask for gzip output
8, // default memory level
Z_DEFAULT_STRATEGY);
if (error != Z_OK)
{
// Cannot initialize zlib
compressed.clear();
throw OrthancException(ErrorCode_InternalError);
}
// Compress the input buffer
error = deflate(&stream, Z_FINISH);
if (error != Z_STREAM_END)
{
deflateEnd(&stream);
compressed.clear();
switch (error)
{
case Z_MEM_ERROR:
throw OrthancException(ErrorCode_NotEnoughMemory);
default:
throw OrthancException(ErrorCode_InternalError);
}
}
size_t size = stream.total_out;
if (deflateEnd(&stream) != Z_OK)
{
throw OrthancException(ErrorCode_InternalError);
}
// The compression was successful
if (HasPrefixWithUncompressedSize())
{
uint64_t s = static_cast(uncompressedSize);
memcpy(&compressed[0], &s, sizeof(uint64_t));
compressed.resize(size + sizeof(uint64_t));
}
else
{
compressed.resize(size);
}
}
void GzipCompressor::Uncompress(std::string& uncompressed,
const void* compressed,
size_t compressedSize)
{
uint64_t uncompressedSize;
const uint8_t* source = reinterpret_cast(compressed);
if (HasPrefixWithUncompressedSize())
{
uncompressedSize = ReadUncompressedSizePrefix(compressed, compressedSize);
source += sizeof(uint64_t);
compressedSize -= sizeof(uint64_t);
}
else
{
uncompressedSize = GuessUncompressedSize(compressed, compressedSize);
}
try
{
uncompressed.resize(static_cast(uncompressedSize));
}
catch (...)
{
throw OrthancException(ErrorCode_NotEnoughMemory);
}
z_stream stream;
memset(&stream, 0, sizeof(stream));
char dummy = '\0'; // zlib does not like NULL output buffers (even if the uncompressed data is empty)
stream.next_in = const_cast(source);
stream.next_out = reinterpret_cast(uncompressedSize == 0 ? &dummy : &uncompressed[0]);
stream.avail_in = static_cast(compressedSize);
stream.avail_out = static_cast(uncompressedSize);
// Ensure no overflow (if the buffer is too large for the current archicture)
if (static_cast(stream.avail_in) != compressedSize ||
static_cast(stream.avail_out) != uncompressedSize)
{
throw OrthancException(ErrorCode_NotEnoughMemory);
}
// Initialize the compression engine
int error = inflateInit2(&stream,
MAX_WBITS + 16); // this is a gzip input
if (error != Z_OK)
{
// Cannot initialize zlib
uncompressed.clear();
throw OrthancException(ErrorCode_InternalError);
}
// Uncompress the input buffer
error = inflate(&stream, Z_FINISH);
if (error != Z_STREAM_END)
{
inflateEnd(&stream);
uncompressed.clear();
switch (error)
{
case Z_MEM_ERROR:
throw OrthancException(ErrorCode_NotEnoughMemory);
case Z_BUF_ERROR:
case Z_NEED_DICT:
throw OrthancException(ErrorCode_BadFileFormat);
default:
throw OrthancException(ErrorCode_InternalError);
}
}
size_t size = stream.total_out;
if (inflateEnd(&stream) != Z_OK)
{
uncompressed.clear();
throw OrthancException(ErrorCode_InternalError);
}
if (size != uncompressedSize)
{
uncompressed.clear();
// The uncompressed size was not that properly guess, presumably
// because of a file size over 4GB. Should fallback to
// stream-based decompression.
throw OrthancException(ErrorCode_NotImplemented,
"The uncompressed size of a gzip-encoded buffer was not properly guessed");
}
}
}