Add unit tests for CPDF_ObjectStream.

Cover CPDF_ObjectStream creation and object offset calculations.

Change-Id: I0a135fa8291e79f0d78ef3159f6ae7a8864d4d31
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/85971
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/core/fpdfapi/parser/BUILD.gn b/core/fpdfapi/parser/BUILD.gn
index 21a341a..f164a49 100644
--- a/core/fpdfapi/parser/BUILD.gn
+++ b/core/fpdfapi/parser/BUILD.gn
@@ -104,6 +104,7 @@
     "cpdf_hint_tables_unittest.cpp",
     "cpdf_indirect_object_holder_unittest.cpp",
     "cpdf_object_avail_unittest.cpp",
+    "cpdf_object_stream_unittest.cpp",
     "cpdf_object_unittest.cpp",
     "cpdf_object_walker_unittest.cpp",
     "cpdf_page_object_avail_unittest.cpp",
diff --git a/core/fpdfapi/parser/cpdf_object_stream_unittest.cpp b/core/fpdfapi/parser/cpdf_object_stream_unittest.cpp
new file mode 100644
index 0000000..2e46524
--- /dev/null
+++ b/core/fpdfapi/parser/cpdf_object_stream_unittest.cpp
@@ -0,0 +1,231 @@
+// Copyright 2021 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/fpdfapi/parser/cpdf_object_stream.h"
+
+#include "core/fpdfapi/parser/cpdf_dictionary.h"
+#include "core/fpdfapi/parser/cpdf_name.h"
+#include "core/fpdfapi/parser/cpdf_number.h"
+#include "core/fpdfapi/parser/cpdf_stream.h"
+#include "core/fpdfapi/parser/cpdf_string.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::ElementsAre;
+using testing::Pair;
+
+namespace {
+
+const char kNormalStreamContent[] = "10 0 11 14 12 21<</Name /Foo>>[1 2 3]4";
+
+}  // namespace
+
+TEST(CPDF_ObjectStreamTest, StreamDictNormal) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("N", 3);
+  dict->SetNewFor<CPDF_Number>("First", 17);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  auto obj_stream = CPDF_ObjectStream::Create(stream.Get());
+  ASSERT_TRUE(obj_stream);
+
+  EXPECT_THAT(obj_stream->objects_offsets(),
+              ElementsAre(Pair(10, 0), Pair(11, 14), Pair(12, 21)));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamNoDict) {
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), /*pDict=*/nullptr);
+  EXPECT_FALSE(CPDF_ObjectStream::Create(stream.Get()));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictNoType) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Number>("N", 3);
+  dict->SetNewFor<CPDF_Number>("First", 5);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  EXPECT_FALSE(CPDF_ObjectStream::Create(stream.Get()));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictWrongType) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_String>("Type", "ObjStm", /*bHex=*/false);
+  dict->SetNewFor<CPDF_Number>("N", 3);
+  dict->SetNewFor<CPDF_Number>("First", 5);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  EXPECT_FALSE(CPDF_ObjectStream::Create(stream.Get()));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictWrongTypeValue) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStmmmm");
+  dict->SetNewFor<CPDF_Number>("N", 3);
+  dict->SetNewFor<CPDF_Number>("First", 5);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  EXPECT_FALSE(CPDF_ObjectStream::Create(stream.Get()));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictNoCount) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("First", 5);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  EXPECT_FALSE(CPDF_ObjectStream::Create(stream.Get()));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictFloatCount) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("N", 2.2f);
+  dict->SetNewFor<CPDF_Number>("First", 5);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  EXPECT_FALSE(CPDF_ObjectStream::Create(stream.Get()));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictNegativeCount) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("N", -1);
+  dict->SetNewFor<CPDF_Number>("First", 5);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  EXPECT_FALSE(CPDF_ObjectStream::Create(stream.Get()));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictCountTooBig) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("N", 999999999);
+  dict->SetNewFor<CPDF_Number>("First", 5);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  EXPECT_FALSE(CPDF_ObjectStream::Create(stream.Get()));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictNoOffset) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("N", 3);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  EXPECT_FALSE(CPDF_ObjectStream::Create(stream.Get()));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictFloatOffset) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("N", 3);
+  dict->SetNewFor<CPDF_Number>("First", 5.5f);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  EXPECT_FALSE(CPDF_ObjectStream::Create(stream.Get()));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictNegativeOffset) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("N", 3);
+  dict->SetNewFor<CPDF_Number>("First", -5);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  EXPECT_FALSE(CPDF_ObjectStream::Create(stream.Get()));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictTooFewCount) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("N", 2);
+  dict->SetNewFor<CPDF_Number>("First", 17);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  auto obj_stream = CPDF_ObjectStream::Create(stream.Get());
+  ASSERT_TRUE(obj_stream);
+
+  EXPECT_THAT(obj_stream->objects_offsets(),
+              ElementsAre(Pair(10, 0), Pair(11, 14)));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictTooManyObject) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("N", 9);
+  dict->SetNewFor<CPDF_Number>("First", 17);
+
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kNormalStreamContent).raw_span(), dict);
+  auto obj_stream = CPDF_ObjectStream::Create(stream.Get());
+  ASSERT_TRUE(obj_stream);
+
+  // TODO(thestig): Can this avoid finding object 2?
+  EXPECT_THAT(obj_stream->objects_offsets(),
+              ElementsAre(Pair(2, 3), Pair(10, 0), Pair(11, 14), Pair(12, 21)));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictGarbageObjNum) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("N", 3);
+  dict->SetNewFor<CPDF_Number>("First", 19);
+
+  const char kStreamContent[] = "10 0 hi 14 12 21<</Name /Foo>>[1 2 3]4";
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kStreamContent).raw_span(), dict);
+  auto obj_stream = CPDF_ObjectStream::Create(stream.Get());
+  ASSERT_TRUE(obj_stream);
+
+  EXPECT_THAT(obj_stream->objects_offsets(),
+              ElementsAre(Pair(10, 0), Pair(12, 21)));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictGarbageOffset) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("N", 3);
+  dict->SetNewFor<CPDF_Number>("First", 19);
+
+  const char kStreamContent[] = "10 0 11 hi 12 21<</Name /Foo>>[1 2 3]4";
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kStreamContent).raw_span(), dict);
+  auto obj_stream = CPDF_ObjectStream::Create(stream.Get());
+  ASSERT_TRUE(obj_stream);
+
+  // TODO(thestig): Should object 11 be rejected?
+  EXPECT_THAT(obj_stream->objects_offsets(),
+              ElementsAre(Pair(10, 0), Pair(11, 0), Pair(12, 21)));
+}
+
+TEST(CPDF_ObjectStreamTest, StreamDictDuplicateObjNum) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
+  dict->SetNewFor<CPDF_Number>("N", 3);
+  dict->SetNewFor<CPDF_Number>("First", 17);
+
+  const char kStreamContent[] = "10 0 10 14 12 21<</Name /Foo>>[1 2 3]4";
+  auto stream = pdfium::MakeRetain<CPDF_Stream>(
+      ByteStringView(kStreamContent).raw_span(), dict);
+  auto obj_stream = CPDF_ObjectStream::Create(stream.Get());
+  ASSERT_TRUE(obj_stream);
+
+  // TODO(thestig): Should object 10 be at offset 0 instead?
+  EXPECT_THAT(obj_stream->objects_offsets(),
+              ElementsAre(Pair(10, 14), Pair(12, 21)));
+}