From 5cbc793994baf12e8c8070344bf9029df627e379 Mon Sep 17 00:00:00 2001 From: Brad Hards Date: Tue, 13 Feb 2024 19:47:18 +1100 Subject: [PATCH] wip: adding construction_method 2 support --- go/heif/heif.go | 2 + libheif/box.cc | 183 ++++++++++++++++++++++++++------------ libheif/box.h | 6 ++ libheif/error.cc | 2 + libheif/file.cc | 13 +-- libheif/file.h | 1 + libheif/heif.h | 2 + libheif/heif_emscripten.h | 1 + 8 files changed, 145 insertions(+), 65 deletions(-) diff --git a/go/heif/heif.go b/go/heif/heif.go index ad2fa5b641..303e5b2eeb 100644 --- a/go/heif/heif.go +++ b/go/heif/heif.go @@ -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. diff --git a/libheif/box.cc b/libheif/box.cc index 1375f2bb00..892d8e1b48 100644 --- a/libheif/box.cc +++ b/libheif/box.cc @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -1188,76 +1189,81 @@ std::string Box_iloc::dump(Indent& indent) const return sstr.str(); } +bool Box_iloc::read_extent(const Item& item, + const std::shared_ptr& istr, + const Box_iloc::Extent extent, + std::vector* 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& istr, - const std::shared_ptr& idat, - std::vector* 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(old_size + extent.length)); + success = istr->read((char*) dest->data() + old_size, static_cast(extent.length)); + assert(success); + return success; +} - // --- read data +Error Box_iloc::read_data(const Item& item, + const std::shared_ptr& istr, + const std::shared_ptr& idat, + const std::shared_ptr& dinf, + std::vector* 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(old_size + extent.length)); - success = istr->read((char*) dest->data() + old_size, static_cast(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; } @@ -1265,7 +1271,7 @@ Error Box_iloc::read_data(const Item& item, 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, @@ -1273,6 +1279,65 @@ Error Box_iloc::read_data(const Item& item, 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 dref = std::dynamic_pointer_cast(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 dataentry = dref->get_all_child_boxes()[(item.data_reference_index - 1)]; + if (dataentry->get_short_type() == fourcc_to_uint32("url ")) { + std::shared_ptr urlBox = std::dynamic_pointer_cast(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(new std::ifstream(convert_utf8_path_to_utf16(location).c_str(), std::ios_base::binary)); +#else + auto datafile_istr = std::unique_ptr(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(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"; diff --git a/libheif/box.h b/libheif/box.h index 0c0b148d12..7b41345d89 100644 --- a/libheif/box.h +++ b/libheif/box.h @@ -391,6 +391,7 @@ class Box_iloc : public FullBox Error read_data(const Item& item, const std::shared_ptr& istr, const std::shared_ptr&, + const std::shared_ptr&, std::vector* dest) const; void set_min_version(uint8_t min_version) { m_user_defined_min_version = min_version; } @@ -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& istr, + const Box_iloc::Extent extent, + std::vector* dest) const; int m_idat_offset = 0; // only for writing: offset of next data array }; @@ -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; diff --git a/libheif/error.cc b/libheif/error.cc index db8d6eaaa0..f0b7e49c0f 100644 --- a/libheif/error.cc +++ b/libheif/error.cc @@ -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: diff --git a/libheif/file.cc b/libheif/file.cc index 4f2a509842..65e5ca8040 100644 --- a/libheif/file.cc +++ b/libheif/file.cc @@ -377,6 +377,7 @@ Error HeifFile::parse_heif_file(BitstreamRange& range) heif_suberror_No_iinf_box); } + m_dinf_box = std::dynamic_pointer_cast(m_meta_box->get_child_box(fourcc("dinf"))); // --- build list of images @@ -775,7 +776,7 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector* 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 @@ -811,7 +812,7 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector* 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")) { @@ -837,7 +838,7 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector* } } - 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> properties; @@ -871,7 +872,7 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector* // 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" || @@ -886,7 +887,7 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector* #if WITH_DEFLATE_HEADER_COMPRESSION read_uncompressed = false; std::vector 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, @@ -897,7 +898,7 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector* } 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); } } diff --git a/libheif/file.h b/libheif/file.h index 881aabccc6..2ea8a22f27 100644 --- a/libheif/file.h +++ b/libheif/file.h @@ -222,6 +222,7 @@ class HeifFile std::shared_ptr m_iinf_box; std::shared_ptr m_iprp_box; + std::shared_ptr m_dinf_box; std::map > m_infe_boxes; diff --git a/libheif/heif.h b/libheif/heif.h index 1c3eb7089d..da968ff0ab 100644 --- a/libheif/heif.h +++ b/libheif/heif.h @@ -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 --- diff --git a/libheif/heif_emscripten.h b/libheif/heif_emscripten.h index fe79be19f6..27bcc81f43 100644 --- a/libheif/heif_emscripten.h +++ b/libheif/heif_emscripten.h @@ -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)