Handle 64b box sizes

See context at
https://aomedia-review.googlesource.com/c/libavifinfo/+/148321#:~:text=I%20see%20that,here%20as%20well.
Add test image.

Change-Id: Id71eca9f4bf4f974c7ffe284ed230dd129e94b53
diff --git a/avifinfo.c b/avifinfo.c
index 62ace0c..a66a825 100644
--- a/avifinfo.c
+++ b/avifinfo.c
@@ -100,18 +100,32 @@
     const uint8_t* bytes, uint32_t num_bytes, uint32_t max_num_bytes,
     uint32_t position, uint32_t* num_parsed_boxes, AvifInfoInternalBox* box) {
   // See ISO/IEC 14496-12:2012(E) 4.2
-  AVIFINFO_CHECK(position <= kAvifInfoInternalMaxSize - 8, kAborted);
-  AVIFINFO_CHECK(position + 8 <= max_num_bytes, kInvalid);  // box size+type
-  AVIFINFO_CHECK(position + 4 <= num_bytes, kTruncated);    // 32b size
+  uint32_t box_header_size = 8;  // box 32b size + 32b type (at least)
+  AVIFINFO_CHECK(position <= kAvifInfoInternalMaxSize - box_header_size,
+                 kAborted);
+  AVIFINFO_CHECK(position + box_header_size <= max_num_bytes, kInvalid);
+  AVIFINFO_CHECK(position + box_header_size <= num_bytes, kTruncated);
   box->size = AvifInfoInternalReadBigEndian(bytes + position, sizeof(uint32_t));
-  // Note: 'box->size==1' means 64b size should be read.
-  //       'box->size==0' means this box extends to all remaining bytes.
-  //       These two use cases are not handled here for simplicity.
-  AVIFINFO_CHECK(box->size >= 2, kAborted);
-  AVIFINFO_CHECK(box->size >= 8, kInvalid);  // box 32b size + 32b type
+  // 'box->size==1' means 64-bit size should be read after the box type.
+  // 'box->size==0' means this box extends to all remaining bytes.
+  if (box->size == 1) {
+    box_header_size += 8;
+    AVIFINFO_CHECK(position + box_header_size <= max_num_bytes, kInvalid);
+    AVIFINFO_CHECK(position + box_header_size <= num_bytes, kTruncated);
+    // Stop the parsing if any box has a size greater than 4GB.
+    AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(bytes + position + 8,
+                                                 sizeof(uint32_t)) == 0,
+                   kAborted);
+    // Read the 32 least-significant bits.
+    box->size =
+        AvifInfoInternalReadBigEndian(bytes + position + 12, sizeof(uint32_t));
+  } else if (box->size == 0) {
+    box->size = max_num_bytes - position;
+  }
+  AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
   AVIFINFO_CHECK(box->size <= kAvifInfoInternalMaxSize - position, kAborted);
   AVIFINFO_CHECK(position + box->size <= max_num_bytes, kInvalid);
-  AVIFINFO_CHECK(position + 8 <= num_bytes, kTruncated);
+  AVIFINFO_CHECK(position + box_header_size <= num_bytes, kTruncated);
   box->type = bytes + position + 4;
 
   const int has_fullbox_header =
@@ -119,7 +133,7 @@
       !memcmp(box->type, "ipma", 4) || !memcmp(box->type, "ispe", 4) ||
       !memcmp(box->type, "pixi", 4) || !memcmp(box->type, "iref", 4) ||
       !memcmp(box->type, "auxC", 4);
-  const uint32_t box_header_size = (has_fullbox_header ? 12 : 8);
+  if (has_fullbox_header) box_header_size += 4;
   AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
   box->content_position = position + box_header_size;
   AVIFINFO_CHECK(box->content_position <= num_bytes, kTruncated);
@@ -153,7 +167,7 @@
 
 // Returns kFound if 'min_size' bytes can be read from the 'box.content' now.
 // 'num_bytes' is the number of available bytes of the parent of the 'box'.
-static AvifInfoInternalStatus AccessContent(AvifInfoInternalBox* box,
+static AvifInfoInternalStatus AccessContent(const AvifInfoInternalBox* box,
                                             uint32_t num_bytes,
                                             uint32_t min_size) {
   AVIFINFO_CHECK(box->content_size >= min_size, kInvalid);
diff --git a/tests/avifinfo_test.cc b/tests/avifinfo_test.cc
index de28ba8..fe59b14 100644
--- a/tests/avifinfo_test.cc
+++ b/tests/avifinfo_test.cc
@@ -44,6 +44,22 @@
   EXPECT_EQ(features.num_channels, 3u);
 }
 
+TEST(AvifInfoGetTest, NoPixi10b) {
+  // Same as above but "meta" box size is stored as 64 bits, "av1C" has
+  // 'high_bitdepth' set to true, "pixi" was renamed to "pixy" and "mdat" size
+  // is 0 (extends to the end of the file).
+  const Data input =
+      LoadFile("avifinfo_test_1x1_10b_nopixi_metasize64b_mdatsize0.avif");
+  ASSERT_FALSE(input.empty());
+
+  AvifInfoFeatures features;
+  EXPECT_EQ(AvifInfoGet(input.data(), input.size(), &features), kAvifInfoOk);
+  EXPECT_EQ(features.width, 1u);
+  EXPECT_EQ(features.height, 1u);
+  EXPECT_EQ(features.bit_depth, 10u);
+  EXPECT_EQ(features.num_channels, 3u);
+}
+
 TEST(AvifInfoGetTest, WithFileSize) {
   const Data input = LoadFile("avifinfo_test_1x1.avif");
   ASSERT_FALSE(input.empty());
diff --git a/tests/avifinfo_test_1x1_10b_nopixi_metasize64b_mdatsize0.avif b/tests/avifinfo_test_1x1_10b_nopixi_metasize64b_mdatsize0.avif
new file mode 100644
index 0000000..64d5377
--- /dev/null
+++ b/tests/avifinfo_test_1x1_10b_nopixi_metasize64b_mdatsize0.avif
Binary files differ