Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/metaArray.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -979,19 +979,27 @@ MetaArray::M_ReadElements(METAIO_STREAM::ifstream * _fstream, void * _data, int
// If compressed we inflate
if (m_CompressedData)
{
// if m_CompressedElementDataSize is not defined we assume the size of the
// file is the size of the compressed data
// if m_CompressedElementDataSize is not defined we assume the compressed
// data runs from the current position to the end of the file
if (m_CompressedElementDataSize == 0)
{
const std::streampos dataPos = _fstream->tellg();
_fstream->seekg(0, std::ios::end);
m_CompressedElementDataSize = _fstream->tellg();
_fstream->seekg(0, std::ios::beg);
m_CompressedElementDataSize = static_cast<std::streamoff>(_fstream->tellg() - dataPos);
_fstream->seekg(dataPos);
}

auto * compr = new unsigned char[static_cast<size_t>(m_CompressedElementDataSize)];
_fstream->read(reinterpret_cast<char *>(compr), static_cast<size_t>(m_CompressedElementDataSize));

MET_PerformUncompression(compr, m_CompressedElementDataSize, static_cast<unsigned char *>(_data), readSize);
const bool uncompressed =
MET_PerformUncompression(compr, m_CompressedElementDataSize, static_cast<unsigned char *>(_data), readSize);
delete[] compr;
if (!uncompressed)
{
std::cerr << "MetaArray: M_ReadElements: could not uncompress element data" << '\n';
return false;
}
}
else // if not compressed
{
Expand Down
18 changes: 13 additions & 5 deletions src/metaImage.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -2622,15 +2622,16 @@ MetaImage::M_ReadElements(METAIO_STREAM::ifstream * _fstream, void * _data, std:
// If compressed we inflate
if (m_BinaryData && m_CompressedData)
{
// if m_CompressedDataSize is not defined we assume the size of the
// file is the size of the compressed data
// if m_CompressedDataSize is not defined we assume the compressed data
// runs from the current position to the end of the file
bool compressedDataDeterminedFromFile = false;
if (m_CompressedDataSize == 0)
{
compressedDataDeterminedFromFile = true;
const std::streampos dataPos = _fstream->tellg();
_fstream->seekg(0, std::ios::end);
m_CompressedDataSize = _fstream->tellg();
_fstream->seekg(0, std::ios::beg);
m_CompressedDataSize = static_cast<std::streamoff>(_fstream->tellg() - dataPos);
_fstream->seekg(dataPos);
}

auto * compr = new unsigned char[static_cast<size_t>(m_CompressedDataSize)];
Expand All @@ -2641,14 +2642,21 @@ MetaImage::M_ReadElements(METAIO_STREAM::ifstream * _fstream, void * _data, std:
return false;
}

MET_PerformUncompression(compr, m_CompressedDataSize, static_cast<unsigned char *>(_data), readSize);
const bool uncompressed =
MET_PerformUncompression(compr, m_CompressedDataSize, static_cast<unsigned char *>(_data), readSize);

if (compressedDataDeterminedFromFile)
{
m_CompressedDataSize = 0;
}

delete[] compr;

if (!uncompressed)
{
std::cerr << "MetaImage: M_ReadElements: could not uncompress element data" << '\n';
return false;
}
}
else // if not compressed
{
Expand Down
12 changes: 10 additions & 2 deletions src/metaUtils.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,10 @@ MET_PerformUncompression(const unsigned char * sourceCompressed,
d_stream.next_out = uncompressedData + dest_pos;
d_stream.avail_out = cur_remain_chunk;
err = inflate(&d_stream, Z_NO_FLUSH);
// Account for this call's output before any exit, including the final
// call that reports Z_STREAM_END.
uInt count_uncompressed = cur_remain_chunk - d_stream.avail_out;
dest_pos += count_uncompressed;
if (err == Z_STREAM_END || err < 0)
{
if (err != Z_STREAM_END && err != Z_BUF_ERROR) // Z_BUF_ERROR means there is still data to uncompress,
Expand All @@ -884,11 +888,15 @@ MET_PerformUncompression(const unsigned char * sourceCompressed,
}
break;
}
uInt count_uncompressed = cur_remain_chunk - d_stream.avail_out;
dest_pos += count_uncompressed;
} while (d_stream.avail_out == 0);
} while (err != Z_STREAM_END && err >= 0);
inflateEnd(&d_stream);
if (dest_pos != uncompressedDataSize)
{
std::cerr << "MET_PerformUncompression: expected " << uncompressedDataSize << " bytes, produced " << dest_pos
<< '\n';
return false;
}
return true;
}

Expand Down
1 change: 1 addition & 0 deletions src/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ MetaAddTest(testMeta10Contour)
MetaAddTest(testMeta11Form)
MetaAddTest(testMeta12Array)
MetaAddTest(testMeta13ImageList)
MetaAddTest(testMeta14ImageCompressed)
110 changes: 110 additions & 0 deletions src/tests/testMeta14ImageCompressed.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Regression tests for reading compressed element data.

#include <cstdio>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

#include <metaImage.h>

namespace
{

const char * const localName = "testMeta14_local.mha";

// Write a compressed single-file (LOCAL) image, then strip CompressedDataSize
// from its header so the reader has to determine the size itself.
bool
WriteLocalWithoutCompressedDataSize(int quantity)
{
{
std::vector<unsigned char> values(static_cast<size_t>(quantity));
for (int i = 0; i < quantity; ++i)
{
values[static_cast<size_t>(i)] = static_cast<unsigned char>(i % 251);
}
MetaImage image(quantity, 1, 1, 1, MET_UCHAR, 1, values.data());
image.CompressedData(true);
if (!image.Write(localName))
{
return false;
}
}

std::ifstream in(localName, std::ios::binary);
std::string contents((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
in.close();

const std::string key = "CompressedDataSize = ";
const size_t at = contents.find(key);
if (at == std::string::npos)
{
std::cout << " no CompressedDataSize field was written" << '\n';
return false;
}
const size_t eol = contents.find('\n', at);
contents.erase(at, eol - at + 1);

std::ofstream out(localName, std::ios::binary);
out.write(contents.data(), static_cast<std::streamsize>(contents.size()));
return true;
}

} // namespace

int
main(int, char *[])
{
const int quantity = 4096;

// A LOCAL image whose header omits CompressedDataSize: the compressed data
// starts after the header, not at the start of the file.
if (!WriteLocalWithoutCompressedDataSize(quantity))
{
std::cout << "Could not prepare the LOCAL test image: FAIL" << '\n';
return EXIT_FAILURE;
}
{
MetaImage image;
if (!image.Read(localName))
{
std::cout << "LOCAL image without CompressedDataSize failed to read: FAIL" << '\n';
return EXIT_FAILURE;
}
for (int i = 0; i < quantity; ++i)
{
const int expected = i % 251;
if (static_cast<int>(image.ElementData(i)) != expected)
{
std::cout << " element " << i << " is " << image.ElementData(i) << ", expected " << expected << '\n';
std::cout << "LOCAL image without CompressedDataSize read wrong values: FAIL" << '\n';
return EXIT_FAILURE;
}
}
}

// Truncating the compressed stream must be reported, not silently accepted.
{
std::ifstream in(localName, std::ios::binary);
std::string contents((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
in.close();
contents.resize(contents.size() - 16);
std::ofstream out("testMeta14_truncated.mha", std::ios::binary);
out.write(contents.data(), static_cast<std::streamsize>(contents.size()));
out.close();

MetaImage image;
if (image.Read("testMeta14_truncated.mha"))
{
std::cout << "Truncated compressed data reported success: FAIL" << '\n';
return EXIT_FAILURE;
}
}

std::remove(localName);
std::remove("testMeta14_truncated.mha");

std::cout << "Compressed element data tests: PASS" << '\n';
return EXIT_SUCCESS;
}
Loading