From b671d752d13c8273df21f8e6e18e7772420fef11 Mon Sep 17 00:00:00 2001
From: Brad Hards <bradh@frogmouth.net>
Date: Tue, 13 Feb 2024 12:27:42 +1100
Subject: [PATCH 1/6] iloc: fix error message

---
 libheif/box.cc | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libheif/box.cc b/libheif/box.cc
index 6d101e46ac..1375f2bb00 100644
--- a/libheif/box.cc
+++ b/libheif/box.cc
@@ -1275,9 +1275,9 @@ Error Box_iloc::read_data(const Item& item,
     }
     else {
       std::stringstream sstr;
-      sstr << "Item construction method " << item.construction_method << " not implemented";
+      sstr << "Item construction method " << (int) item.construction_method << " not implemented";
       return Error(heif_error_Unsupported_feature,
-                   heif_suberror_No_idat_box,
+                   heif_suberror_Unsupported_item_construction_method,
                    sstr.str());
     }
   }

From 5cbc793994baf12e8c8070344bf9029df627e379 Mon Sep 17 00:00:00 2001
From: Brad Hards <bradh@frogmouth.net>
Date: Tue, 13 Feb 2024 19:47:18 +1100
Subject: [PATCH 2/6] 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 <iostream>
 #include <algorithm>
 #include <cstring>
+#include <fstream>
 #include <set>
 #include <cassert>
 
@@ -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<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;
     }
@@ -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<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";
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<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; }
@@ -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
 };
@@ -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<Box_dinf>(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<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
@@ -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")) {
@@ -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;
@@ -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" ||
@@ -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,
@@ -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);
     }
   }
 
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<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;
 
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)

From 72c2bb28bb1bd3fa307d8cea7889325e34fdc10e Mon Sep 17 00:00:00 2001
From: Brad Hards <bradh@frogmouth.net>
Date: Wed, 14 Feb 2024 20:58:09 +1100
Subject: [PATCH 3/6] wip: add support for relative path names

This uses a C++-17 library feature for filesystem, which is pretty widely supported
but still an additional requirement
---
 libheif/CMakeLists.txt |  2 ++
 libheif/box.cc         | 14 ++++++++------
 libheif/box.h          |  2 ++
 libheif/file.cc        | 18 ++++++++++++------
 libheif/file.h         |  2 ++
 tests/CMakeLists.txt   |  1 +
 6 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt
index 818a37b5ff..3913797862 100644
--- a/libheif/CMakeLists.txt
+++ b/libheif/CMakeLists.txt
@@ -116,6 +116,8 @@ target_compile_definitions(heif
         LIBHEIF_EXPORTS
         HAVE_VISIBILITY)
 
+target_compile_features(heif PRIVATE cxx_std_17)
+
 if (PLUGIN_LOADING_SUPPORTED_AND_ENABLED)
     target_compile_definitions(heif PRIVATE ENABLE_PLUGIN_LOADING=1)
     target_link_libraries(heif PRIVATE ${CMAKE_DL_LIBS})
diff --git a/libheif/box.cc b/libheif/box.cc
index 892d8e1b48..7754c90298 100644
--- a/libheif/box.cc
+++ b/libheif/box.cc
@@ -1255,6 +1255,7 @@ 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,
+                          const std::filesystem::path base_path,
                           std::vector<uint8_t>* dest) const
 {
   // TODO: this function should always append the data to the output vector as this is used when
@@ -1313,11 +1314,12 @@ Error Box_iloc::read_data(const Item& item,
             (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
+            // TODO: handle case where its really a URL
+            std::filesystem::path locationPath(location);
+            if (locationPath.is_relative()) {
+              locationPath = base_path / locationPath;
+            }
+            auto datafile_istr = std::unique_ptr<std::istream>(new std::ifstream(locationPath, std::ios_base::binary));
             if (!datafile_istr->good()) {
               std::stringstream sstr;
               sstr << "Error opening file: " << location << ", " << strerror(errno) << " (" << errno << ")\n";
@@ -1331,7 +1333,7 @@ Error Box_iloc::read_data(const Item& item,
           }
         } else {
           std::stringstream sstr;
-          sstr << "Item construction method 2 with data reference type " << dataentry->get_type_string() << "is not implemented";
+          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());
diff --git a/libheif/box.h b/libheif/box.h
index 7b41345d89..75e00556b6 100644
--- a/libheif/box.h
+++ b/libheif/box.h
@@ -34,6 +34,7 @@
 #include <istream>
 #include <bitset>
 #include <utility>
+#include <filesystem>
 
 #include "error.h"
 #include "heif.h"
@@ -392,6 +393,7 @@ class Box_iloc : public FullBox
                   const std::shared_ptr<StreamReader>& istr,
                   const std::shared_ptr<class Box_idat>&,
                   const std::shared_ptr<class Box_dinf>&,
+                  const std::filesystem::path base_path,
                   std::vector<uint8_t>* dest) const;
 
   void set_min_version(uint8_t min_version) { m_user_defined_min_version = min_version; }
diff --git a/libheif/file.cc b/libheif/file.cc
index 65e5ca8040..929ec4df74 100644
--- a/libheif/file.cc
+++ b/libheif/file.cc
@@ -27,6 +27,7 @@
 #include "libheif/vvc.h"
 
 #include <cstdint>
+#include <filesystem>
 #include <fstream>
 #include <limits>
 #include <sstream>
@@ -83,7 +84,12 @@ Error HeifFile::read_from_file(const char* input_filename)
     sstr << "Error opening file: " << strerror(errno) << " (" << errno << ")\n";
     return Error(heif_error_Input_does_not_exist, heif_suberror_Unspecified, sstr.str());
   }
-
+  std::filesystem::path input_path(input_filename);
+  if (input_path.has_parent_path()) {
+    m_base_path = input_path.parent_path();
+  } else {
+    m_base_path.clear();
+  }
   auto input_stream = std::make_shared<StreamReader_istream>(std::move(input_stream_istr));
   return read(input_stream);
 }
@@ -776,7 +782,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, m_dinf_box, data);
+    error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, m_dinf_box, m_base_path, data);
   }
   else if (item_type == "av01") {
     // --- --- --- AV1
@@ -812,7 +818,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, m_dinf_box, data);
+    error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, m_dinf_box, m_base_path, data);
   }
   else if (item_type == "jpeg" ||
            (item_type == "mime" && get_content_type(ID) == "image/jpeg")) {
@@ -838,7 +844,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, m_dinf_box, data);
+    error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, m_dinf_box, m_base_path, data);
   }
   else if (item_type == "j2k1") {
     std::vector<std::shared_ptr<Box>> properties;
@@ -872,7 +878,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, m_dinf_box, data);
+    error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, m_dinf_box, m_base_path, data);
   }
   else if (true ||  // fallback case for all kinds of generic metadata (e.g. 'iptc')
            item_type == "grid" ||
@@ -898,7 +904,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, m_dinf_box, data);
+      error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, m_dinf_box, m_base_path, data);
     }
   }
 
diff --git a/libheif/file.h b/libheif/file.h
index 2ea8a22f27..c59c4ed377 100644
--- a/libheif/file.h
+++ b/libheif/file.h
@@ -26,6 +26,7 @@
 #include "hevc.h"
 #include "nclx.h"
 
+#include <filesystem>
 #include <map>
 #include <memory>
 #include <string>
@@ -206,6 +207,7 @@ class HeifFile
 #endif
 
   std::shared_ptr<StreamReader> m_input_stream;
+  std::filesystem::path m_base_path;
 
   std::vector<std::shared_ptr<Box> > m_top_level_boxes;
 
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 6d619b7070..57fe4f9ee2 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -9,6 +9,7 @@ configure_file(test-config.cc.in ${CMAKE_BINARY_DIR}/generated/test-config.cc)
 macro(add_libheif_test TEST_FILE)
     set(TEST_NAME ${TEST_FILE})
     add_executable(${TEST_NAME} main.cc catch.hpp ${CMAKE_BINARY_DIR}/generated/test-config.cc test_utils.cc ${TEST_FILE}.cc)
+    target_compile_features(${TEST_NAME} PRIVATE cxx_std_17)
     target_link_libraries(${TEST_NAME} PRIVATE heif)
     add_test(NAME ${TEST_NAME} COMMAND ./${TEST_NAME})
 endmacro()

From 28b065ea3205a17160f037bfe3f945e20a18edf5 Mon Sep 17 00:00:00 2001
From: Brad Hards <bradh@frogmouth.net>
Date: Thu, 15 Feb 2024 12:37:20 +1100
Subject: [PATCH 4/6] Update CMakeLists.txt

---
 CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b56dabad04..9ff397b393 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -41,7 +41,7 @@ if(NOT MSVC)
   endif ()
 endif()
 
-set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 set(CMAKE_CXX_EXTENSIONS OFF)
 

From 3e1e04de878a9d1affb3d5896fb6800bb7495930 Mon Sep 17 00:00:00 2001
From: Brad Hards <bradh@frogmouth.net>
Date: Thu, 22 Feb 2024 18:23:55 +1100
Subject: [PATCH 5/6] wip on remote access - DO NOT MERGE

---
 CMakeLists.txt            |  9 +++++++++
 libheif/CMakeLists.txt    |  2 --
 libheif/box.cc            | 37 +++++++++++++++++++++++--------------
 libheif/security_limits.h |  2 +-
 tests/CMakeLists.txt      |  1 -
 5 files changed, 33 insertions(+), 18 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9ff397b393..4f8a2f9083 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -197,6 +197,8 @@ endif()
 
 option(WITH_UNCOMPRESSED_CODEC " Support internal ISO/IEC 23001-17 uncompressed codec (experimental) " OFF)
 
+# CURL
+find_package(CURL)
 
 # --- show codec compilation summary
 
@@ -301,6 +303,13 @@ else()
     message("libsharpyuv: disabled")
 endif ()
 
+# --- CURL download library --
+if(CURL_FOUND)
+    message("curl: found")
+else()
+    message("curl: not found")
+endif()
+
 # --- Create libheif pkgconfig file
 
 set(prefix ${CMAKE_INSTALL_PREFIX})
diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt
index 3913797862..818a37b5ff 100644
--- a/libheif/CMakeLists.txt
+++ b/libheif/CMakeLists.txt
@@ -116,8 +116,6 @@ target_compile_definitions(heif
         LIBHEIF_EXPORTS
         HAVE_VISIBILITY)
 
-target_compile_features(heif PRIVATE cxx_std_17)
-
 if (PLUGIN_LOADING_SUPPORTED_AND_ENABLED)
     target_compile_definitions(heif PRIVATE ENABLE_PLUGIN_LOADING=1)
     target_link_libraries(heif PRIVATE ${CMAKE_DL_LIBS})
diff --git a/libheif/box.cc b/libheif/box.cc
index 7754c90298..8f33707f43 100644
--- a/libheif/box.cc
+++ b/libheif/box.cc
@@ -1314,22 +1314,31 @@ Error Box_iloc::read_data(const Item& item,
             (void) success;
           } else {
             std::string location = urlBox->get_location();
-            // TODO: handle case where its really a URL
-            std::filesystem::path locationPath(location);
-            if (locationPath.is_relative()) {
-              locationPath = base_path / locationPath;
-            }
-            auto datafile_istr = std::unique_ptr<std::istream>(new std::ifstream(locationPath, std::ios_base::binary));
-            if (!datafile_istr->good()) {
+            if (location.rfind("https://", 0) == 0) {
+              // TODO
               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());
+              sstr << "Item construction method 2 with https location of " << location << " is not implemented";
+              return Error(heif_error_Unsupported_feature,
+                          heif_suberror_Unsupported_item_construction_method,
+                          sstr.str());
+            } else {
+              // See if we can read as a local file
+              std::filesystem::path locationPath(location);
+              if (locationPath.is_relative()) {
+                locationPath = base_path / locationPath;
+              }
+              auto datafile_istr = std::unique_ptr<std::istream>(new std::ifstream(locationPath, std::ios_base::binary));
+              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;
             }
-
-            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;
diff --git a/libheif/security_limits.h b/libheif/security_limits.h
index f1c177c0a1..281b4e3cff 100644
--- a/libheif/security_limits.h
+++ b/libheif/security_limits.h
@@ -25,7 +25,7 @@
 
 static const size_t MAX_CHILDREN_PER_BOX = 20000;
 static const int MAX_ILOC_ITEMS = 20000;
-static const int MAX_ILOC_EXTENTS_PER_ITEM = 32;
+static const int MAX_ILOC_EXTENTS_PER_ITEM = 1024;
 static const int MAX_MEMORY_BLOCK_SIZE = 512 * 1024 * 1024; // 512 MB
 static const int MAX_COLOR_PROFILE_SIZE = 100 * 1024 * 1024; // 100 MB
 
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 57fe4f9ee2..6d619b7070 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -9,7 +9,6 @@ configure_file(test-config.cc.in ${CMAKE_BINARY_DIR}/generated/test-config.cc)
 macro(add_libheif_test TEST_FILE)
     set(TEST_NAME ${TEST_FILE})
     add_executable(${TEST_NAME} main.cc catch.hpp ${CMAKE_BINARY_DIR}/generated/test-config.cc test_utils.cc ${TEST_FILE}.cc)
-    target_compile_features(${TEST_NAME} PRIVATE cxx_std_17)
     target_link_libraries(${TEST_NAME} PRIVATE heif)
     add_test(NAME ${TEST_NAME} COMMAND ./${TEST_NAME})
 endmacro()

From 68c58da55b6660db36bac7c257063cb23a36f1be Mon Sep 17 00:00:00 2001
From: Brad Hards <bradh@frogmouth.net>
Date: Mon, 26 Feb 2024 21:40:41 +1100
Subject: [PATCH 6/6] add remote fetching using libcurl

---
 libheif/CMakeLists.txt |  9 +++++++++
 libheif/box.cc         | 45 +++++++++++++++++++++++++++++++++++++-----
 2 files changed, 49 insertions(+), 5 deletions(-)

diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt
index 818a37b5ff..60011d1dfc 100644
--- a/libheif/CMakeLists.txt
+++ b/libheif/CMakeLists.txt
@@ -132,6 +132,15 @@ else ()
     message("Not compiling 'libsharpyuv'")
 endif ()
 
+if (CURL_FOUND)
+    message("Compiling in 'libcurl'")
+    target_compile_definitions(heif PUBLIC HAVE_CURL=1)
+    target_include_directories(heif PRIVATE ${CURL_INCLUDE_DIRS})
+    target_link_libraries(heif PRIVATE ${CURL_LIBRARIES})
+else ()
+    message("Not compiling 'libcurl'")
+endif ()
+
 if (WITH_DEFLATE_HEADER_COMPRESSION)
     find_package(ZLIB REQUIRED)
     target_link_libraries(heif PRIVATE ZLIB::ZLIB)
diff --git a/libheif/box.cc b/libheif/box.cc
index 8f33707f43..bc6d9d2643 100644
--- a/libheif/box.cc
+++ b/libheif/box.cc
@@ -43,6 +43,9 @@
 #include "uncompressed_box.h"
 #endif
 
+#if HAVE_CURL
+#include <curl/curl.h>
+#endif
 
 Fraction::Fraction(int32_t num, int32_t den)
 {
@@ -1251,6 +1254,15 @@ bool Box_iloc::read_extent(const Item& item,
   return success;
 }
 
+static size_t memoryHandler(void *contents, size_t size, size_t nmemb, void *userp)
+{
+  std::vector<u_int8_t> *fileData = (std::vector<uint8_t>*) userp;
+  size_t numBytes = size * nmemb;
+  uint8_t* data = (uint8_t*) contents;
+  fileData->insert(fileData->end(), data, data + numBytes);
+  return numBytes;
+}
+
 Error Box_iloc::read_data(const Item& item,
                           const std::shared_ptr<StreamReader>& istr,
                           const std::shared_ptr<Box_idat>& idat,
@@ -1315,12 +1327,35 @@ Error Box_iloc::read_data(const Item& item,
           } else {
             std::string location = urlBox->get_location();
             if (location.rfind("https://", 0) == 0) {
-              // TODO
+#if HAVE_CURL
+              CURL *curl_handle;
+              CURLcode result;
+              curl_global_init(CURL_GLOBAL_ALL);
+              curl_handle = curl_easy_init();
+              std::vector<uint8_t> fileData;
+              curl_easy_setopt(curl_handle, CURLOPT_URL, location.c_str());
+              curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libheif/2.18.0");
+              curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, memoryHandler);
+              curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &fileData);
+              curl_easy_setopt(curl_handle, CURLOPT_BUFFERSIZE, 1024*1024);
+              result = curl_easy_perform(curl_handle);
+              curl_easy_cleanup(curl_handle);
+              if (result != CURLE_OK) {
+                std::stringstream sstr;
+                sstr << "Item construction method 2 with https location of " << location << " failed";
+                return Error(heif_error_Unsupported_feature,
+                            heif_suberror_Unsupported_item_construction_method,
+                            sstr.str());
+              }
+              auto memoryReader = std::make_shared<StreamReader_memory>(fileData.data(), fileData.size(), false);
+              bool success = read_extent(item, memoryReader, extent, dest);
+              assert(success);
+              (void) success;
+#else
               std::stringstream sstr;
-              sstr << "Item construction method 2 with https location of " << location << " is not implemented";
-              return Error(heif_error_Unsupported_feature,
-                          heif_suberror_Unsupported_item_construction_method,
-                          sstr.str());
+              sstr << "Item construction method 2 with https location of " << location << " is not supported without libcurl";
+              return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_item_construction_method, sstr.str());
+#endif
             } else {
               // See if we can read as a local file
               std::filesystem::path locationPath(location);