Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] add support for iloc construction method 2 #1129

Draft
wants to merge 6 commits into
base: develop-v1.18.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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})
Expand Down
2 changes: 2 additions & 0 deletions go/heif/heif.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions libheif/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
231 changes: 171 additions & 60 deletions libheif/box.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@
#include <iostream>
#include <algorithm>
#include <cstring>
#include <fstream>
#include <set>
#include <cassert>

#if WITH_UNCOMPRESSED_CODEC
#include "uncompressed_box.h"
#endif

#if HAVE_CURL
#include <curl/curl.h>
#endif

Fraction::Fraction(int32_t num, int32_t den)
{
Expand Down Expand Up @@ -1188,96 +1192,203 @@ 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");
}

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();

// --- make sure that all data is available
std::stringstream sstr;
sstr << "Extent in iloc box references data outside of file bounds "
<< "(points to file position " << extent.offset + item.base_offset << ")\n";

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");
}
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);
}

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();
// --- move file pointer to start 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";
bool success = istr->seek(extent.offset + item.base_offset);
assert(success);
(void) success;

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);
}

// --- move file pointer to start of data
// --- read data

bool success = istr->seek(extent.offset + item.base_offset);
assert(success);
(void) success;
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;
}

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;
}

// --- 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,
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
// 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;
}
else if (item.construction_method == 1) {
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,
extent.offset + item.base_offset,
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 (location.rfind("https://", 0) == 0) {
#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 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);
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;
}
}
} 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 " << 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());
}
}
Expand Down
8 changes: 8 additions & 0 deletions libheif/box.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <istream>
#include <bitset>
#include <utility>
#include <filesystem>

#include "error.h"
#include "heif.h"
Expand Down Expand Up @@ -391,6 +392,8 @@ 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>&,
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; }
Expand Down Expand Up @@ -430,6 +433,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
};
Expand Down Expand Up @@ -857,6 +864,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;
Expand Down
2 changes: 2 additions & 0 deletions libheif/error.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading