// Copyright 2016 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_document.h" #include #include #include "core/fpdfapi/page/test_with_page_module.h" #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_boolean.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_linearized_header.h" #include "core/fpdfapi/parser/cpdf_name.h" #include "core/fpdfapi/parser/cpdf_number.h" #include "core/fpdfapi/parser/cpdf_parser.h" #include "core/fpdfapi/parser/cpdf_reference.h" #include "core/fpdfapi/parser/cpdf_string.h" #include "core/fpdfapi/parser/cpdf_test_document.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/base/check.h" namespace { const int kNumTestPages = 7; RetainPtr CreatePageTreeNode(RetainPtr kids, CPDF_Document* pDoc, int count) { uint32_t new_objnum = pDoc->AddIndirectObject(kids); auto pageNode = pDoc->NewIndirect(); pageNode->SetNewFor("Type", "Pages"); pageNode->SetNewFor("Kids", pDoc, new_objnum); pageNode->SetNewFor("Count", count); for (size_t i = 0; i < kids->size(); i++) { kids->GetMutableDictAt(i)->SetNewFor("Parent", pDoc, pageNode->GetObjNum()); } return pageNode; } RetainPtr CreateNumberedPage(size_t number) { auto page = pdfium::MakeRetain(); page->SetNewFor("Type", "Page"); page->SetNewFor("PageNumbering", static_cast(number)); return page; } class CPDF_TestDocumentForPages final : public CPDF_TestDocument { public: CPDF_TestDocumentForPages() { // Set up test auto zeroToTwo = pdfium::MakeRetain(); zeroToTwo->AppendNew( this, AddIndirectObject(CreateNumberedPage(0))); zeroToTwo->AppendNew( this, AddIndirectObject(CreateNumberedPage(1))); zeroToTwo->AppendNew( this, AddIndirectObject(CreateNumberedPage(2))); RetainPtr branch1 = CreatePageTreeNode(std::move(zeroToTwo), this, 3); auto zeroToThree = pdfium::MakeRetain(); zeroToThree->AppendNew(this, branch1->GetObjNum()); zeroToThree->AppendNew( this, AddIndirectObject(CreateNumberedPage(3))); RetainPtr branch2 = CreatePageTreeNode(std::move(zeroToThree), this, 4); auto fourFive = pdfium::MakeRetain(); fourFive->AppendNew( this, AddIndirectObject(CreateNumberedPage(4))); fourFive->AppendNew( this, AddIndirectObject(CreateNumberedPage(5))); RetainPtr branch3 = CreatePageTreeNode(std::move(fourFive), this, 2); auto justSix = pdfium::MakeRetain(); justSix->AppendNew( this, AddIndirectObject(CreateNumberedPage(6))); RetainPtr branch4 = CreatePageTreeNode(std::move(justSix), this, 1); auto allPages = pdfium::MakeRetain(); allPages->AppendNew(this, branch2->GetObjNum()); allPages->AppendNew(this, branch3->GetObjNum()); allPages->AppendNew(this, branch4->GetObjNum()); RetainPtr pagesDict = CreatePageTreeNode(std::move(allPages), this, kNumTestPages); SetRootForTesting(NewIndirect()); GetMutableRoot()->SetNewFor("Pages", this, pagesDict->GetObjNum()); ResizePageListForTesting(kNumTestPages); } void SetTreeSize(int size) { GetMutableRoot()->SetNewFor("Count", size); ResizePageListForTesting(size); } }; class CPDF_TestDocumentWithPageWithoutPageNum final : public CPDF_TestDocument { public: CPDF_TestDocumentWithPageWithoutPageNum() { // Set up test auto allPages = pdfium::MakeRetain(); allPages->AppendNew( this, AddIndirectObject(CreateNumberedPage(0))); allPages->AppendNew( this, AddIndirectObject(CreateNumberedPage(1))); // Page without pageNum. inlined_page_ = CreateNumberedPage(2); allPages->Append(inlined_page_); RetainPtr pagesDict = CreatePageTreeNode(std::move(allPages), this, 3); SetRootForTesting(NewIndirect()); GetMutableRoot()->SetNewFor("Pages", this, pagesDict->GetObjNum()); ResizePageListForTesting(3); } const CPDF_Object* inlined_page() const { return inlined_page_.Get(); } private: RetainPtr inlined_page_; }; class TestLinearized final : public CPDF_LinearizedHeader { public: explicit TestLinearized(CPDF_Dictionary* dict) : CPDF_LinearizedHeader(dict, 0) {} }; class CPDF_TestDocPagesWithoutKids final : public CPDF_TestDocument { public: CPDF_TestDocPagesWithoutKids() { auto pagesDict = NewIndirect(); pagesDict->SetNewFor("Type", "Pages"); pagesDict->SetNewFor("Count", 3); ResizePageListForTesting(10); SetRootForTesting(NewIndirect()); GetMutableRoot()->SetNewFor("Pages", this, pagesDict->GetObjNum()); } }; class CPDF_TestDocumentAllowSetParser final : public CPDF_TestDocument { public: CPDF_TestDocumentAllowSetParser() = default; using CPDF_Document::SetParser; }; } // namespace using DocumentTest = TestWithPageModule; TEST_F(DocumentTest, GetPages) { std::unique_ptr document = std::make_unique(); for (int i = 0; i < kNumTestPages; i++) { RetainPtr page = document->GetPageDictionary(i); ASSERT_TRUE(page); ASSERT_TRUE(page->KeyExist("PageNumbering")); EXPECT_EQ(i, page->GetIntegerFor("PageNumbering")); } RetainPtr page = document->GetPageDictionary(kNumTestPages); EXPECT_FALSE(page); } TEST_F(DocumentTest, GetPageWithoutObjNumTwice) { auto document = std::make_unique(); RetainPtr page = document->GetPageDictionary(2); ASSERT_TRUE(page); ASSERT_EQ(document->inlined_page(), page); RetainPtr second_call_page = document->GetPageDictionary(2); EXPECT_TRUE(second_call_page); EXPECT_EQ(page, second_call_page); } TEST_F(DocumentTest, GetPagesReverseOrder) { std::unique_ptr document = std::make_unique(); for (int i = 6; i >= 0; i--) { RetainPtr page = document->GetPageDictionary(i); ASSERT_TRUE(page); ASSERT_TRUE(page->KeyExist("PageNumbering")); EXPECT_EQ(i, page->GetIntegerFor("PageNumbering")); } RetainPtr page = document->GetPageDictionary(kNumTestPages); EXPECT_FALSE(page); } TEST_F(DocumentTest, GetPagesInDisorder) { std::unique_ptr document = std::make_unique(); RetainPtr page = document->GetPageDictionary(1); ASSERT_TRUE(page); ASSERT_TRUE(page->KeyExist("PageNumbering")); EXPECT_EQ(1, page->GetIntegerFor("PageNumbering")); page = document->GetPageDictionary(3); ASSERT_TRUE(page); ASSERT_TRUE(page->KeyExist("PageNumbering")); EXPECT_EQ(3, page->GetIntegerFor("PageNumbering")); page = document->GetPageDictionary(kNumTestPages); EXPECT_FALSE(page); page = document->GetPageDictionary(6); ASSERT_TRUE(page); ASSERT_TRUE(page->KeyExist("PageNumbering")); EXPECT_EQ(6, page->GetIntegerFor("PageNumbering")); } TEST_F(DocumentTest, IsValidPageObject) { CPDF_TestDocumentForPages document; auto dict_type_name_page = pdfium::MakeRetain(); dict_type_name_page->SetNewFor("Type", "Page"); document.AddIndirectObject(dict_type_name_page); EXPECT_TRUE(CPDF_Document::IsValidPageObject(dict_type_name_page.Get())); auto dict_type_string_page = pdfium::MakeRetain(); dict_type_string_page->SetNewFor("Type", "Page", false); document.AddIndirectObject(dict_type_string_page); EXPECT_FALSE(CPDF_Document::IsValidPageObject(dict_type_string_page.Get())); auto dict_type_name_font = pdfium::MakeRetain(); dict_type_name_font->SetNewFor("Type", "Font"); document.AddIndirectObject(dict_type_name_font); EXPECT_FALSE(CPDF_Document::IsValidPageObject(dict_type_name_font.Get())); auto obj_no_type = document.NewIndirect(); EXPECT_FALSE(CPDF_Document::IsValidPageObject(obj_no_type.Get())); } TEST_F(DocumentTest, UseCachedPageObjNumIfHaveNotPagesDict) { // ObjNum can be added in CPDF_DataAvail::IsPageAvail(), and PagesDict may not // exist in this case, e.g. when hint table is used to page check in // CPDF_DataAvail. constexpr int kPageCount = 100; constexpr int kTestPageNum = 33; auto linearization_dict = pdfium::MakeRetain(); CPDF_TestDocumentAllowSetParser document; { auto first_page = CreateNumberedPage(0); ASSERT_TRUE(first_page); int first_page_obj_num = document.AddIndirectObject(first_page); ASSERT_NE(kTestPageNum, first_page_obj_num); linearization_dict->SetNewFor("Linearized", true); linearization_dict->SetNewFor("N", kPageCount); linearization_dict->SetNewFor("O", first_page_obj_num); auto parser = std::make_unique(); parser->SetLinearizedHeaderForTesting( std::make_unique(linearization_dict.Get())); document.SetParser(std::move(parser)); } document.LoadPages(); ASSERT_EQ(kPageCount, document.GetPageCount()); auto page_stub = document.NewIndirect(); const uint32_t obj_num = page_stub->GetObjNum(); EXPECT_FALSE(document.IsPageLoaded(kTestPageNum)); EXPECT_FALSE(document.GetPageDictionary(kTestPageNum)); document.SetPageObjNum(kTestPageNum, obj_num); EXPECT_TRUE(document.IsPageLoaded(kTestPageNum)); EXPECT_EQ(page_stub, document.GetPageDictionary(kTestPageNum)); } TEST_F(DocumentTest, CountGreaterThanPageTree) { std::unique_ptr document = std::make_unique(); document->SetTreeSize(kNumTestPages + 3); for (int i = 0; i < kNumTestPages; i++) EXPECT_TRUE(document->GetPageDictionary(i)); for (int i = kNumTestPages; i < kNumTestPages + 4; i++) EXPECT_FALSE(document->GetPageDictionary(i)); EXPECT_TRUE(document->GetPageDictionary(kNumTestPages - 1)); } TEST_F(DocumentTest, PagesWithoutKids) { // Set up a document with Pages dict without kids, and Count = 3 auto pDoc = std::make_unique(); EXPECT_TRUE(pDoc->GetPageDictionary(0)); // Test GetPage does not fetch pages out of range for (int i = 1; i < 5; i++) EXPECT_FALSE(pDoc->GetPageDictionary(i)); EXPECT_TRUE(pDoc->GetPageDictionary(0)); }