Skip to content

Commit

Permalink
wip: adding construction_method 2 support
Browse files Browse the repository at this point in the history
  • Loading branch information
bradh committed Feb 14, 2024
1 parent b671d75 commit 5cbc793
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 65 deletions.
2 changes: 2 additions & 0 deletions go/heif/heif.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ const (

SuberrorNoIrefBox = C.heif_suberror_No_iref_box

SuberrorNoDinfBox = C.heif_suberror_No_dinf_box

SuberrorNoPictHandler = C.heif_suberror_No_pict_handler

// An item property referenced in the 'ipma' box is not existing in the 'ipco' container.
Expand Down
183 changes: 124 additions & 59 deletions libheif/box.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <iostream>
#include <algorithm>
#include <cstring>
#include <fstream>
#include <set>
#include <cassert>

Expand Down Expand Up @@ -1188,91 +1189,155 @@ std::string Box_iloc::dump(Indent& indent) const
return sstr.str();
}

bool Box_iloc::read_extent(const Item& item,
const std::shared_ptr<StreamReader>& istr,
const Box_iloc::Extent extent,
std::vector<uint8_t>* dest) const {
// --- security check that we do not allocate too much memory
size_t old_size = dest->size();
if (MAX_MEMORY_BLOCK_SIZE - old_size < extent.length) {
std::stringstream sstr;
sstr << "iloc box contained " << extent.length << " bytes, total memory size would be "
<< (old_size + extent.length) << " bytes, exceeding the security limit of "
<< MAX_MEMORY_BLOCK_SIZE << " bytes";

Error Box_iloc::read_data(const Item& item,
const std::shared_ptr<StreamReader>& istr,
const std::shared_ptr<Box_idat>& idat,
std::vector<uint8_t>* dest) const
{
// TODO: this function should always append the data to the output vector as this is used when
// the image data is concatenated with data in a configuration box. However, it seems that
// this function clears the array in some cases. This should be corrected.

for (const auto& extent : item.extents) {
if (item.construction_method == 0) {

// --- security check that we do not allocate too much memory

size_t old_size = dest->size();
if (MAX_MEMORY_BLOCK_SIZE - old_size < extent.length) {
std::stringstream sstr;
sstr << "iloc box contained " << extent.length << " bytes, total memory size would be "
<< (old_size + extent.length) << " bytes, exceeding the security limit of "
<< MAX_MEMORY_BLOCK_SIZE << " bytes";

return Error(heif_error_Memory_allocation_error,
heif_suberror_Security_limit_exceeded,
sstr.str());
}
return Error(heif_error_Memory_allocation_error,
heif_suberror_Security_limit_exceeded,
sstr.str());
}

// --- make sure that all data is available
if (extent.offset > MAX_FILE_POS ||
item.base_offset > MAX_FILE_POS ||
extent.length > MAX_FILE_POS) {
return Error(heif_error_Invalid_input,
heif_suberror_Security_limit_exceeded,
"iloc data pointers out of allowed range");
}

// --- make sure that all data is available
StreamReader::grow_status status = istr->wait_for_file_size(extent.offset + item.base_offset + extent.length);
if (status == StreamReader::size_beyond_eof) {
// Out-of-bounds
// TODO: I think we should not clear this. Maybe we want to try reading again later and
// hence should not lose the data already read.
dest->clear();

if (extent.offset > MAX_FILE_POS ||
item.base_offset > MAX_FILE_POS ||
extent.length > MAX_FILE_POS) {
return Error(heif_error_Invalid_input,
heif_suberror_Security_limit_exceeded,
"iloc data pointers out of allowed range");
}
std::stringstream sstr;
sstr << "Extent in iloc box references data outside of file bounds "
<< "(points to file position " << extent.offset + item.base_offset << ")\n";

StreamReader::grow_status status = istr->wait_for_file_size(extent.offset + item.base_offset + extent.length);
if (status == StreamReader::size_beyond_eof) {
// Out-of-bounds
// TODO: I think we should not clear this. Maybe we want to try reading again later and
// hence should not lose the data already read.
dest->clear();
return Error(heif_error_Invalid_input,
heif_suberror_End_of_data,
sstr.str());
}
else if (status == StreamReader::timeout) {
// TODO: maybe we should introduce some 'Recoverable error' instead of 'Invalid input'
return Error(heif_error_Invalid_input,
heif_suberror_End_of_data);
}

std::stringstream sstr;
sstr << "Extent in iloc box references data outside of file bounds "
<< "(points to file position " << extent.offset + item.base_offset << ")\n";
// --- move file pointer to start of data

return Error(heif_error_Invalid_input,
heif_suberror_End_of_data,
sstr.str());
}
else if (status == StreamReader::timeout) {
// TODO: maybe we should introduce some 'Recoverable error' instead of 'Invalid input'
return Error(heif_error_Invalid_input,
heif_suberror_End_of_data);
}
bool success = istr->seek(extent.offset + item.base_offset);
assert(success);
(void) success;

// --- move file pointer to start of data

bool success = istr->seek(extent.offset + item.base_offset);
assert(success);
(void) success;
// --- read data

dest->resize(static_cast<size_t>(old_size + extent.length));
success = istr->read((char*) dest->data() + old_size, static_cast<size_t>(extent.length));
assert(success);
return success;
}

// --- read data
Error Box_iloc::read_data(const Item& item,
const std::shared_ptr<StreamReader>& istr,
const std::shared_ptr<Box_idat>& idat,
const std::shared_ptr<Box_dinf>& dinf,
std::vector<uint8_t>* dest) const
{
// TODO: this function should always append the data to the output vector as this is used when
// the image data is concatenated with data in a configuration box. However, it seems that
// this function clears the array in some cases. This should be corrected.

dest->resize(static_cast<size_t>(old_size + extent.length));
success = istr->read((char*) dest->data() + old_size, static_cast<size_t>(extent.length));
for (const auto& extent : item.extents) {
if (item.construction_method == 0) {
bool success = read_extent(item, istr, extent, dest);
assert(success);
(void) success;
}
else if (item.construction_method == 1) {
if (!idat) {
return Error(heif_error_Invalid_input,
heif_suberror_No_idat_box,
"idat box referenced in iref box is not present in file");
"idat box referenced in iloc box is not present in file");
}

idat->read_data(istr,
extent.offset + item.base_offset,
extent.length,
*dest);
}
else if (item.construction_method == 2) {
if (item.data_reference_index == 0) {
bool success = read_extent(item, istr, extent, dest);
assert(success);
(void) success;
} else {
if (!dinf) {
return Error(heif_error_Invalid_input,
heif_suberror_No_dinf_box,
"dinf box referenced in iloc box is not present in file");

}
if (dinf->get_child_boxes(fourcc_to_uint32("dref")).size() != 1) {
return Error(heif_error_Invalid_input,
heif_suberror_No_dinf_box,
"dinf box is incomplete - missing dref child box");
}
std::shared_ptr<Box_dref> dref = std::dynamic_pointer_cast<Box_dref>(dinf->get_child_boxes(fourcc_to_uint32("dref"))[0]);
if (dref->get_all_child_boxes().size() < item.data_reference_index) {
std::stringstream sstr;
sstr << "Item construction method requires data references that are not present";
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_item_construction_method,
sstr.str());
}
std::shared_ptr<Box> dataentry = dref->get_all_child_boxes()[(item.data_reference_index - 1)];
if (dataentry->get_short_type() == fourcc_to_uint32("url ")) {
std::shared_ptr<Box_url> urlBox = std::dynamic_pointer_cast<Box_url>(dataentry);
if (urlBox->get_flags() == 0x000001) {
bool success = read_extent(item, istr, extent, dest);
assert(success);
(void) success;
} else {
std::string location = urlBox->get_location();
#if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER)
auto input_stream_istr = std::unique_ptr<std::istream>(new std::ifstream(convert_utf8_path_to_utf16(location).c_str(), std::ios_base::binary));
#else
auto datafile_istr = std::unique_ptr<std::istream>(new std::ifstream(location.c_str(), std::ios_base::binary));
#endif
if (!datafile_istr->good()) {
std::stringstream sstr;
sstr << "Error opening file: " << location << ", " << strerror(errno) << " (" << errno << ")\n";
return Error(heif_error_Input_does_not_exist, heif_suberror_Unspecified, sstr.str());
}

auto datafileReader = std::make_shared<StreamReader_istream>(std::move(datafile_istr));
bool success = read_extent(item, datafileReader, extent, dest);
assert(success);
(void) success;
}
} else {
std::stringstream sstr;
sstr << "Item construction method 2 with data reference type " << dataentry->get_type_string() << "is not implemented";
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_item_construction_method,
sstr.str());
}
}
}
else {
std::stringstream sstr;
sstr << "Item construction method " << (int) item.construction_method << " not implemented";
Expand Down
6 changes: 6 additions & 0 deletions libheif/box.h
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ class Box_iloc : public FullBox
Error read_data(const Item& item,
const std::shared_ptr<StreamReader>& istr,
const std::shared_ptr<class Box_idat>&,
const std::shared_ptr<class Box_dinf>&,
std::vector<uint8_t>* dest) const;

void set_min_version(uint8_t min_version) { m_user_defined_min_version = min_version; }
Expand Down Expand Up @@ -430,6 +431,10 @@ class Box_iloc : public FullBox
uint8_t m_index_size = 0;

void patch_iloc_header(StreamWriter& writer) const;
bool read_extent(const Item& item,
const std::shared_ptr<StreamReader>& istr,
const Box_iloc::Extent extent,
std::vector<uint8_t>* dest) const;

int m_idat_offset = 0; // only for writing: offset of next data array
};
Expand Down Expand Up @@ -857,6 +862,7 @@ class Box_url : public FullBox
{
public:
std::string dump(Indent&) const override;
std::string get_location() const { return m_location; };

protected:
Error parse(BitstreamRange& range) override;
Expand Down
2 changes: 2 additions & 0 deletions libheif/error.cc
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ const char* Error::get_error_string(heif_suberror_code err)
return "No 'iref' box";
case heif_suberror_No_infe_box:
return "No 'infe' box";
case heif_suberror_No_dinf_box:
return "No 'dinf' box";
case heif_suberror_No_pict_handler:
return "Not a 'pict' handler";
case heif_suberror_Ipma_box_references_nonexisting_property:
Expand Down
13 changes: 7 additions & 6 deletions libheif/file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ Error HeifFile::parse_heif_file(BitstreamRange& range)
heif_suberror_No_iinf_box);
}

m_dinf_box = std::dynamic_pointer_cast<Box_dinf>(m_meta_box->get_child_box(fourcc("dinf")));


// --- build list of images
Expand Down Expand Up @@ -775,7 +776,7 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector<uint8_t>*
heif_suberror_No_item_data);
}

error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data);
error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, m_dinf_box, data);
}
else if (item_type == "av01") {
// --- --- --- AV1
Expand Down Expand Up @@ -811,7 +812,7 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector<uint8_t>*
heif_suberror_No_item_data);
}

error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data);
error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, m_dinf_box, data);
}
else if (item_type == "jpeg" ||
(item_type == "mime" && get_content_type(ID) == "image/jpeg")) {
Expand All @@ -837,7 +838,7 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector<uint8_t>*
}
}

error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data);
error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, m_dinf_box, data);
}
else if (item_type == "j2k1") {
std::vector<std::shared_ptr<Box>> properties;
Expand Down Expand Up @@ -871,7 +872,7 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector<uint8_t>*
// heif_suberror_No_item_data);
// }

error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data);
error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, m_dinf_box, data);
}
else if (true || // fallback case for all kinds of generic metadata (e.g. 'iptc')
item_type == "grid" ||
Expand All @@ -886,7 +887,7 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector<uint8_t>*
#if WITH_DEFLATE_HEADER_COMPRESSION
read_uncompressed = false;
std::vector<uint8_t> compressed_data;
error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data);
error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, m_dinf_box, &compressed_data);
*data = inflate(compressed_data);
#else
return Error(heif_error_Unsupported_feature,
Expand All @@ -897,7 +898,7 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector<uint8_t>*
}

if (read_uncompressed) {
error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data);
error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, m_dinf_box, data);
}
}

Expand Down
1 change: 1 addition & 0 deletions libheif/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ class HeifFile
std::shared_ptr<Box_iinf> m_iinf_box;

std::shared_ptr<Box_iprp> m_iprp_box;
std::shared_ptr<Box_dinf> m_dinf_box;

std::map<heif_item_id, std::shared_ptr<Box_infe> > m_infe_boxes;

Expand Down
2 changes: 2 additions & 0 deletions libheif/heif.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ enum heif_suberror_code
// Invalid JPEG 2000 codestream - usually a missing marker
heif_suberror_Invalid_J2K_codestream = 140,

heif_suberror_No_dinf_box = 141,


// --- Memory_allocation_error ---

Expand Down
1 change: 1 addition & 0 deletions libheif/heif_emscripten.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ EMSCRIPTEN_BINDINGS(libheif) {
.value("heif_suberror_No_iinf_box", heif_suberror_No_iinf_box)
.value("heif_suberror_No_iprp_box", heif_suberror_No_iprp_box)
.value("heif_suberror_No_iref_box", heif_suberror_No_iref_box)
.value("heif_suberror_No_dinf_box", heif_suberror_No_dinf_box)
.value("heif_suberror_No_pict_handler", heif_suberror_No_pict_handler)
.value("heif_suberror_Ipma_box_references_nonexisting_property", heif_suberror_Ipma_box_references_nonexisting_property)
.value("heif_suberror_No_properties_assigned_to_item", heif_suberror_No_properties_assigned_to_item)
Expand Down

0 comments on commit 5cbc793

Please sign in to comment.