1 // Copyright 2016 PDFium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "core/fpdfapi/parser/cpdf_document.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "core/fpdfapi/cpdf_modulemgr.h"
11 #include "core/fpdfapi/parser/cpdf_array.h"
12 #include "core/fpdfapi/parser/cpdf_boolean.h"
13 #include "core/fpdfapi/parser/cpdf_dictionary.h"
14 #include "core/fpdfapi/parser/cpdf_linearized_header.h"
15 #include "core/fpdfapi/parser/cpdf_number.h"
16 #include "core/fpdfapi/parser/cpdf_parser.h"
17 #include "core/fpdfapi/parser/cpdf_reference.h"
18 #include "core/fpdfapi/parser/cpdf_string.h"
19 #include "core/fxcrt/fx_memory.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 #include "third_party/base/ptr_util.h"
22 
23 namespace {
24 
CreatePageTreeNode(std::unique_ptr<CPDF_Array> kids,CPDF_Document * pDoc,int count)25 CPDF_Dictionary* CreatePageTreeNode(std::unique_ptr<CPDF_Array> kids,
26                                     CPDF_Document* pDoc,
27                                     int count) {
28   CPDF_Array* pUnowned = pDoc->AddIndirectObject(std::move(kids))->AsArray();
29   CPDF_Dictionary* pageNode = pDoc->NewIndirect<CPDF_Dictionary>();
30   pageNode->SetNewFor<CPDF_String>("Type", "Pages", false);
31   pageNode->SetNewFor<CPDF_Reference>("Kids", pDoc, pUnowned->GetObjNum());
32   pageNode->SetNewFor<CPDF_Number>("Count", count);
33   for (size_t i = 0; i < pUnowned->GetCount(); i++) {
34     pUnowned->GetDictAt(i)->SetNewFor<CPDF_Reference>("Parent", pDoc,
35                                                       pageNode->GetObjNum());
36   }
37   return pageNode;
38 }
39 
CreateNumberedPage(size_t number)40 std::unique_ptr<CPDF_Dictionary> CreateNumberedPage(size_t number) {
41   auto page = pdfium::MakeUnique<CPDF_Dictionary>();
42   page->SetNewFor<CPDF_String>("Type", "Page", false);
43   page->SetNewFor<CPDF_Number>("PageNumbering", static_cast<int>(number));
44   return page;
45 }
46 
47 class CPDF_TestDocumentForPages : public CPDF_Document {
48  public:
CPDF_TestDocumentForPages()49   CPDF_TestDocumentForPages() : CPDF_Document(nullptr) {
50     // Set up test
51     auto zeroToTwo = pdfium::MakeUnique<CPDF_Array>();
52     zeroToTwo->AddNew<CPDF_Reference>(
53         this, AddIndirectObject(CreateNumberedPage(0))->GetObjNum());
54     zeroToTwo->AddNew<CPDF_Reference>(
55         this, AddIndirectObject(CreateNumberedPage(1))->GetObjNum());
56     zeroToTwo->AddNew<CPDF_Reference>(
57         this, AddIndirectObject(CreateNumberedPage(2))->GetObjNum());
58     CPDF_Dictionary* branch1 =
59         CreatePageTreeNode(std::move(zeroToTwo), this, 3);
60 
61     auto zeroToThree = pdfium::MakeUnique<CPDF_Array>();
62     zeroToThree->AddNew<CPDF_Reference>(this, branch1->GetObjNum());
63     zeroToThree->AddNew<CPDF_Reference>(
64         this, AddIndirectObject(CreateNumberedPage(3))->GetObjNum());
65     CPDF_Dictionary* branch2 =
66         CreatePageTreeNode(std::move(zeroToThree), this, 4);
67 
68     auto fourFive = pdfium::MakeUnique<CPDF_Array>();
69     fourFive->AddNew<CPDF_Reference>(
70         this, AddIndirectObject(CreateNumberedPage(4))->GetObjNum());
71     fourFive->AddNew<CPDF_Reference>(
72         this, AddIndirectObject(CreateNumberedPage(5))->GetObjNum());
73     CPDF_Dictionary* branch3 = CreatePageTreeNode(std::move(fourFive), this, 2);
74 
75     auto justSix = pdfium::MakeUnique<CPDF_Array>();
76     justSix->AddNew<CPDF_Reference>(
77         this, AddIndirectObject(CreateNumberedPage(6))->GetObjNum());
78     CPDF_Dictionary* branch4 = CreatePageTreeNode(std::move(justSix), this, 1);
79 
80     auto allPages = pdfium::MakeUnique<CPDF_Array>();
81     allPages->AddNew<CPDF_Reference>(this, branch2->GetObjNum());
82     allPages->AddNew<CPDF_Reference>(this, branch3->GetObjNum());
83     allPages->AddNew<CPDF_Reference>(this, branch4->GetObjNum());
84     CPDF_Dictionary* pagesDict =
85         CreatePageTreeNode(std::move(allPages), this, 7);
86 
87     m_pOwnedRootDict = pdfium::MakeUnique<CPDF_Dictionary>();
88     m_pOwnedRootDict->SetNewFor<CPDF_Reference>("Pages", this,
89                                                 pagesDict->GetObjNum());
90     m_pRootDict = m_pOwnedRootDict.get();
91     m_PageList.resize(7);
92   }
93 
94  private:
95   std::unique_ptr<CPDF_Dictionary> m_pOwnedRootDict;
96 };
97 
98 class CPDF_TestDocumentWithPageWithoutPageNum : public CPDF_Document {
99  public:
CPDF_TestDocumentWithPageWithoutPageNum()100   CPDF_TestDocumentWithPageWithoutPageNum() : CPDF_Document(nullptr) {
101     // Set up test
102     auto allPages = pdfium::MakeUnique<CPDF_Array>();
103     allPages->AddNew<CPDF_Reference>(
104         this, AddIndirectObject(CreateNumberedPage(0))->GetObjNum());
105     allPages->AddNew<CPDF_Reference>(
106         this, AddIndirectObject(CreateNumberedPage(1))->GetObjNum());
107     // Page without pageNum.
108     allPages->Add(CreateNumberedPage(2));
109     CPDF_Dictionary* pagesDict =
110         CreatePageTreeNode(std::move(allPages), this, 3);
111     m_pOwnedRootDict = pdfium::MakeUnique<CPDF_Dictionary>();
112     m_pOwnedRootDict->SetNewFor<CPDF_Reference>("Pages", this,
113                                                 pagesDict->GetObjNum());
114     m_pRootDict = m_pOwnedRootDict.get();
115     m_PageList.resize(3);
116   }
117 
118  private:
119   std::unique_ptr<CPDF_Dictionary> m_pOwnedRootDict;
120 };
121 
122 class TestLinearized : public CPDF_LinearizedHeader {
123  public:
TestLinearized(CPDF_Dictionary * dict)124   explicit TestLinearized(CPDF_Dictionary* dict)
125       : CPDF_LinearizedHeader(dict) {}
126 };
127 }  // namespace
128 
129 class cpdf_document_test : public testing::Test {
130  public:
SetUp()131   void SetUp() override {
132     CPDF_ModuleMgr* module_mgr = CPDF_ModuleMgr::Get();
133     module_mgr->InitPageModule();
134   }
TearDown()135   void TearDown() override { CPDF_ModuleMgr::Destroy(); }
136 };
137 
TEST_F(cpdf_document_test,GetPages)138 TEST_F(cpdf_document_test, GetPages) {
139   std::unique_ptr<CPDF_TestDocumentForPages> document =
140       pdfium::MakeUnique<CPDF_TestDocumentForPages>();
141   for (int i = 0; i < 7; i++) {
142     CPDF_Dictionary* page = document->GetPage(i);
143     ASSERT_TRUE(page);
144     ASSERT_TRUE(page->KeyExist("PageNumbering"));
145     EXPECT_EQ(i, page->GetIntegerFor("PageNumbering"));
146   }
147   CPDF_Dictionary* page = document->GetPage(7);
148   EXPECT_FALSE(page);
149 }
150 
TEST_F(cpdf_document_test,GetPageWithoutObjNumTwice)151 TEST_F(cpdf_document_test, GetPageWithoutObjNumTwice) {
152   std::unique_ptr<CPDF_TestDocumentWithPageWithoutPageNum> document =
153       pdfium::MakeUnique<CPDF_TestDocumentWithPageWithoutPageNum>();
154   const CPDF_Dictionary* page = document->GetPage(2);
155   ASSERT_TRUE(page);
156   // This is page without obj num.
157   ASSERT_EQ(0ul, page->GetObjNum());
158   const CPDF_Dictionary* second_call_page = document->GetPage(2);
159   EXPECT_TRUE(second_call_page);
160   EXPECT_EQ(page, second_call_page);
161 }
162 
TEST_F(cpdf_document_test,GetPagesReverseOrder)163 TEST_F(cpdf_document_test, GetPagesReverseOrder) {
164   std::unique_ptr<CPDF_TestDocumentForPages> document =
165       pdfium::MakeUnique<CPDF_TestDocumentForPages>();
166   for (int i = 6; i >= 0; i--) {
167     CPDF_Dictionary* page = document->GetPage(i);
168     ASSERT_TRUE(page);
169     ASSERT_TRUE(page->KeyExist("PageNumbering"));
170     EXPECT_EQ(i, page->GetIntegerFor("PageNumbering"));
171   }
172   CPDF_Dictionary* page = document->GetPage(7);
173   EXPECT_FALSE(page);
174 }
175 
TEST_F(cpdf_document_test,GetPagesInDisorder)176 TEST_F(cpdf_document_test, GetPagesInDisorder) {
177   std::unique_ptr<CPDF_TestDocumentForPages> document =
178       pdfium::MakeUnique<CPDF_TestDocumentForPages>();
179 
180   CPDF_Dictionary* page = document->GetPage(1);
181   ASSERT_TRUE(page);
182   ASSERT_TRUE(page->KeyExist("PageNumbering"));
183   EXPECT_EQ(1, page->GetIntegerFor("PageNumbering"));
184 
185   page = document->GetPage(3);
186   ASSERT_TRUE(page);
187   ASSERT_TRUE(page->KeyExist("PageNumbering"));
188   EXPECT_EQ(3, page->GetIntegerFor("PageNumbering"));
189 
190   page = document->GetPage(7);
191   EXPECT_FALSE(page);
192 
193   page = document->GetPage(6);
194   ASSERT_TRUE(page);
195   ASSERT_TRUE(page->KeyExist("PageNumbering"));
196   EXPECT_EQ(6, page->GetIntegerFor("PageNumbering"));
197 }
198 
TEST_F(cpdf_document_test,UseCachedPageObjNumIfHaveNotPagesDict)199 TEST_F(cpdf_document_test, UseCachedPageObjNumIfHaveNotPagesDict) {
200   // ObjNum can be added in CPDF_DataAvail::IsPageAvail, and PagesDict
201   // can be not exists in this case.
202   // (case, when hint table is used to page check in CPDF_DataAvail).
203   CPDF_Document document(pdfium::MakeUnique<CPDF_Parser>());
204   auto dict = pdfium::MakeUnique<CPDF_Dictionary>();
205   dict->SetNewFor<CPDF_Boolean>("Linearized", true);
206   const int page_count = 100;
207   dict->SetNewFor<CPDF_Number>("N", page_count);
208   TestLinearized linearized(dict.get());
209   document.LoadLinearizedDoc(&linearized);
210   ASSERT_EQ(page_count, document.GetPageCount());
211   CPDF_Object* page_stub = document.NewIndirect<CPDF_Dictionary>();
212   const uint32_t obj_num = page_stub->GetObjNum();
213   const int test_page_num = 33;
214 
215   EXPECT_FALSE(document.IsPageLoaded(test_page_num));
216   EXPECT_EQ(nullptr, document.GetPage(test_page_num));
217 
218   document.SetPageObjNum(test_page_num, obj_num);
219   EXPECT_TRUE(document.IsPageLoaded(test_page_num));
220   EXPECT_EQ(page_stub, document.GetPage(test_page_num));
221 }
222