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_name.h"
16 #include "core/fpdfapi/parser/cpdf_number.h"
17 #include "core/fpdfapi/parser/cpdf_parser.h"
18 #include "core/fpdfapi/parser/cpdf_reference.h"
19 #include "core/fpdfapi/parser/cpdf_string.h"
20 #include "core/fxcrt/fx_memory.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22 #include "third_party/base/ptr_util.h"
23 
24 namespace {
25 
26 const int kNumTestPages = 7;
27 
CreatePageTreeNode(std::unique_ptr<CPDF_Array> kids,CPDF_Document * pDoc,int count)28 CPDF_Dictionary* CreatePageTreeNode(std::unique_ptr<CPDF_Array> kids,
29                                     CPDF_Document* pDoc,
30                                     int count) {
31   CPDF_Array* pUnowned = pDoc->AddIndirectObject(std::move(kids))->AsArray();
32   CPDF_Dictionary* pageNode = pDoc->NewIndirect<CPDF_Dictionary>();
33   pageNode->SetNewFor<CPDF_String>("Type", "Pages", false);
34   pageNode->SetNewFor<CPDF_Reference>("Kids", pDoc, pUnowned->GetObjNum());
35   pageNode->SetNewFor<CPDF_Number>("Count", count);
36   for (size_t i = 0; i < pUnowned->GetCount(); i++) {
37     pUnowned->GetDictAt(i)->SetNewFor<CPDF_Reference>("Parent", pDoc,
38                                                       pageNode->GetObjNum());
39   }
40   return pageNode;
41 }
42 
CreateNumberedPage(size_t number)43 std::unique_ptr<CPDF_Dictionary> CreateNumberedPage(size_t number) {
44   auto page = pdfium::MakeUnique<CPDF_Dictionary>();
45   page->SetNewFor<CPDF_String>("Type", "Page", false);
46   page->SetNewFor<CPDF_Number>("PageNumbering", static_cast<int>(number));
47   return page;
48 }
49 
50 class CPDF_TestDocumentForPages : public CPDF_Document {
51  public:
CPDF_TestDocumentForPages()52   CPDF_TestDocumentForPages() : CPDF_Document(nullptr) {
53     // Set up test
54     auto zeroToTwo = pdfium::MakeUnique<CPDF_Array>();
55     zeroToTwo->AddNew<CPDF_Reference>(
56         this, AddIndirectObject(CreateNumberedPage(0))->GetObjNum());
57     zeroToTwo->AddNew<CPDF_Reference>(
58         this, AddIndirectObject(CreateNumberedPage(1))->GetObjNum());
59     zeroToTwo->AddNew<CPDF_Reference>(
60         this, AddIndirectObject(CreateNumberedPage(2))->GetObjNum());
61     CPDF_Dictionary* branch1 =
62         CreatePageTreeNode(std::move(zeroToTwo), this, 3);
63 
64     auto zeroToThree = pdfium::MakeUnique<CPDF_Array>();
65     zeroToThree->AddNew<CPDF_Reference>(this, branch1->GetObjNum());
66     zeroToThree->AddNew<CPDF_Reference>(
67         this, AddIndirectObject(CreateNumberedPage(3))->GetObjNum());
68     CPDF_Dictionary* branch2 =
69         CreatePageTreeNode(std::move(zeroToThree), this, 4);
70 
71     auto fourFive = pdfium::MakeUnique<CPDF_Array>();
72     fourFive->AddNew<CPDF_Reference>(
73         this, AddIndirectObject(CreateNumberedPage(4))->GetObjNum());
74     fourFive->AddNew<CPDF_Reference>(
75         this, AddIndirectObject(CreateNumberedPage(5))->GetObjNum());
76     CPDF_Dictionary* branch3 = CreatePageTreeNode(std::move(fourFive), this, 2);
77 
78     auto justSix = pdfium::MakeUnique<CPDF_Array>();
79     justSix->AddNew<CPDF_Reference>(
80         this, AddIndirectObject(CreateNumberedPage(6))->GetObjNum());
81     CPDF_Dictionary* branch4 = CreatePageTreeNode(std::move(justSix), this, 1);
82 
83     auto allPages = pdfium::MakeUnique<CPDF_Array>();
84     allPages->AddNew<CPDF_Reference>(this, branch2->GetObjNum());
85     allPages->AddNew<CPDF_Reference>(this, branch3->GetObjNum());
86     allPages->AddNew<CPDF_Reference>(this, branch4->GetObjNum());
87     CPDF_Dictionary* pagesDict =
88         CreatePageTreeNode(std::move(allPages), this, kNumTestPages);
89 
90     m_pOwnedRootDict = pdfium::MakeUnique<CPDF_Dictionary>();
91     m_pOwnedRootDict->SetNewFor<CPDF_Reference>("Pages", this,
92                                                 pagesDict->GetObjNum());
93     m_pRootDict = m_pOwnedRootDict.get();
94     m_PageList.resize(kNumTestPages);
95   }
96 
SetTreeSize(int size)97   void SetTreeSize(int size) {
98     m_pOwnedRootDict->SetNewFor<CPDF_Number>("Count", size);
99     m_PageList.resize(size);
100   }
101 
102  private:
103   std::unique_ptr<CPDF_Dictionary> m_pOwnedRootDict;
104 };
105 
106 class CPDF_TestDocumentWithPageWithoutPageNum : public CPDF_Document {
107  public:
CPDF_TestDocumentWithPageWithoutPageNum()108   CPDF_TestDocumentWithPageWithoutPageNum() : CPDF_Document(nullptr) {
109     // Set up test
110     auto allPages = pdfium::MakeUnique<CPDF_Array>();
111     allPages->AddNew<CPDF_Reference>(
112         this, AddIndirectObject(CreateNumberedPage(0))->GetObjNum());
113     allPages->AddNew<CPDF_Reference>(
114         this, AddIndirectObject(CreateNumberedPage(1))->GetObjNum());
115     // Page without pageNum.
116     inlined_page_ = allPages->Add(CreateNumberedPage(2));
117     CPDF_Dictionary* pagesDict =
118         CreatePageTreeNode(std::move(allPages), this, 3);
119     m_pOwnedRootDict = pdfium::MakeUnique<CPDF_Dictionary>();
120     m_pOwnedRootDict->SetNewFor<CPDF_Reference>("Pages", this,
121                                                 pagesDict->GetObjNum());
122     m_pRootDict = m_pOwnedRootDict.get();
123     m_PageList.resize(3);
124   }
125 
inlined_page() const126   const CPDF_Object* inlined_page() const { return inlined_page_; }
127 
128  private:
129   std::unique_ptr<CPDF_Dictionary> m_pOwnedRootDict;
130   const CPDF_Object* inlined_page_;
131 };
132 
133 class TestLinearized : public CPDF_LinearizedHeader {
134  public:
TestLinearized(CPDF_Dictionary * dict)135   explicit TestLinearized(CPDF_Dictionary* dict)
136       : CPDF_LinearizedHeader(dict, 0) {}
137 };
138 
139 class CPDF_TestDocPagesWithoutKids : public CPDF_Document {
140  public:
CPDF_TestDocPagesWithoutKids()141   CPDF_TestDocPagesWithoutKids() : CPDF_Document(nullptr) {
142     CPDF_Dictionary* pagesDict = NewIndirect<CPDF_Dictionary>();
143     pagesDict->SetNewFor<CPDF_Name>("Type", "Pages");
144     pagesDict->SetNewFor<CPDF_Number>("Count", 3);
145     m_PageList.resize(10);
146     m_pOwnedRootDict = pdfium::MakeUnique<CPDF_Dictionary>();
147     m_pOwnedRootDict->SetNewFor<CPDF_Reference>("Pages", this,
148                                                 pagesDict->GetObjNum());
149     m_pRootDict = m_pOwnedRootDict.get();
150   }
151 
152  private:
153   std::unique_ptr<CPDF_Dictionary> m_pOwnedRootDict;
154 };
155 }  // namespace
156 
157 class cpdf_document_test : public testing::Test {
158  public:
SetUp()159   void SetUp() override { CPDF_ModuleMgr::Get()->Init(); }
TearDown()160   void TearDown() override { CPDF_ModuleMgr::Destroy(); }
161 };
162 
TEST_F(cpdf_document_test,GetPages)163 TEST_F(cpdf_document_test, GetPages) {
164   std::unique_ptr<CPDF_TestDocumentForPages> document =
165       pdfium::MakeUnique<CPDF_TestDocumentForPages>();
166   for (int i = 0; i < kNumTestPages; 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(kNumTestPages);
173   EXPECT_FALSE(page);
174 }
175 
TEST_F(cpdf_document_test,GetPageWithoutObjNumTwice)176 TEST_F(cpdf_document_test, GetPageWithoutObjNumTwice) {
177   auto document = pdfium::MakeUnique<CPDF_TestDocumentWithPageWithoutPageNum>();
178   const CPDF_Dictionary* page = document->GetPage(2);
179   ASSERT_TRUE(page);
180   ASSERT_EQ(document->inlined_page(), page);
181 
182   const CPDF_Dictionary* second_call_page = document->GetPage(2);
183   EXPECT_TRUE(second_call_page);
184   EXPECT_EQ(page, second_call_page);
185 }
186 
TEST_F(cpdf_document_test,GetPagesReverseOrder)187 TEST_F(cpdf_document_test, GetPagesReverseOrder) {
188   std::unique_ptr<CPDF_TestDocumentForPages> document =
189       pdfium::MakeUnique<CPDF_TestDocumentForPages>();
190   for (int i = 6; i >= 0; i--) {
191     CPDF_Dictionary* page = document->GetPage(i);
192     ASSERT_TRUE(page);
193     ASSERT_TRUE(page->KeyExist("PageNumbering"));
194     EXPECT_EQ(i, page->GetIntegerFor("PageNumbering"));
195   }
196   CPDF_Dictionary* page = document->GetPage(kNumTestPages);
197   EXPECT_FALSE(page);
198 }
199 
TEST_F(cpdf_document_test,GetPagesInDisorder)200 TEST_F(cpdf_document_test, GetPagesInDisorder) {
201   std::unique_ptr<CPDF_TestDocumentForPages> document =
202       pdfium::MakeUnique<CPDF_TestDocumentForPages>();
203 
204   CPDF_Dictionary* page = document->GetPage(1);
205   ASSERT_TRUE(page);
206   ASSERT_TRUE(page->KeyExist("PageNumbering"));
207   EXPECT_EQ(1, page->GetIntegerFor("PageNumbering"));
208 
209   page = document->GetPage(3);
210   ASSERT_TRUE(page);
211   ASSERT_TRUE(page->KeyExist("PageNumbering"));
212   EXPECT_EQ(3, page->GetIntegerFor("PageNumbering"));
213 
214   page = document->GetPage(kNumTestPages);
215   EXPECT_FALSE(page);
216 
217   page = document->GetPage(6);
218   ASSERT_TRUE(page);
219   ASSERT_TRUE(page->KeyExist("PageNumbering"));
220   EXPECT_EQ(6, page->GetIntegerFor("PageNumbering"));
221 }
222 
TEST_F(cpdf_document_test,UseCachedPageObjNumIfHaveNotPagesDict)223 TEST_F(cpdf_document_test, UseCachedPageObjNumIfHaveNotPagesDict) {
224   // ObjNum can be added in CPDF_DataAvail::IsPageAvail, and PagesDict
225   // can be not exists in this case.
226   // (case, when hint table is used to page check in CPDF_DataAvail).
227   CPDF_Document document(pdfium::MakeUnique<CPDF_Parser>());
228   auto dict = pdfium::MakeUnique<CPDF_Dictionary>();
229   dict->SetNewFor<CPDF_Boolean>("Linearized", true);
230   const int page_count = 100;
231   dict->SetNewFor<CPDF_Number>("N", page_count);
232   TestLinearized linearized(dict.get());
233   document.LoadLinearizedDoc(&linearized);
234   ASSERT_EQ(page_count, document.GetPageCount());
235   CPDF_Object* page_stub = document.NewIndirect<CPDF_Dictionary>();
236   const uint32_t obj_num = page_stub->GetObjNum();
237   const int test_page_num = 33;
238 
239   EXPECT_FALSE(document.IsPageLoaded(test_page_num));
240   EXPECT_EQ(nullptr, document.GetPage(test_page_num));
241 
242   document.SetPageObjNum(test_page_num, obj_num);
243   EXPECT_TRUE(document.IsPageLoaded(test_page_num));
244   EXPECT_EQ(page_stub, document.GetPage(test_page_num));
245 }
246 
TEST_F(cpdf_document_test,CountGreaterThanPageTree)247 TEST_F(cpdf_document_test, CountGreaterThanPageTree) {
248   std::unique_ptr<CPDF_TestDocumentForPages> document =
249       pdfium::MakeUnique<CPDF_TestDocumentForPages>();
250   document->SetTreeSize(kNumTestPages + 3);
251   for (int i = 0; i < kNumTestPages; i++)
252     EXPECT_TRUE(document->GetPage(i));
253   for (int i = kNumTestPages; i < kNumTestPages + 4; i++)
254     EXPECT_FALSE(document->GetPage(i));
255   EXPECT_TRUE(document->GetPage(kNumTestPages - 1));
256 }
257 
TEST_F(cpdf_document_test,PagesWithoutKids)258 TEST_F(cpdf_document_test, PagesWithoutKids) {
259   // Set up a document with Pages dict without kids, and Count = 3
260   auto pDoc = pdfium::MakeUnique<CPDF_TestDocPagesWithoutKids>();
261   EXPECT_TRUE(pDoc->GetPage(0));
262   // Test GetPage does not fetch pages out of range
263   for (int i = 1; i < 5; i++)
264     EXPECT_FALSE(pDoc->GetPage(i));
265 
266   EXPECT_TRUE(pDoc->GetPage(0));
267 }
268