Error Box_iref::parse()

in libheif/box.cc [3830:3952]


Error Box_iref::parse(BitstreamRange& range)
{
  parse_full_box_header(range);

  if (get_version() > 1) {
    return unsupported_version_error("iref");
  }

  while (!range.eof()) {
    Reference ref;

    Error err = ref.header.parse_header(range);
    if (err != Error::Ok) {
      return err;
    }

    if (get_version() == 0) {
      ref.from_item_ID = range.read16();
      int nRefs = range.read16();
      for (int i = 0; i < nRefs; i++) {
        ref.to_item_ID.push_back(range.read16());
        if (range.eof()) {
          break;
        }
      }
    }
    else {
      ref.from_item_ID = range.read32();
      int nRefs = range.read16();
      for (int i = 0; i < nRefs; i++) {
        ref.to_item_ID.push_back(range.read32());
        if (range.eof()) {
          break;
        }
      }
    }

    m_references.push_back(ref);
  }


  // --- check number of total refs

  size_t nTotalRefs = 0;
  for (const auto& ref : m_references) {
    nTotalRefs += ref.to_item_ID.size();
  }

  if (nTotalRefs > MAX_IREF_REFERENCES) {
    return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded,
                 "Number of iref references exceeds security limit.");
  }

  // --- check for duplicate references

  for (const auto& ref : m_references) {
    std::set<heif_item_id> to_ids;
    for (const auto to_id : ref.to_item_ID) {
      if (to_ids.find(to_id) == to_ids.end()) {
        to_ids.insert(to_id);
      }
      else {
        return Error(heif_error_Invalid_input,
                     heif_suberror_Unspecified,
                     "'iref' has double references");
      }
    }
  }


#if 0
  // Note: This input sanity check does not work as expected.
  // Its idea was to prevent infinite recursions while decoding when the input file
  // contains cyclic references. However, apparently there are cases where cyclic
  // references are actually allowed, like with images that have premultiplied alpha channels:
  // | Box: iref -----
  // | size: 40   (header size: 12)
  // | reference with type 'auxl' from ID: 2 to IDs: 1
  // | reference with type 'prem' from ID: 1 to IDs: 2
  //
  // TODO: implement the infinite recursion detection in a different way. E.g. by passing down
  //       the already processed item-ids while decoding an image and checking whether the current
  //       item has already been decoded before.

  // --- check for cyclic references

  for (const auto& ref : m_references) {
    std::set<heif_item_id> reached_ids; // IDs that we have already reached in the DAG
    std::set<heif_item_id> todo;    // IDs that still need to be followed

    todo.insert(ref.from_item_ID);  // start at base item

    while (!todo.empty()) {
      // transfer ID into set of reached IDs
      auto id = *todo.begin();
      todo.erase(id);
      reached_ids.insert(id);

      // if this ID refers to another 'iref', follow it

      for (const auto& succ_ref : m_references) {
        if (succ_ref.from_item_ID == id) {

          // Check whether any successor IDs has been visited yet, which would be an error.
          // Otherwise, put that ID into the 'todo' set.

          for (const auto& succ_ref_id : succ_ref.to_item_ID) {
            if (reached_ids.find(succ_ref_id) != reached_ids.end()) {
              return Error(heif_error_Invalid_input,
                           heif_suberror_Unspecified,
                           "'iref' has cyclic references");
            }

            todo.insert(succ_ref_id);
          }
        }
      }
    }
  }
#endif

  return range.get_error();
}