Commit 0cc60d75 for libheif
commit 0cc60d759e33a30f6b3240967a02c4ef5a37301b
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Thu Aug 27 02:53:12 2026 +0200
harden the generic item writers
heif_context_add_item / add_mime_item / add_precompressed_mime_item /
add_uri_item:
- NULL content_type / content_encoding / item_uri_type were passed straight into
std::string and threw std::logic_error through the C API (SIGABRT). A negative
size was converted to size_t (std::length_error, abort), NULL data with
size > 0 was dereferenced. All of these now return heif_error_Usage_error, and
the bodies run under exception_guard.
- The copy-pasted success/error tail (five copies, which is why the Result bug was
fixed piecemeal) is a single helper. A NULL out_item_id is allowed: the item is
added, its ID is not reported. Doxygen comments added for all four functions.
- HeifFile::add_infe (behind heif_context_add_item) used add_new_infe_box, which
runs init_for_image(): the file got a 'pict' handler, an empty iprp and a 'pitm'
with item ID 0, so the written file was unreadable ("pitm references
non-existing image") and a sequence-only file lost its readability. It now uses
add_new_meta_infe_box like the mime/uri writers. The property writers create
the iprp/ipco/ipma boxes on demand (init_for_item_properties()) so properties
can still be attached to such items.
- Errors from set_item_data / set_precompressed_item_data were discarded: with an
unavailable compressor (e.g. a build without zlib/brotli) the call reported
success and a valid ID for an item without any iloc data. The error is now
propagated and the half-created infe box is removed again. Requesting a
compression for a non-mime item is an error instead of a TODO, and empty data
no longer memcpy()s from a NULL pointer.
- heif_item_get_item_data: do not write *out_data_size on error when it is NULL.
diff --git a/libheif/api/libheif/heif_items.cc b/libheif/api/libheif/heif_items.cc
index f4e9357d..af92e95e 100644
--- a/libheif/api/libheif/heif_items.cc
+++ b/libheif/api/libheif/heif_items.cc
@@ -150,9 +150,11 @@ heif_error heif_item_get_item_data(const heif_context* ctx,
auto dataResult = ctx->context->get_heif_file()->get_item_data(item_id, out_compression_format);
if (!dataResult) {
- *out_data_size = 0;
+ if (out_data_size) {
+ *out_data_size = 0;
+ }
if (out_data) {
- *out_data = 0;
+ *out_data = nullptr;
}
return dataResult.error_struct(ctx->context.get());
@@ -301,6 +303,42 @@ heif_error heif_context_add_item_references(heif_context* ctx,
// ------------------------- writing -------------------------
+// Check the (data, size) pair that all item writers take. 'size' is a public 'int' that
+// is converted to size_t further down: a negative value would turn into a huge allocation
+// (std::length_error through the C API), and a NULL buffer with a non-zero size would be
+// dereferenced.
+static heif_error check_item_data_arguments(const void* data, int size)
+{
+ if (size < 0) {
+ return {heif_error_Usage_error,
+ heif_suberror_Invalid_parameter_value,
+ "item data size must not be negative"};
+ }
+
+ if (size > 0 && data == nullptr) {
+ return heif_error_null_pointer_argument;
+ }
+
+ return heif_error_success;
+}
+
+
+// Common tail of all item writers: report the error, or store the new item ID.
+// A NULL out_item_id is allowed (the item is added, but its ID is not reported).
+static heif_error return_new_item_id(heif_context* ctx, Result<heif_item_id> result, heif_item_id* out_item_id)
+{
+ if (!result) {
+ return result.error_struct(ctx->context.get());
+ }
+
+ if (out_item_id) {
+ *out_item_id = *result;
+ }
+
+ return heif_error_success;
+}
+
+
heif_error heif_context_add_item(heif_context* ctx,
const char* item_type,
const void* data, int size,
@@ -314,15 +352,14 @@ heif_error heif_context_add_item(heif_context* ctx,
};
}
- Result<heif_item_id> result = ctx->context->get_heif_file()->add_infe(fourcc(item_type), (const uint8_t*) data, size);
-
- if (result && out_item_id) {
- *out_item_id = *result;
- return heif_error_success;
- }
- else {
- return result.error_struct(ctx->context.get());
+ if (heif_error err = check_item_data_arguments(data, size); err.code != heif_error_Ok) {
+ return err;
}
+
+ return exception_guard([&]() -> heif_error {
+ Result<heif_item_id> result = ctx->context->get_heif_file()->add_infe(fourcc(item_type), (const uint8_t*) data, size);
+ return return_new_item_id(ctx, std::move(result), out_item_id);
+ });
}
heif_error heif_context_add_mime_item(heif_context* ctx,
@@ -331,15 +368,18 @@ heif_error heif_context_add_mime_item(heif_context* ctx,
const void* data, int size,
heif_item_id* out_item_id)
{
- Result<heif_item_id> result = ctx->context->get_heif_file()->add_infe_mime(content_type, content_encoding, (const uint8_t*) data, size);
-
- if (result && out_item_id) {
- *out_item_id = *result;
- return heif_error_success;
+ if (content_type == nullptr) {
+ return heif_error_null_pointer_argument;
}
- else {
- return result.error_struct(ctx->context.get());
+
+ if (heif_error err = check_item_data_arguments(data, size); err.code != heif_error_Ok) {
+ return err;
}
+
+ return exception_guard([&]() -> heif_error {
+ Result<heif_item_id> result = ctx->context->get_heif_file()->add_infe_mime(content_type, content_encoding, (const uint8_t*) data, size);
+ return return_new_item_id(ctx, std::move(result), out_item_id);
+ });
}
@@ -349,15 +389,18 @@ heif_error heif_context_add_precompressed_mime_item(heif_context* ctx,
const void* data, int size,
heif_item_id* out_item_id)
{
- Result<heif_item_id> result = ctx->context->get_heif_file()->add_precompressed_infe_mime(content_type, content_encoding, (const uint8_t*) data, size);
-
- if (result && out_item_id) {
- *out_item_id = *result;
- return heif_error_success;
+ if (content_type == nullptr || content_encoding == nullptr) {
+ return heif_error_null_pointer_argument;
}
- else {
- return result.error_struct(ctx->context.get());
+
+ if (heif_error err = check_item_data_arguments(data, size); err.code != heif_error_Ok) {
+ return err;
}
+
+ return exception_guard([&]() -> heif_error {
+ Result<heif_item_id> result = ctx->context->get_heif_file()->add_precompressed_infe_mime(content_type, content_encoding, (const uint8_t*) data, size);
+ return return_new_item_id(ctx, std::move(result), out_item_id);
+ });
}
heif_error heif_context_add_uri_item(heif_context* ctx,
@@ -365,13 +408,16 @@ heif_error heif_context_add_uri_item(heif_context* ctx,
const void* data, int size,
heif_item_id* out_item_id)
{
- Result<heif_item_id> result = ctx->context->get_heif_file()->add_infe_uri(item_uri_type, (const uint8_t*) data, size);
-
- if (result && out_item_id) {
- *out_item_id = *result;
- return heif_error_success;
+ if (item_uri_type == nullptr) {
+ return heif_error_null_pointer_argument;
}
- else {
- return result.error_struct(ctx->context.get());
+
+ if (heif_error err = check_item_data_arguments(data, size); err.code != heif_error_Ok) {
+ return err;
}
+
+ return exception_guard([&]() -> heif_error {
+ Result<heif_item_id> result = ctx->context->get_heif_file()->add_infe_uri(item_uri_type, (const uint8_t*) data, size);
+ return return_new_item_id(ctx, std::move(result), out_item_id);
+ });
}
diff --git a/libheif/api/libheif/heif_items.h b/libheif/api/libheif/heif_items.h
index b446b702..46bf71e8 100644
--- a/libheif/api/libheif/heif_items.h
+++ b/libheif/api/libheif/heif_items.h
@@ -227,12 +227,50 @@ heif_error heif_context_add_item_references(heif_context* ctx,
// ------------------------- adding new items -------------------------
+/*
+ * All functions in this section add a new item to the context and return its ID in
+ * 'out_item_id'. The item ID is freshly allocated and does not collide with any item that
+ * already exists in the context, including items read from an input file.
+ *
+ * 'data' is copied into the context, the caller may release it afterwards. 'size' is the
+ * number of bytes in 'data'. A NULL 'data' is only allowed together with size 0.
+ * 'out_item_id' may be NULL, in which case the item is added but its ID is not reported.
+ *
+ * The new item is marked as hidden. Use heif_context_add_item_reference() to link it to an
+ * image (e.g. with a 'cdsc' reference for metadata that describes the image).
+ *
+ * Note that a file must contain at least one image or image sequence to be written;
+ * a context holding only items added with these functions cannot be written.
+ */
+
+/**
+ * Add an item with an arbitrary four-character item type (e.g. "Exif").
+ *
+ * @param ctx the file context
+ * @param item_type four-character item type
+ * @param data the item payload
+ * @param size number of bytes in 'data'
+ * @param out_item_id receives the ID of the new item (may be NULL)
+ */
LIBHEIF_API
heif_error heif_context_add_item(heif_context* ctx,
const char* item_type,
const void* data, int size,
heif_item_id* out_item_id);
+/**
+ * Add a 'mime' item, optionally compressing the payload.
+ *
+ * @param ctx the file context
+ * @param content_type MIME content type of the payload (e.g. "application/rdf+xml")
+ * @param content_encoding compression to apply to the payload before storing it.
+ * Use heif_metadata_compression_method_supported() to check which methods this
+ * build of libheif can compress. An unsupported method returns an error and no
+ * item is added.
+ * @param data the (uncompressed) item payload
+ * @param size number of bytes in 'data'
+ * @param out_item_id receives the ID of the new item (may be NULL)
+ */
LIBHEIF_API
heif_error heif_context_add_mime_item(heif_context* ctx,
const char* content_type,
@@ -240,6 +278,21 @@ heif_error heif_context_add_mime_item(heif_context* ctx,
const void* data, int size,
heif_item_id* out_item_id);
+/**
+ * Add a 'mime' item whose payload has already been compressed by the caller.
+ *
+ * @param ctx the file context
+ * @param content_type MIME content type of the (uncompressed) payload
+ * @param content_encoding the HTTP content-coding name that was applied to 'data', stored
+ * verbatim as the item's content_encoding. Use "" for uncompressed data.
+ * libheif itself can decode "deflate", "compress_zlib" and "br" (when built with
+ * zlib / Brotli support) and treats "identity" as uncompressed; items with any other
+ * content_encoding are kept, but their data can only be retrieved in compressed form
+ * with heif_item_get_item_data().
+ * @param data the compressed item payload
+ * @param size number of bytes in 'data'
+ * @param out_item_id receives the ID of the new item (may be NULL)
+ */
LIBHEIF_API
heif_error heif_context_add_precompressed_mime_item(heif_context* ctx,
const char* content_type,
@@ -247,6 +300,15 @@ heif_error heif_context_add_precompressed_mime_item(heif_context* ctx,
const void* data, int size,
heif_item_id* out_item_id);
+/**
+ * Add a 'uri ' item.
+ *
+ * @param ctx the file context
+ * @param item_uri_type the URI that identifies the type of the item payload
+ * @param data the item payload
+ * @param size number of bytes in 'data'
+ * @param out_item_id receives the ID of the new item (may be NULL)
+ */
LIBHEIF_API
heif_error heif_context_add_uri_item(heif_context* ctx,
const char* item_uri_type,
diff --git a/libheif/file.cc b/libheif/file.cc
index d1f7fdd0..d9050f30 100644
--- a/libheif/file.cc
+++ b/libheif/file.cc
@@ -212,6 +212,14 @@ void HeifFile::init_for_image()
m_meta_box->append_child_box(m_pitm_box);
}
+ init_for_item_properties();
+}
+
+
+void HeifFile::init_for_item_properties()
+{
+ init_for_meta_item();
+
if (!m_iprp_box) {
m_iprp_box = std::make_shared<Box_iprp>();
m_meta_box->append_child_box(m_iprp_box);
@@ -1068,6 +1076,16 @@ void HeifFile::seed_id_creator()
}
+void HeifFile::remove_infe_box(const std::shared_ptr<Box_infe>& infe)
+{
+ m_infe_boxes.erase(infe->get_item_ID());
+
+ if (m_iinf_box) {
+ m_iinf_box->remove_child_box(infe);
+ }
+}
+
+
Result<heif_item_id> HeifFile::add_new_image(uint32_t item_type)
{
auto result = add_new_infe_box(item_type);
@@ -1124,6 +1142,8 @@ 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)
{
+ init_for_item_properties();
+
auto ispe = std::make_shared<Box_ispe>();
ispe->set_size(width, height);
@@ -1136,6 +1156,8 @@ void HeifFile::add_ispe_property(heif_item_id id, uint32_t width, uint32_t heigh
heif_property_id HeifFile::add_property(heif_item_id id, const std::shared_ptr<Box>& property, bool essential)
{
+ init_for_item_properties();
+
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)});
@@ -1146,6 +1168,8 @@ heif_property_id HeifFile::add_property(heif_item_id id, const std::shared_ptr<B
heif_property_id HeifFile::add_property_without_deduplication(heif_item_id id, const std::shared_ptr<Box>& property, bool essential)
{
+ init_for_item_properties();
+
uint32_t index = m_ipco_box->append_child_box(property);
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)});
@@ -1156,6 +1180,8 @@ heif_property_id HeifFile::add_property_without_deduplication(heif_item_id id, c
void HeifFile::add_orientation_properties(heif_item_id id, heif_orientation orientation)
{
+ init_for_item_properties();
+
// Note: ISO/IEC 23000-22:2019(E) (MIAF) 7.3.6.7 requires the following order:
// clean aperture first, then rotation, then mirror
@@ -1220,7 +1246,7 @@ Result<heif_item_id> HeifFile::add_infe(uint32_t item_type, const uint8_t* data,
{
// create an infe box describing what kind of data we are storing (this also creates a new ID)
- auto infe_result = add_new_infe_box(item_type);
+ auto infe_result = add_new_meta_infe_box(item_type);
if (!infe_result) {
return infe_result.error();
}
@@ -1229,7 +1255,10 @@ Result<heif_item_id> HeifFile::add_infe(uint32_t item_type, const uint8_t* data,
heif_item_id metadata_id = infe_box->get_item_ID();
- set_item_data(infe_box, data, size, heif_metadata_compression_off);
+ if (Error err = set_item_data(infe_box, data, size, heif_metadata_compression_off)) {
+ remove_infe_box(infe_box);
+ return err;
+ }
return metadata_id;
}
@@ -1255,7 +1284,10 @@ Result<heif_item_id> HeifFile::add_infe_mime(const char* content_type, heif_meta
heif_item_id metadata_id = infe_box->get_item_ID();
- set_item_data(infe_box, data, size, content_encoding);
+ if (Error err = set_item_data(infe_box, data, size, content_encoding)) {
+ remove_infe_box(infe_box);
+ return err;
+ }
return metadata_id;
}
@@ -1275,7 +1307,10 @@ Result<heif_item_id> HeifFile::add_precompressed_infe_mime(const char* content_t
heif_item_id metadata_id = infe_box->get_item_ID();
- set_precompressed_item_data(infe_box, data, size, content_encoding);
+ if (Error err = set_precompressed_item_data(infe_box, data, size, content_encoding)) {
+ remove_infe_box(infe_box);
+ return err;
+ }
return metadata_id;
}
@@ -1295,7 +1330,10 @@ Result<heif_item_id> HeifFile::add_infe_uri(const char* item_uri_type, const uin
heif_item_id metadata_id = infe_box->get_item_ID();
- set_item_data(infe_box, data, size, heif_metadata_compression_off);
+ if (Error err = set_item_data(infe_box, data, size, heif_metadata_compression_off)) {
+ remove_infe_box(infe_box);
+ return err;
+ }
return metadata_id;
}
@@ -1312,7 +1350,9 @@ Error HeifFile::set_item_data(const std::shared_ptr<Box_infe>& item, const uint8
// only set metadata compression for MIME type data which has 'content_encoding' field
if (compression != heif_metadata_compression_off &&
item->get_item_type_4cc() != fourcc("mime")) {
- // TODO: error, compression not supported
+ return Error(heif_error_Usage_error,
+ heif_suberror_Invalid_parameter_value,
+ "Item data compression is only supported for 'mime' items");
}
@@ -1366,13 +1406,17 @@ Error HeifFile::set_precompressed_item_data(const std::shared_ptr<Box_infe>& ite
// only set metadata compression for MIME type data which has 'content_encoding' field
if (!content_encoding.empty() &&
item->get_item_type_4cc() != fourcc("mime")) {
- // TODO: error, compression not supported
+ return Error(heif_error_Usage_error,
+ heif_suberror_Invalid_parameter_value,
+ "A content_encoding can only be set for 'mime' items");
}
std::vector<uint8_t> data_array;
- data_array.resize(size);
- memcpy(data_array.data(), data, size);
+ if (size > 0) { // do not memcpy() from a NULL pointer when there is no data
+ data_array.resize(size);
+ memcpy(data_array.data(), data, size);
+ }
item->set_content_encoding(content_encoding);
@@ -1487,6 +1531,8 @@ 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)
{
+ init_for_item_properties();
+
auto auxC = std::make_shared<Box_auxC>();
auxC->set_aux_type(type);
diff --git a/libheif/file.h b/libheif/file.h
index fe4388cd..7691342f 100644
--- a/libheif/file.h
+++ b/libheif/file.h
@@ -88,6 +88,10 @@ public:
void init_for_image();
+ // Create the 'iprp'/'ipco'/'ipma' boxes if they do not exist yet. Unlike init_for_image(),
+ // this does not turn the file into an image file (no 'pict' handler, no 'pitm').
+ void init_for_item_properties();
+
void init_for_sequence();
void set_hdlr_box(std::shared_ptr<Box_hdlr> box) { m_hdlr_box = std::move(box); }
@@ -308,6 +312,9 @@ private:
// file, so that IDs allocated for new items cannot collide with existing ones.
void seed_id_creator();
+ // Undo add_new_infe_box() / add_new_meta_infe_box() when the item could not be completed.
+ void remove_infe_box(const std::shared_ptr<Box_infe>& infe);
+
Error parse_heif_images();
Error parse_heif_sequences();