Commit 542ccdfb for libheif
commit 542ccdfbc322cdb5b543e25083e038d17df63728
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Sat Sep 5 01:57:51 2026 +0200
Return the item's own property position from add_property (GHSA-5w8x-856j-7x7c)
heif_item_add_*_property() returned the index of the property box in the file-wide
'ipco' container, while the property getters address the properties of a single item.
The two numberings only coincide when 'ipco' holds exactly the properties of that one
item, which is the case for a single image, but not for any file with a second item.
Reading a property back with the returned id then indexed past the end of the item's
property list, and the out-of-range check in HeifContext::find_property() built an
Error object without returning it, so nothing stopped the access.
Box_ipma::add_property_for_item_ID() now returns the position of the association within
the item's own property list, which is what the getters subscript and what the
enumerators heif_item_get_properties_of_type() and
heif_item_get_transformation_properties() already hand out. This also resolves the
question in the TODO in ImageItem::add_property(): the id stays correct when the
property box is deduplicated.
Also:
- restore the missing 'return' in HeifContext::find_property()
- refuse to add properties to a context that was read from a file, like
HeifContext::write() already does. The item data still lives in the input file, so
such a context cannot be written out again
- refuse instead of truncating when a property index does not fit into the 16 bits that
'ipma' stores, and propagate that error out of add_ispe_property(),
add_orientation_properties() and set_auxC_property()
Reported by hackerman70000. The reported scenario (annotating a context read from an
untrusted file) is not supported API use, so this is not a vulnerability, but the same
defect is reachable when building a file from scratch: encoding an image with a
thumbnail and then adding a property to the primary item was enough to crash.
diff --git a/libheif/api/libheif/heif_experimental.cc b/libheif/api/libheif/heif_experimental.cc
index 0dc96df2..a6a5cae1 100644
--- a/libheif/api/libheif/heif_experimental.cc
+++ b/libheif/api/libheif/heif_experimental.cc
@@ -169,10 +169,13 @@ heif_error heif_item_add_property_camera_intrinsic_matrix(const heif_context* co
auto cmin = std::make_shared<Box_cmin>();
cmin->set_intrinsic_matrix(matrix->matrix);
- heif_property_id id = context->context->add_property(itemId, cmin, false);
+ auto id = context->context->add_property(itemId, cmin, false);
+ if (!id) {
+ return id.error_struct(context->context.get());
+ }
if (out_propertyId) {
- *out_propertyId = id;
+ *out_propertyId = *id;
}
return heif_error_success;
diff --git a/libheif/api/libheif/heif_properties.cc b/libheif/api/libheif/heif_properties.cc
index d43a5d0d..80c92ff8 100644
--- a/libheif/api/libheif/heif_properties.cc
+++ b/libheif/api/libheif/heif_properties.cc
@@ -184,10 +184,13 @@ heif_error heif_item_add_property_user_description(const heif_context* context,
udes->set_description(description->description ? description->description : "");
udes->set_tags(description->tags ? description->tags : "");
- heif_property_id id = context->context->add_property(itemId, udes, false);
+ auto id = context->context->add_property(itemId, udes, false);
+ if (!id) {
+ return id.error_struct(context->context.get());
+ }
if (out_propertyId) {
- *out_propertyId = id;
+ *out_propertyId = *id;
}
return heif_error_success;
@@ -286,10 +289,13 @@ heif_error heif_item_add_raw_property(const heif_context* context,
std::vector<uint8_t> data_vector(data, data + size);
raw_box->set_raw_data(data_vector);
- heif_property_id id = context->context->add_property(itemId, raw_box, is_essential != 0);
+ auto id = context->context->add_property(itemId, raw_box, is_essential != 0);
+ if (!id) {
+ return id.error_struct(context->context.get());
+ }
if (out_propertyId) {
- *out_propertyId = id;
+ *out_propertyId = *id;
}
return heif_error_success;
diff --git a/libheif/api/libheif/heif_tai_timestamps.cc b/libheif/api/libheif/heif_tai_timestamps.cc
index b5b4a9a1..37a580d0 100644
--- a/libheif/api/libheif/heif_tai_timestamps.cc
+++ b/libheif/api/libheif/heif_tai_timestamps.cc
@@ -117,10 +117,13 @@ heif_error heif_item_set_property_tai_clock_info(heif_context* ctx,
auto taic = std::make_shared<Box_taic>();
taic->set_from_tai_clock_info(clock);
- heif_property_id id = ctx->context->add_property(itemId, taic, false);
+ auto id = ctx->context->add_property(itemId, taic, false);
+ if (!id) {
+ return id.error_struct(ctx->context.get());
+ }
if (out_propertyId) {
- *out_propertyId = id;
+ *out_propertyId = *id;
}
return heif_error_success;
@@ -198,10 +201,13 @@ heif_error heif_item_set_property_tai_timestamp(heif_context* ctx,
auto itai = std::make_shared<Box_itai>();
itai->set_from_tai_timestamp_packet(timestamp);
- heif_property_id id = ctx->context->add_property(itemId, itai, false);
+ auto id = ctx->context->add_property(itemId, itai, false);
+ if (!id) {
+ return id.error_struct(ctx->context.get());
+ }
if (out_propertyId) {
- *out_propertyId = id;
+ *out_propertyId = *id;
}
return heif_error_success;
diff --git a/libheif/box.cc b/libheif/box.cc
index ebb16093..d3bdb580 100644
--- a/libheif/box.cc
+++ b/libheif/box.cc
@@ -3432,8 +3432,8 @@ bool Box_ipma::is_property_essential_for_item(heif_item_id itemId, int propertyI
}
-void Box_ipma::add_property_for_item_ID(heif_item_id itemID,
- PropertyAssociation assoc)
+heif_property_id Box_ipma::add_property_for_item_ID(heif_item_id itemID,
+ PropertyAssociation assoc)
{
size_t idx;
for (idx = 0; idx < m_entries.size(); idx++) {
@@ -3452,7 +3452,7 @@ void Box_ipma::add_property_for_item_ID(heif_item_id itemID,
// If the property is already associated with the item, skip.
for (auto const& a : m_entries[idx].associations) {
if (a.property_index == assoc.property_index) {
- return;
+ return get_property_id_for_item_ID(itemID, assoc.property_index);
}
// TODO: should we check that the essential flag matches and return an internal error if not?
@@ -3460,6 +3460,43 @@ void Box_ipma::add_property_for_item_ID(heif_item_id itemID,
// add the property association
m_entries[idx].associations.push_back(assoc);
+
+ return get_property_id_for_item_ID(itemID, assoc.property_index);
+}
+
+
+heif_property_id Box_ipma::get_property_id_for_item_ID(heif_item_id itemID, uint16_t property_index) const
+{
+ // Index 0 means "no property". It is skipped by Box_ipco::get_properties_for_item_ID() and
+ // hence has no position in the item's property list.
+ if (property_index == 0) {
+ return 0;
+ }
+
+ for (const auto& entry : m_entries) {
+ if (entry.item_ID != itemID) {
+ continue;
+ }
+
+ // Count the associations the way Box_ipco::get_properties_for_item_ID() does, i.e. skipping
+ // the associations with index 0, so that the returned id indexes the vector it produces.
+ heif_property_id id = 0;
+ for (const auto& assoc : entry.associations) {
+ if (assoc.property_index == 0) {
+ continue;
+ }
+
+ id++;
+
+ if (assoc.property_index == property_index) {
+ return id;
+ }
+ }
+
+ break;
+ }
+
+ return 0;
}
diff --git a/libheif/box.h b/libheif/box.h
index 30663a06..424aebd1 100644
--- a/libheif/box.h
+++ b/libheif/box.h
@@ -835,8 +835,18 @@ public:
bool is_property_essential_for_item(heif_item_id itemId, int propertyIndex) const;
- void add_property_for_item_ID(heif_item_id itemID,
- PropertyAssociation assoc);
+ // Associates the property with the item and returns the 1-based position of the property
+ // within this item's property list, i.e. the 'heif_property_id' that the public API uses.
+ // Note that this is not the index of the property in the 'ipco' box, as one 'ipco' is shared
+ // by all items of the file. If the property is already associated with the item, the position
+ // of the existing association is returned.
+ heif_property_id add_property_for_item_ID(heif_item_id itemID,
+ PropertyAssociation assoc);
+
+ // Returns the 1-based position of the property with the given 'ipco' index within the item's
+ // property list, or 0 if the property is not associated with the item. This matches the
+ // indices into the vector filled by Box_ipco::get_properties_for_item_ID().
+ heif_property_id get_property_id_for_item_ID(heif_item_id itemID, uint16_t property_index) const;
void derive_box_version() override;
diff --git a/libheif/context.cc b/libheif/context.cc
index 3b3e1c10..82158753 100644
--- a/libheif/context.cc
+++ b/libheif/context.cc
@@ -1770,7 +1770,9 @@ Result<std::shared_ptr<ImageItem>> HeifContext::encode_image(const std::shared_p
std::shared_ptr<ImageItem> heif_alpha_image = *alphaEncodingResult;
m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {output_image_item->get_id()});
- m_heif_file->set_auxC_property(heif_alpha_image->get_id(), output_image_item->get_auxC_alpha_channel_type());
+ if (Error err = m_heif_file->set_auxC_property(heif_alpha_image->get_id(), output_image_item->get_auxC_alpha_channel_type())) {
+ return err;
+ }
if (pixel_image->is_premultiplied_alpha()) {
m_heif_file->add_iref_reference(output_image_item->get_id(), fourcc("prem"), {heif_alpha_image->get_id()});
@@ -1982,8 +1984,17 @@ Error HeifContext::add_generic_metadata(const std::shared_ptr<ImageItem>& master
}
-heif_property_id HeifContext::add_property(heif_item_id targetItem, const std::shared_ptr<Box>& property, bool essential)
+Result<heif_property_id> HeifContext::add_property(heif_item_id targetItem, const std::shared_ptr<Box>& property, bool essential)
{
+ // Like writing, adding properties is only implemented for contexts that were built in memory.
+ // In a context read from a file, the item data still lives in the input file and the context
+ // cannot be written out again (see HeifContext::write()).
+ if (m_heif_file->get_reader()) {
+ return Error(heif_error_Unsupported_feature,
+ heif_suberror_Unspecified,
+ "Adding a property to a context that was read from a file is not supported");
+ }
+
heif_property_id id;
if (auto img = get_image(targetItem, false)) {
@@ -1993,6 +2004,12 @@ heif_property_id HeifContext::add_property(heif_item_id targetItem, const std::s
id = m_heif_file->add_property(targetItem, property, essential);
}
+ if (id == 0) {
+ return Error(heif_error_Encoding_error,
+ heif_suberror_Unspecified,
+ "Cannot add property to item");
+ }
+
return id;
}
@@ -2105,8 +2122,7 @@ Result<heif_property_id> HeifContext::add_text_property(heif_item_id itemId, con
auto elng = std::make_shared<Box_elng>();
elng->set_lang(std::string(language));
- heif_property_id id = add_property(itemId, elng, false);
- return id;
+ return add_property(itemId, elng, false);
}
diff --git a/libheif/context.h b/libheif/context.h
index 8e562be2..0128fc7a 100644
--- a/libheif/context.h
+++ b/libheif/context.h
@@ -175,7 +175,7 @@ public:
uint32_t item_type, const char* content_type, const char* item_uri_type,
heif_metadata_compression compression, heif_item_id* out_item_id);
- heif_property_id add_property(heif_item_id targetItem, const std::shared_ptr<Box>& property, bool essential);
+ Result<heif_property_id> add_property(heif_item_id targetItem, const std::shared_ptr<Box>& property, bool essential);
Result<heif_item_id> add_pyramid_group(const std::vector<heif_item_id>& layers);
@@ -268,7 +268,7 @@ public:
}
if (propertyId - 1 >= properties.size()) {
- Error(heif_error_Usage_error, heif_suberror_Invalid_property, "property index out of range");
+ return Error(heif_error_Usage_error, heif_suberror_Invalid_property, "property index out of range");
}
auto box = properties[propertyId - 1];
diff --git a/libheif/file.cc b/libheif/file.cc
index 02b449c4..5263d8fc 100644
--- a/libheif/file.cc
+++ b/libheif/file.cc
@@ -61,6 +61,8 @@
// TODO: make this a decoder option
#define STRICT_PARSING false
+static const char* const too_many_properties_error = "Too many item properties in file to add another one";
+
HeifFile::HeifFile()
{
@@ -1141,7 +1143,7 @@ Result<std::shared_ptr<Box_infe>> HeifFile::add_new_meta_infe_box(uint32_t item_
}
-void HeifFile::add_ispe_property(heif_item_id id, uint32_t width, uint32_t height, bool essential)
+Error HeifFile::add_ispe_property(heif_item_id id, uint32_t width, uint32_t height, bool essential)
{
init_for_item_properties();
@@ -1150,7 +1152,17 @@ void HeifFile::add_ispe_property(heif_item_id id, uint32_t width, uint32_t heigh
uint32_t index = m_ipco_box->find_or_append_child_box(ispe);
+ // 'ipma' stores the property index as a 16 bit value. Rather than truncating it and writing a
+ // wrong association, refuse to add the property.
+ if (index + 1 > 0xFFFF) {
+ return {heif_error_Encoding_error,
+ heif_suberror_Unspecified,
+ too_many_properties_error};
+ }
+
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)});
+
+ return Error::Ok;
}
@@ -1161,9 +1173,15 @@ heif_property_id HeifFile::add_property(heif_item_id id, const std::shared_ptr<B
uint32_t index = m_ipco_box->find_or_append_child_box(property);
- m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)});
+ // 'ipma' stores the property index as a 16 bit value. Rather than truncating it and writing a
+ // wrong association, refuse to add the property.
+ if (index + 1 > 0xFFFF) {
+ return 0;
+ }
- return index + 1;
+ // Note that we have to return the position within the item's property list and not 'index',
+ // as 'index' is the position in the file-wide 'ipco' box, which is shared by all items.
+ return m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)});
}
@@ -1172,14 +1190,15 @@ heif_property_id HeifFile::add_property_without_deduplication(heif_item_id id, c
init_for_item_properties();
uint32_t index = m_ipco_box->append_child_box(property);
+ if (index + 1 > 0xFFFF) {
+ return 0;
+ }
- m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)});
-
- return index + 1;
+ return m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)});
}
-void HeifFile::add_orientation_properties(heif_item_id id, heif_orientation orientation)
+Error HeifFile::add_orientation_properties(heif_item_id id, heif_orientation orientation)
{
init_for_item_properties();
@@ -1228,6 +1247,9 @@ void HeifFile::add_orientation_properties(heif_item_id id, heif_orientation orie
irot->set_rotation_ccw(rotation_ccw);
uint32_t index = m_ipco_box->find_or_append_child_box(irot);
+ if (index + 1 > 0xFFFF) {
+ return {heif_error_Encoding_error, heif_suberror_Unspecified, too_many_properties_error};
+ }
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)});
}
@@ -1237,9 +1259,14 @@ void HeifFile::add_orientation_properties(heif_item_id id, heif_orientation orie
imir->set_mirror_direction(mirror);
uint32_t index = m_ipco_box->find_or_append_child_box(imir);
+ if (index + 1 > 0xFFFF) {
+ return {heif_error_Encoding_error, heif_suberror_Unspecified, too_many_properties_error};
+ }
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)});
}
+
+ return Error::Ok;
}
@@ -1530,7 +1557,7 @@ std::shared_ptr<Box_EntityToGroup> HeifFile::get_entity_group(heif_entity_group_
}
-void HeifFile::set_auxC_property(heif_item_id id, const std::string& type)
+Error HeifFile::set_auxC_property(heif_item_id id, const std::string& type)
{
init_for_item_properties();
@@ -1538,8 +1565,13 @@ void HeifFile::set_auxC_property(heif_item_id id, const std::string& type)
auxC->set_aux_type(type);
uint32_t index = m_ipco_box->find_or_append_child_box(auxC);
+ if (index + 1 > 0xFFFF) {
+ return {heif_error_Encoding_error, heif_suberror_Unspecified, too_many_properties_error};
+ }
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)});
+
+ return Error::Ok;
}
#if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER)
diff --git a/libheif/file.h b/libheif/file.h
index 7691342f..b7e0c966 100644
--- a/libheif/file.h
+++ b/libheif/file.h
@@ -209,10 +209,10 @@ public:
Result<std::shared_ptr<Box_infe>> add_new_meta_infe_box(uint32_t item_type);
- void add_ispe_property(heif_item_id id, uint32_t width, uint32_t height, bool essential);
+ Error add_ispe_property(heif_item_id id, uint32_t width, uint32_t height, bool essential);
// set irot/imir according to heif_orientation
- void add_orientation_properties(heif_item_id id, heif_orientation);
+ Error add_orientation_properties(heif_item_id id, heif_orientation);
// TODO: can we remove the 'essential' parameter and take this from the box? Or is that depending on the context?
heif_property_id add_property(heif_item_id id, const std::shared_ptr<Box>& property, bool essential);
@@ -250,7 +250,7 @@ public:
void add_entity_group_box(const std::shared_ptr<Box>& entity_group_box);
- void set_auxC_property(heif_item_id id, const std::string& type);
+ Error set_auxC_property(heif_item_id id, const std::string& type);
#if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER)
static std::wstring convert_utf8_path_to_utf16(std::string pathutf8);
diff --git a/libheif/image-items/grid.cc b/libheif/image-items/grid.cc
index bb05f54a..c7c3a7a5 100644
--- a/libheif/image-items/grid.cc
+++ b/libheif/image-items/grid.cc
@@ -800,7 +800,9 @@ Result<std::shared_ptr<ImageItem_Grid>> ImageItem_Grid::add_new_grid_item(HeifCo
file->add_iref_reference(grid_id, fourcc("dimg"), tile_ids);
// Add ISPE property
- file->add_ispe_property(grid_id, output_width, output_height, false);
+ if (Error err = file->add_ispe_property(grid_id, output_width, output_height, false)) {
+ return err;
+ }
// PIXI property will be added when the first tile is set
@@ -866,7 +868,9 @@ Error ImageItem_Grid::add_image_tile(uint32_t tile_x, uint32_t tile_y,
// Add transformative properties
- get_context()->get_heif_file()->add_orientation_properties(get_id(), m_grid_orientation);
+ if (Error err = get_context()->get_heif_file()->add_orientation_properties(get_id(), m_grid_orientation)) {
+ return err;
+ }
}
return Error::Ok;
diff --git a/libheif/image-items/image_item.cc b/libheif/image-items/image_item.cc
index b377e863..5b79f981 100644
--- a/libheif/image-items/image_item.cc
+++ b/libheif/image-items/image_item.cc
@@ -37,6 +37,7 @@
#include "plugin_registry.h"
#include "security_limits.h"
+#include <algorithm>
#include <limits>
#include <cassert>
#include <cstring>
@@ -85,8 +86,13 @@ heif_property_id ImageItem::add_property(const std::shared_ptr<Box>& property, b
return 0;
}
- // TODO: is this correct? What happens when add_property does deduplicate the property?
- m_properties.push_back(property);
+ // HeifFile::add_property() deduplicates the property box, so only remember it here if the
+ // item does not hold it yet. The returned id is the position in the item's property list and
+ // is correct in both cases.
+ if (std::find(m_properties.begin(), m_properties.end(), property) == m_properties.end()) {
+ m_properties.push_back(property);
+ }
+
return get_file()->add_property(get_id(), property, essential);
}
@@ -436,7 +442,9 @@ Error ImageItem::encode_to_item(HeifContext* ctx,
}
// TODO: move this into encode_to_bistream_and_boxes()
- ctx->get_heif_file()->add_orientation_properties(image_id, options.image_orientation);
+ if (Error err = ctx->get_heif_file()->add_orientation_properties(image_id, options.image_orientation)) {
+ return err;
+ }
return Error::Ok;
}
diff --git a/libheif/image-items/tiled.cc b/libheif/image-items/tiled.cc
index 936b43b8..3e4e8a40 100644
--- a/libheif/image-items/tiled.cc
+++ b/libheif/image-items/tiled.cc
@@ -936,7 +936,9 @@ Error ImageItem_Tiled::add_image_tile(uint32_t tile_x, uint32_t tile_y,
break;
}
- get_file()->add_orientation_properties(get_id(), m_image_orientation);
+ if (Error err = get_file()->add_orientation_properties(get_id(), m_image_orientation)) {
+ return err;
+ }
}
//get_file()->set_brand(encoder->plugin->compression_format,
diff --git a/libheif/image-items/unc_image.cc b/libheif/image-items/unc_image.cc
index ff490252..32e0552d 100644
--- a/libheif/image-items/unc_image.cc
+++ b/libheif/image-items/unc_image.cc
@@ -282,7 +282,9 @@ Result<std::shared_ptr<ImageItem_uncompressed>> ImageItem_uncompressed::add_unci
// Add transformative properties
- ctx->get_heif_file()->add_orientation_properties(unci_id, encoding_options->image_orientation);
+ if (Error err = ctx->get_heif_file()->add_orientation_properties(unci_id, encoding_options->image_orientation)) {
+ return err;
+ }
// Create empty image. If we use compression, we append the data piece by piece.
diff --git a/libheif/mini.cc b/libheif/mini.cc
index 27720624..5054751f 100644
--- a/libheif/mini.cc
+++ b/libheif/mini.cc
@@ -1551,9 +1551,13 @@ Error Box_mini::create_expanded_boxes(class HeifFile* file)
// mini's orientation field uses standard EXIF orientation values 1..8,
// matching the heif_orientation enum.
auto orientation = static_cast<heif_orientation>(get_orientation());
- file->add_orientation_properties(1, orientation);
+ if (Error err = file->add_orientation_properties(1, orientation)) {
+ return err;
+ }
if (get_alpha_item_data_size() != 0) {
- file->add_orientation_properties(2, orientation);
+ if (Error err = file->add_orientation_properties(2, orientation)) {
+ return err;
+ }
}
auto iloc_box = std::make_shared<Box_iloc>();
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index a3c1054b..bb693ad8 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -85,6 +85,7 @@ add_libheif_test(entity_groups)
add_libheif_test(extended_type)
add_libheif_test(grid_tile_missing)
add_libheif_test(iden_declared_size)
+add_libheif_test(item_properties)
add_libheif_test(item_writing)
add_libheif_test(overlay_amplification)
add_libheif_test(alpha_cycle_deadlock)
diff --git a/tests/item_properties.cc b/tests/item_properties.cc
new file mode 100644
index 00000000..a164adb4
--- /dev/null
+++ b/tests/item_properties.cc
@@ -0,0 +1,213 @@
+/*
+ libheif integration tests for item property ids
+
+ MIT License
+
+ Copyright (c) 2026 Dirk Farin <dirk.farin@gmail.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+#include "catch_amalgamated.hpp"
+#include "libheif/heif.h"
+#include "libheif/heif_items.h"
+#include "libheif/heif_properties.h"
+#include "test_utils.h"
+#include <cstdint>
+#include <vector>
+
+
+// The 'heif_property_id' returned by heif_item_add_*_property() has to be usable with the
+// property getters. The property boxes of all items are stored in a single, file-wide 'ipco'
+// box, while the getters address the properties of one item. As soon as the file holds more
+// properties than the item we add to, the two numberings differ. Using an 'ipco' index as
+// property id then reads past the end of the item's property list.
+
+TEST_CASE("property id round trip with several items") {
+ heif_init(nullptr);
+ heif_context* ctx = heif_context_alloc();
+
+ const std::vector<uint8_t> payload1{'i', 't', 'e', 'm', '1'};
+ const std::vector<uint8_t> payload2{'i', 't', 'e', 'm', '2'};
+
+ heif_item_id item1, item2;
+ heif_error err;
+
+ err = heif_context_add_item(ctx, "mime", payload1.data(), (int) payload1.size(), &item1);
+ REQUIRE(err.code == heif_error_Ok);
+ err = heif_context_add_item(ctx, "mime", payload2.data(), (int) payload2.size(), &item2);
+ REQUIRE(err.code == heif_error_Ok);
+
+ // Add a property to the second item first, so that the 'ipco' box holds a property that the
+ // first item does not associate.
+
+ const std::vector<uint8_t> body2{0x02, 0x02, 0x02, 0x02};
+ heif_property_id propertyId2;
+ err = heif_item_add_raw_property(ctx, item2, heif_fourcc('p', 'r', 'p', '2'), nullptr,
+ body2.data(), body2.size(), 0, &propertyId2);
+ REQUIRE(err.code == heif_error_Ok);
+
+ const std::vector<uint8_t> body1{0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
+ heif_property_id propertyId1;
+ err = heif_item_add_raw_property(ctx, item1, heif_fourcc('p', 'r', 'p', '1'), nullptr,
+ body1.data(), body1.size(), 0, &propertyId1);
+ REQUIRE(err.code == heif_error_Ok);
+
+ // Both items hold exactly one property, hence both ids have to be 1. Before this was fixed,
+ // 'propertyId1' was 2 (the position in 'ipco'), which read past the end of item1's list.
+
+ REQUIRE(propertyId1 == 1);
+ REQUIRE(propertyId2 == 1);
+
+ // The ids the enumerator hands out have to match the ids we got when adding.
+
+ heif_property_id enumerated[8];
+ int n = heif_item_get_properties_of_type(ctx, item1, heif_item_property_type_invalid, enumerated, 8);
+ REQUIRE(n == 1);
+ REQUIRE(enumerated[0] == propertyId1);
+
+ // Read the properties back through the ids that were returned when adding them.
+
+ size_t size = 0;
+ err = heif_item_get_property_raw_size(ctx, item1, propertyId1, &size);
+ REQUIRE(err.code == heif_error_Ok);
+ REQUIRE(size == body1.size());
+
+ std::vector<uint8_t> readback(size);
+ err = heif_item_get_property_raw_data(ctx, item1, propertyId1, readback.data());
+ REQUIRE(err.code == heif_error_Ok);
+ REQUIRE(readback == body1);
+
+ err = heif_item_get_property_raw_size(ctx, item2, propertyId2, &size);
+ REQUIRE(err.code == heif_error_Ok);
+ REQUIRE(size == body2.size());
+
+ readback.resize(size);
+ err = heif_item_get_property_raw_data(ctx, item2, propertyId2, readback.data());
+ REQUIRE(err.code == heif_error_Ok);
+ REQUIRE(readback == body2);
+
+ // An id past the end of the item's property list has to be rejected instead of reading
+ // out of bounds.
+
+ err = heif_item_get_property_raw_size(ctx, item1, propertyId1 + 1, &size);
+ REQUIRE(err.code == heif_error_Usage_error);
+
+ heif_context_free(ctx);
+ heif_deinit();
+}
+
+
+// The same divergence occurs for a plain image with a thumbnail: the thumbnail contributes its
+// own codec configuration and 'ispe' to the shared 'ipco' box.
+
+TEST_CASE("property id round trip with thumbnail") {
+ heif_init(nullptr);
+ // Query the encoder before allocating anything, so that a skipped test leaks nothing.
+ heif_encoder* encoder = get_encoder_or_skip_test(heif_compression_HEVC);
+ heif_image* input_image = createImage_RGB_planar();
+ heif_context* ctx = heif_context_alloc();
+
+ heif_image_handle* image_handle;
+ heif_error err = heif_context_encode_image(ctx, input_image, encoder, nullptr, &image_handle);
+ UNSCOPED_INFO("heif_context_encode_image: " << err.message);
+ REQUIRE(err.code == heif_error_Ok);
+
+ heif_image_handle* thumbnail_handle = nullptr;
+ err = heif_context_encode_thumbnail(ctx, input_image, image_handle, encoder, nullptr, 16,
+ &thumbnail_handle);
+ UNSCOPED_INFO("heif_context_encode_thumbnail: " << err.message);
+ REQUIRE(err.code == heif_error_Ok);
+ REQUIRE(thumbnail_handle != nullptr);
+
+ heif_item_id itemId;
+ err = heif_context_get_primary_image_ID(ctx, &itemId);
+ REQUIRE(err.code == heif_error_Ok);
+
+ int n_before = heif_item_get_properties_of_type(ctx, itemId, heif_item_property_type_invalid,
+ nullptr, 0);
+ REQUIRE(n_before > 0);
+
+ const std::vector<uint8_t> body{0xfa, 0xde, 0x99, 0x04};
+ heif_property_id propertyId;
+ err = heif_item_add_raw_property(ctx, itemId, heif_fourcc('p', 'r', 'p', 'x'), nullptr,
+ body.data(), body.size(), 0, &propertyId);
+ REQUIRE(err.code == heif_error_Ok);
+
+ // The property was appended to the item's property list, so its id is the new list length.
+ REQUIRE(propertyId == (heif_property_id) (n_before + 1));
+
+ size_t size = 0;
+ err = heif_item_get_property_raw_size(ctx, itemId, propertyId, &size);
+ REQUIRE(err.code == heif_error_Ok);
+ REQUIRE(size == body.size());
+
+ std::vector<uint8_t> readback(size);
+ err = heif_item_get_property_raw_data(ctx, itemId, propertyId, readback.data());
+ REQUIRE(err.code == heif_error_Ok);
+ REQUIRE(readback == body);
+
+ heif_image_handle_release(thumbnail_handle);
+ heif_image_handle_release(image_handle);
+ heif_encoder_release(encoder);
+ heif_image_release(input_image);
+ heif_context_free(ctx);
+ heif_deinit();
+}
+
+
+// Adding properties to a context that was read from a file is not supported: the item data
+// still lives in the input file and the context cannot be written out again. This has to be
+// refused rather than half-succeeding.
+
+TEST_CASE("adding a property to a loaded context is refused") {
+ heif_init(nullptr);
+ heif_encoder* encoder = get_encoder_or_skip_test(heif_compression_HEVC);
+ heif_context* ctx = heif_context_alloc();
+
+ heif_image* input_image = createImage_RGB_planar();
+ heif_image_handle* image_handle;
+ heif_error err = heif_context_encode_image(ctx, input_image, encoder, nullptr, &image_handle);
+ REQUIRE(err.code == heif_error_Ok);
+
+ err = heif_context_write_to_file(ctx, "property_ids.heif");
+ REQUIRE(err.code == heif_error_Ok);
+
+ heif_image_handle_release(image_handle);
+ heif_image_release(input_image);
+ heif_encoder_release(encoder);
+ heif_context_free(ctx);
+
+ heif_context* read_ctx = heif_context_alloc();
+ err = heif_context_read_from_file(read_ctx, "property_ids.heif", nullptr);
+ REQUIRE(err.code == heif_error_Ok);
+
+ heif_item_id itemId;
+ err = heif_context_get_primary_image_ID(read_ctx, &itemId);
+ REQUIRE(err.code == heif_error_Ok);
+
+ const std::vector<uint8_t> body{0x01, 0x02, 0x03, 0x04};
+ heif_property_id propertyId = 0;
+ err = heif_item_add_raw_property(read_ctx, itemId, heif_fourcc('p', 'r', 'p', 'y'), nullptr,
+ body.data(), body.size(), 0, &propertyId);
+ REQUIRE(err.code == heif_error_Unsupported_feature);
+
+ heif_context_free(read_ctx);
+ heif_deinit();
+}