blob: 43306f41d9f30372858d097ecbbf5b494b394f30 [file] [log] [blame]
// Copyright 2021 The PDFium Authors
// 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 <iterator>
#include <utility>
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_indirect_object_holder.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 "core/fxcrt/data_vector.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::ElementsAre;
namespace {
constexpr char kNormalStreamContent[] =
"10 0 11 14 12 21<</Name /Foo>>[1 2 3]4";
constexpr int kNormalStreamContentOffset = 16;
static_assert(kNormalStreamContent[kNormalStreamContentOffset] == '<',
"Wrong offset");
static_assert(kNormalStreamContent[kNormalStreamContentOffset + 1] == '<',
"Wrong offset");
} // namespace
TEST(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", kNormalStreamContentOffset);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
auto obj_stream = CPDF_ObjectStream::Create(std::move(stream));
ASSERT_TRUE(obj_stream);
EXPECT_THAT(obj_stream->object_info(),
ElementsAre(CPDF_ObjectStream::ObjectInfo(10, 0),
CPDF_ObjectStream::ObjectInfo(11, 14),
CPDF_ObjectStream::ObjectInfo(12, 21)));
// Check expected indices.
CPDF_IndirectObjectHolder holder;
RetainPtr<CPDF_Object> obj10 = obj_stream->ParseObject(&holder, 10, 0);
ASSERT_TRUE(obj10);
EXPECT_EQ(10u, obj10->GetObjNum());
EXPECT_EQ(0u, obj10->GetGenNum());
EXPECT_TRUE(obj10->IsDictionary());
RetainPtr<CPDF_Object> obj11 = obj_stream->ParseObject(&holder, 11, 1);
ASSERT_TRUE(obj11);
EXPECT_EQ(11u, obj11->GetObjNum());
EXPECT_EQ(0u, obj11->GetGenNum());
EXPECT_TRUE(obj11->IsArray());
RetainPtr<CPDF_Object> obj12 = obj_stream->ParseObject(&holder, 12, 2);
ASSERT_TRUE(obj12);
EXPECT_EQ(12u, obj12->GetObjNum());
EXPECT_EQ(0u, obj12->GetGenNum());
EXPECT_TRUE(obj12->IsNumber());
// Check bad indices.
EXPECT_FALSE(obj_stream->ParseObject(&holder, 10, 1));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 10, 2));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 10, 3));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 11, 0));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 11, 2));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 11, 3));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 12, 0));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 12, 1));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 12, 3));
}
TEST(ObjectStreamTest, StreamNoDict) {
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()),
/*pDict=*/nullptr);
EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream)));
}
TEST(ObjectStreamTest, StreamDictNoType) {
auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
dict->SetNewFor<CPDF_Number>("N", 3);
dict->SetNewFor<CPDF_Number>("First", 5);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream)));
}
TEST(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);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream)));
}
TEST(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);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream)));
}
TEST(ObjectStreamTest, StreamDictNoCount) {
auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
dict->SetNewFor<CPDF_Number>("First", 5);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream)));
}
TEST(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);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream)));
}
TEST(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);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream)));
}
TEST(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);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream)));
}
TEST(ObjectStreamTest, StreamDictNoOffset) {
auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
dict->SetNewFor<CPDF_Number>("N", 3);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream)));
}
TEST(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);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream)));
}
TEST(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);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream)));
}
TEST(ObjectStreamTest, StreamDictOffsetTooBig) {
auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
dict->SetNewFor<CPDF_Number>("N", 3);
constexpr int kTooBigOffset = std::size(kNormalStreamContent);
dict->SetNewFor<CPDF_Number>("First", kTooBigOffset);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
auto obj_stream = CPDF_ObjectStream::Create(std::move(stream));
ASSERT_TRUE(obj_stream);
EXPECT_THAT(obj_stream->object_info(),
ElementsAre(CPDF_ObjectStream::ObjectInfo(10, 0),
CPDF_ObjectStream::ObjectInfo(11, 14),
CPDF_ObjectStream::ObjectInfo(12, 21)));
CPDF_IndirectObjectHolder holder;
EXPECT_FALSE(obj_stream->ParseObject(&holder, 10, 0));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 11, 1));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 12, 2));
}
TEST(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", kNormalStreamContentOffset);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
auto obj_stream = CPDF_ObjectStream::Create(std::move(stream));
ASSERT_TRUE(obj_stream);
EXPECT_THAT(obj_stream->object_info(),
ElementsAre(CPDF_ObjectStream::ObjectInfo(10, 0),
CPDF_ObjectStream::ObjectInfo(11, 14)));
CPDF_IndirectObjectHolder holder;
RetainPtr<CPDF_Object> obj10 = obj_stream->ParseObject(&holder, 10, 0);
ASSERT_TRUE(obj10);
EXPECT_EQ(10u, obj10->GetObjNum());
EXPECT_EQ(0u, obj10->GetGenNum());
EXPECT_TRUE(obj10->IsDictionary());
RetainPtr<CPDF_Object> obj11 = obj_stream->ParseObject(&holder, 11, 1);
ASSERT_TRUE(obj11);
EXPECT_EQ(11u, obj11->GetObjNum());
EXPECT_EQ(0u, obj11->GetGenNum());
EXPECT_TRUE(obj11->IsArray());
EXPECT_FALSE(obj_stream->ParseObject(&holder, 12, 2));
}
TEST(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", kNormalStreamContentOffset);
ByteStringView contents_view(kNormalStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
auto obj_stream = CPDF_ObjectStream::Create(std::move(stream));
ASSERT_TRUE(obj_stream);
// TODO(thestig): Can this avoid finding object 2?
EXPECT_THAT(obj_stream->object_info(),
ElementsAre(CPDF_ObjectStream::ObjectInfo(10, 0),
CPDF_ObjectStream::ObjectInfo(11, 14),
CPDF_ObjectStream::ObjectInfo(12, 21),
CPDF_ObjectStream::ObjectInfo(2, 3)));
CPDF_IndirectObjectHolder holder;
EXPECT_FALSE(obj_stream->ParseObject(&holder, 2, 0));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 2, 1));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 2, 2));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 2, 3));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 2, 4));
}
TEST(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";
ByteStringView contents_view(kStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
auto obj_stream = CPDF_ObjectStream::Create(std::move(stream));
ASSERT_TRUE(obj_stream);
EXPECT_THAT(obj_stream->object_info(),
ElementsAre(CPDF_ObjectStream::ObjectInfo(10, 0),
CPDF_ObjectStream::ObjectInfo(12, 21)));
}
TEST(ObjectStreamTest, StreamDictGarbageObjectOffset) {
auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
dict->SetNewFor<CPDF_Number>("N", 3);
dict->SetNewFor<CPDF_Number>("First", 16);
const char kStreamContent[] = "10 0 11 hi 12 21<</Name /Foo>>[1 2 3]4";
ByteStringView contents_view(kStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
auto obj_stream = CPDF_ObjectStream::Create(std::move(stream));
ASSERT_TRUE(obj_stream);
// TODO(thestig): Should object 11 be rejected?
EXPECT_THAT(obj_stream->object_info(),
ElementsAre(CPDF_ObjectStream::ObjectInfo(10, 0),
CPDF_ObjectStream::ObjectInfo(11, 0),
CPDF_ObjectStream::ObjectInfo(12, 21)));
CPDF_IndirectObjectHolder holder;
RetainPtr<CPDF_Object> obj10 = obj_stream->ParseObject(&holder, 10, 0);
ASSERT_TRUE(obj10);
EXPECT_EQ(10u, obj10->GetObjNum());
EXPECT_EQ(0u, obj10->GetGenNum());
EXPECT_TRUE(obj10->IsDictionary());
RetainPtr<CPDF_Object> obj11 = obj_stream->ParseObject(&holder, 11, 1);
ASSERT_TRUE(obj11);
EXPECT_EQ(11u, obj11->GetObjNum());
EXPECT_EQ(0u, obj11->GetGenNum());
EXPECT_TRUE(obj11->IsDictionary());
}
TEST(ObjectStreamTest, StreamDictNegativeObjectOffset) {
auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
dict->SetNewFor<CPDF_Number>("N", 3);
dict->SetNewFor<CPDF_Number>("First", 16);
const char kStreamContent[] = "10 0 11 -1 12 21<</Name /Foo>>[1 2 3]4";
ByteStringView contents_view(kStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
auto obj_stream = CPDF_ObjectStream::Create(std::move(stream));
ASSERT_TRUE(obj_stream);
// TODO(thestig): Should object 11 be rejected?
EXPECT_THAT(obj_stream->object_info(),
ElementsAre(CPDF_ObjectStream::ObjectInfo(10, 0),
CPDF_ObjectStream::ObjectInfo(11, 4294967295),
CPDF_ObjectStream::ObjectInfo(12, 21)));
CPDF_IndirectObjectHolder holder;
EXPECT_FALSE(obj_stream->ParseObject(&holder, 11, 1));
}
TEST(ObjectStreamTest, StreamDictObjectOffsetTooBig) {
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 11 999 12 21<</Name /Foo>>[1 2 3]4";
ByteStringView contents_view(kStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
auto obj_stream = CPDF_ObjectStream::Create(std::move(stream));
ASSERT_TRUE(obj_stream);
// TODO(thestig): Should object 11 be rejected?
EXPECT_THAT(obj_stream->object_info(),
ElementsAre(CPDF_ObjectStream::ObjectInfo(10, 0),
CPDF_ObjectStream::ObjectInfo(11, 999),
CPDF_ObjectStream::ObjectInfo(12, 21)));
CPDF_IndirectObjectHolder holder;
EXPECT_FALSE(obj_stream->ParseObject(&holder, 11, 1));
}
TEST(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", 16);
const char kStreamContent[] = "10 0 10 14 12 21<</Name /Foo>>[1 2 3]4";
ByteStringView contents_view(kStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
auto obj_stream = CPDF_ObjectStream::Create(std::move(stream));
ASSERT_TRUE(obj_stream);
EXPECT_THAT(obj_stream->object_info(),
ElementsAre(CPDF_ObjectStream::ObjectInfo(10, 0),
CPDF_ObjectStream::ObjectInfo(10, 14),
CPDF_ObjectStream::ObjectInfo(12, 21)));
CPDF_IndirectObjectHolder holder;
RetainPtr<CPDF_Object> obj10 = obj_stream->ParseObject(&holder, 10, 0);
ASSERT_TRUE(obj10);
EXPECT_EQ(10u, obj10->GetObjNum());
EXPECT_EQ(0u, obj10->GetGenNum());
EXPECT_TRUE(obj10->IsDictionary());
obj10 = obj_stream->ParseObject(&holder, 10, 1);
ASSERT_TRUE(obj10);
EXPECT_EQ(10u, obj10->GetObjNum());
EXPECT_EQ(0u, obj10->GetGenNum());
EXPECT_TRUE(obj10->IsArray());
EXPECT_FALSE(obj_stream->ParseObject(&holder, 10, 2));
EXPECT_FALSE(obj_stream->ParseObject(&holder, 10, 3));
RetainPtr<CPDF_Object> obj12 = obj_stream->ParseObject(&holder, 12, 2);
ASSERT_TRUE(obj12);
EXPECT_EQ(12u, obj12->GetObjNum());
EXPECT_EQ(0u, obj12->GetGenNum());
EXPECT_TRUE(obj12->IsNumber());
}
TEST(ObjectStreamTest, StreamDictUnorderedObjectNumbers) {
// ISO 32000-1:2008 spec. section 7.5.7, note 6 says there is no restriction
// on object number ordering.
auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
dict->SetNewFor<CPDF_Number>("N", 3);
dict->SetNewFor<CPDF_Number>("First", 16);
const char kStreamContent[] = "11 0 12 14 10 21<</Name /Foo>>[1 2 3]4";
ByteStringView contents_view(kStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
auto obj_stream = CPDF_ObjectStream::Create(std::move(stream));
ASSERT_TRUE(obj_stream);
EXPECT_THAT(obj_stream->object_info(),
ElementsAre(CPDF_ObjectStream::ObjectInfo(11, 0),
CPDF_ObjectStream::ObjectInfo(12, 14),
CPDF_ObjectStream::ObjectInfo(10, 21)));
CPDF_IndirectObjectHolder holder;
RetainPtr<CPDF_Object> obj10 = obj_stream->ParseObject(&holder, 10, 2);
ASSERT_TRUE(obj10);
EXPECT_EQ(10u, obj10->GetObjNum());
EXPECT_EQ(0u, obj10->GetGenNum());
EXPECT_TRUE(obj10->IsNumber());
RetainPtr<CPDF_Object> obj11 = obj_stream->ParseObject(&holder, 11, 0);
ASSERT_TRUE(obj11);
EXPECT_EQ(11u, obj11->GetObjNum());
EXPECT_EQ(0u, obj11->GetGenNum());
EXPECT_TRUE(obj11->IsDictionary());
RetainPtr<CPDF_Object> obj12 = obj_stream->ParseObject(&holder, 12, 1);
ASSERT_TRUE(obj12);
EXPECT_EQ(12u, obj12->GetObjNum());
EXPECT_EQ(0u, obj12->GetGenNum());
EXPECT_TRUE(obj12->IsArray());
}
TEST(ObjectStreamTest, StreamDictUnorderedObjectOffsets) {
// ISO 32000-1:2008 spec. section 7.5.7, says offsets shall be in increasing
// order.
// TODO(thestig): Should CPDF_ObjectStream check for this and reject this
// object stream?
auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
dict->SetNewFor<CPDF_Name>("Type", "ObjStm");
dict->SetNewFor<CPDF_Number>("N", 3);
dict->SetNewFor<CPDF_Number>("First", 16);
const char kStreamContent[] = "10 21 11 0 12 14<</Name /Foo>>[1 2 3]4";
ByteStringView contents_view(kStreamContent);
auto stream = pdfium::MakeRetain<CPDF_Stream>(
DataVector<uint8_t>(contents_view.begin(), contents_view.end()), dict);
auto obj_stream = CPDF_ObjectStream::Create(std::move(stream));
ASSERT_TRUE(obj_stream);
EXPECT_THAT(obj_stream->object_info(),
ElementsAre(CPDF_ObjectStream::ObjectInfo(10, 21),
CPDF_ObjectStream::ObjectInfo(11, 0),
CPDF_ObjectStream::ObjectInfo(12, 14)));
CPDF_IndirectObjectHolder holder;
RetainPtr<CPDF_Object> obj10 = obj_stream->ParseObject(&holder, 10, 0);
ASSERT_TRUE(obj10);
EXPECT_EQ(10u, obj10->GetObjNum());
EXPECT_EQ(0u, obj10->GetGenNum());
EXPECT_TRUE(obj10->IsNumber());
RetainPtr<CPDF_Object> obj11 = obj_stream->ParseObject(&holder, 11, 1);
ASSERT_TRUE(obj11);
EXPECT_EQ(11u, obj11->GetObjNum());
EXPECT_EQ(0u, obj11->GetGenNum());
EXPECT_TRUE(obj11->IsDictionary());
RetainPtr<CPDF_Object> obj12 = obj_stream->ParseObject(&holder, 12, 2);
ASSERT_TRUE(obj12);
EXPECT_EQ(12u, obj12->GetObjNum());
EXPECT_EQ(0u, obj12->GetGenNum());
EXPECT_TRUE(obj12->IsArray());
}