Commit 410b53e6 for libheif

commit 410b53e6dca4538777e267089e9f03dcfb25deac
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Thu Aug 27 02:53:12 2026 +0200

    serve in-memory iloc extents; do not reject a file for an undecodable mime item

    Box_iloc::read_data() dereferenced the input stream for construction-method-0
    extents. Items added to a context that is being written only exist in memory
    (Extent::data), and a writer-only context has no input stream at all, so
    heif_item_get_item_data() on a freshly added item segfaulted. In-memory extents
    are now served directly (also on a context read from a file and then modified,
    where the stream does not contain the new data), and a missing stream is an
    error instead of a crash.

    A 'mime' item whose content_encoding is unknown, or whose decompressor was not
    compiled in, made the whole file unreadable ("Unsupported codec"), including
    the image. Both the metadata loop and the text item loop now skip such an item;
    its raw data stays accessible through heif_item_get_item_data(). "identity"
    (the RFC 2616 no-op content coding) is treated as no encoding.

diff --git a/libheif/box.cc b/libheif/box.cc
index 4f571e13..088274ab 100644
--- a/libheif/box.cc
+++ b/libheif/box.cc
@@ -1833,8 +1833,46 @@ Error Box_iloc::read_data(heif_item_id item_id,
   //       this function clears the array in some cases. This should be corrected.

   for (const auto& extent : item->extents) {
+
+    // --- data that was appended in memory (append_data()) and not written to a file yet
+
+    // This is the case for items added to a context that is being written. There may be no
+    // input stream at all (writer-only context), and even if there is one (context read from
+    // a file, then modified), the stream does not contain this data.
+
+    if (!extent.data.empty() || extent.length == 0) {
+      uint64_t skip_len = std::min(offset, extent.length);
+      offset -= skip_len;
+
+      uint64_t read_len = std::min(extent.length - skip_len, size);
+
+      if (offset > 0 || read_len == 0) {
+        continue;
+      }
+
+      auto max_memory_block_size = limits->max_memory_block_size;
+      if (max_memory_block_size && max_memory_block_size - dest->size() < read_len) {
+        return {heif_error_Memory_allocation_error,
+                heif_suberror_Security_limit_exceeded,
+                "iloc item data exceeds the maximum memory block size"};
+      }
+
+      dest->insert(dest->end(),
+                   extent.data.begin() + static_cast<size_t>(skip_len),
+                   extent.data.begin() + static_cast<size_t>(skip_len + read_len));
+
+      size -= read_len;
+      continue;
+    }
+
     if (item->construction_method == 0) {

+      if (!istr) {
+        return {heif_error_Invalid_input,
+                heif_suberror_No_item_data,
+                "Item data is not available: the context has no input file"};
+      }
+
       // --- make sure that all data is available

       if (extent.offset > MAX_FILE_POS ||
diff --git a/libheif/context.cc b/libheif/context.cc
index 6b5210e2..9208c2f7 100644
--- a/libheif/context.cc
+++ b/libheif/context.cc
@@ -1110,6 +1110,14 @@ Error HeifContext::interpret_heif_file_images()

     auto metadataResult = m_heif_file->get_uncompressed_item_data(id);
     if (!metadataResult) {
+      if (metadataResult.error().error_code == heif_error_Unsupported_feature) {
+        // The item uses a content_encoding that we cannot decode (unknown coding, or the
+        // decompressor was not compiled in). That is a limitation of this one item, not of
+        // the file: skip it instead of refusing the whole file. Its raw data stays accessible
+        // through heif_item_get_item_data().
+        continue;
+      }
+
       if (item_type == fourcc("Exif") || item_type == fourcc("mime")) {
         // these item types should have data
         return metadataResult.error();
@@ -1264,6 +1272,12 @@ Error HeifContext::interpret_heif_file_images()

     auto textDataResult = m_heif_file->get_uncompressed_item_data(id);
     if (!textDataResult) {
+      if (textDataResult.error().error_code == heif_error_Unsupported_feature) {
+        // content_encoding that we cannot decode: this is not a text item we can use, but
+        // that is no reason to reject the file (see the metadata loop above)
+        continue;
+      }
+
       return textDataResult.error();
     }

diff --git a/libheif/file.cc b/libheif/file.cc
index d9050f30..1a495b45 100644
--- a/libheif/file.cc
+++ b/libheif/file.cc
@@ -793,7 +793,7 @@ Result<std::vector<uint8_t>> HeifFile::get_uncompressed_item_data(heif_item_id I
     // Skipping is also necessary to avoid libstdc++'s _S_compare(0, N) which
     // computes unsigned `0 - N` and trips UBSan even though the cast result
     // is benign.
-    if (!encoding.empty()) {
+    if (!encoding.empty() && encoding != "identity") {
       if (encoding == "compress_zlib") {
 #if HAVE_ZLIB
         std::vector<uint8_t> compressed_data;
@@ -976,8 +976,8 @@ Result<std::vector<uint8_t>> HeifFile::get_item_data(heif_item_id ID, heif_metad

   heif_metadata_compression compression;

-  if (encoding.empty()) {
-    // shortcut for case of uncompressed mime data
+  if (encoding.empty() || encoding == "identity") {
+    // shortcut for case of uncompressed mime data ("identity" is the RFC 2616 no-op coding)

     if (out_compression) {
       *out_compression = heif_metadata_compression_off;