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 "public/fpdf_doc.h"
6 
7 #include <memory>
8 #include <vector>
9 
10 #include "core/fpdfapi/page/cpdf_docpagedata.h"
11 #include "core/fpdfapi/page/cpdf_pagemodule.h"
12 #include "core/fpdfapi/parser/cpdf_array.h"
13 #include "core/fpdfapi/parser/cpdf_dictionary.h"
14 #include "core/fpdfapi/parser/cpdf_document.h"
15 #include "core/fpdfapi/parser/cpdf_name.h"
16 #include "core/fpdfapi/parser/cpdf_null.h"
17 #include "core/fpdfapi/parser/cpdf_number.h"
18 #include "core/fpdfapi/parser/cpdf_parser.h"
19 #include "core/fpdfapi/parser/cpdf_reference.h"
20 #include "core/fpdfapi/parser/cpdf_string.h"
21 #include "core/fpdfapi/render/cpdf_docrenderdata.h"
22 #include "core/fpdfdoc/cpdf_dest.h"
23 #include "fpdfsdk/cpdfsdk_helpers.h"
24 #include "public/cpp/fpdf_scopers.h"
25 #include "testing/fx_string_testhelpers.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 #include "third_party/base/ptr_util.h"
28 
29 class CPDF_TestDocument final : public CPDF_Document {
30  public:
CPDF_TestDocument()31   CPDF_TestDocument()
32       : CPDF_Document(pdfium::MakeUnique<CPDF_DocRenderData>(),
33                       pdfium::MakeUnique<CPDF_DocPageData>()) {}
34 
SetRoot(CPDF_Dictionary * root)35   void SetRoot(CPDF_Dictionary* root) { m_pRootDict.Reset(root); }
GetHolder()36   CPDF_IndirectObjectHolder* GetHolder() { return this; }
37 };
38 
39 class PDFDocTest : public testing::Test {
40  public:
41   struct DictObjInfo {
42     uint32_t num;
43     CPDF_Dictionary* obj;
44   };
45 
SetUp()46   void SetUp() override {
47     CPDF_PageModule::Create();
48     auto pTestDoc = pdfium::MakeUnique<CPDF_TestDocument>();
49     m_pIndirectObjs = pTestDoc->GetHolder();
50     m_pRootObj.Reset(m_pIndirectObjs->NewIndirect<CPDF_Dictionary>());
51     pTestDoc->SetRoot(m_pRootObj.Get());
52     m_pDoc.reset(FPDFDocumentFromCPDFDocument(pTestDoc.release()));
53   }
54 
TearDown()55   void TearDown() override {
56     m_pRootObj = nullptr;
57     m_pIndirectObjs = nullptr;
58     m_pDoc.reset();
59     CPDF_PageModule::Destroy();
60   }
61 
CreateDictObjs(int num)62   std::vector<DictObjInfo> CreateDictObjs(int num) {
63     std::vector<DictObjInfo> info;
64     for (int i = 0; i < num; ++i) {
65       // Objects created will be released by the document.
66       CPDF_Dictionary* obj = m_pIndirectObjs->NewIndirect<CPDF_Dictionary>();
67       info.push_back({obj->GetObjNum(), obj});
68     }
69     return info;
70   }
71 
72  protected:
73   ScopedFPDFDocument m_pDoc;
74   UnownedPtr<CPDF_IndirectObjectHolder> m_pIndirectObjs;
75   RetainPtr<CPDF_Dictionary> m_pRootObj;
76 };
77 
TEST_F(PDFDocTest,FindBookmark)78 TEST_F(PDFDocTest, FindBookmark) {
79   {
80     // No bookmark information.
81     ScopedFPDFWideString title = GetFPDFWideString(L"");
82     EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
83 
84     title = GetFPDFWideString(L"Preface");
85     EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
86   }
87   {
88     // Empty bookmark tree.
89     m_pRootObj->SetNewFor<CPDF_Dictionary>("Outlines");
90     ScopedFPDFWideString title = GetFPDFWideString(L"");
91     EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
92 
93     title = GetFPDFWideString(L"Preface");
94     EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
95   }
96   {
97     // Check on a regular bookmark tree.
98     auto bookmarks = CreateDictObjs(3);
99 
100     bookmarks[1].obj->SetNewFor<CPDF_String>("Title", L"Chapter 1");
101     bookmarks[1].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
102                                                 bookmarks[0].num);
103     bookmarks[1].obj->SetNewFor<CPDF_Reference>("Next", m_pIndirectObjs.Get(),
104                                                 bookmarks[2].num);
105 
106     bookmarks[2].obj->SetNewFor<CPDF_String>("Title", L"Chapter 2");
107     bookmarks[2].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
108                                                 bookmarks[0].num);
109     bookmarks[2].obj->SetNewFor<CPDF_Reference>("Prev", m_pIndirectObjs.Get(),
110                                                 bookmarks[1].num);
111 
112     bookmarks[0].obj->SetNewFor<CPDF_Name>("Type", "Outlines");
113     bookmarks[0].obj->SetNewFor<CPDF_Number>("Count", 2);
114     bookmarks[0].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(),
115                                                 bookmarks[1].num);
116     bookmarks[0].obj->SetNewFor<CPDF_Reference>("Last", m_pIndirectObjs.Get(),
117                                                 bookmarks[2].num);
118 
119     m_pRootObj->SetNewFor<CPDF_Reference>("Outlines", m_pIndirectObjs.Get(),
120                                           bookmarks[0].num);
121 
122     // Title with no match.
123     ScopedFPDFWideString title = GetFPDFWideString(L"Chapter 3");
124     EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
125 
126     // Title with partial match only.
127     title = GetFPDFWideString(L"Chapter");
128     EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
129 
130     // Title with a match.
131     title = GetFPDFWideString(L"Chapter 2");
132     EXPECT_EQ(FPDFBookmarkFromCPDFDictionary(bookmarks[2].obj),
133               FPDFBookmark_Find(m_pDoc.get(), title.get()));
134 
135     // Title match is case insensitive.
136     title = GetFPDFWideString(L"cHaPter 2");
137     EXPECT_EQ(FPDFBookmarkFromCPDFDictionary(bookmarks[2].obj),
138               FPDFBookmark_Find(m_pDoc.get(), title.get()));
139   }
140   {
141     // Circular bookmarks in depth.
142     auto bookmarks = CreateDictObjs(3);
143 
144     bookmarks[1].obj->SetNewFor<CPDF_String>("Title", L"Chapter 1");
145     bookmarks[1].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
146                                                 bookmarks[0].num);
147     bookmarks[1].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(),
148                                                 bookmarks[2].num);
149 
150     bookmarks[2].obj->SetNewFor<CPDF_String>("Title", L"Chapter 2");
151     bookmarks[2].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
152                                                 bookmarks[1].num);
153     bookmarks[2].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(),
154                                                 bookmarks[1].num);
155 
156     bookmarks[0].obj->SetNewFor<CPDF_Name>("Type", "Outlines");
157     bookmarks[0].obj->SetNewFor<CPDF_Number>("Count", 2);
158     bookmarks[0].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(),
159                                                 bookmarks[1].num);
160     bookmarks[0].obj->SetNewFor<CPDF_Reference>("Last", m_pIndirectObjs.Get(),
161                                                 bookmarks[2].num);
162 
163     m_pRootObj->SetNewFor<CPDF_Reference>("Outlines", m_pIndirectObjs.Get(),
164                                           bookmarks[0].num);
165 
166     // Title with no match.
167     ScopedFPDFWideString title = GetFPDFWideString(L"Chapter 3");
168     EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
169 
170     // Title with a match.
171     title = GetFPDFWideString(L"Chapter 2");
172     EXPECT_EQ(FPDFBookmarkFromCPDFDictionary(bookmarks[2].obj),
173               FPDFBookmark_Find(m_pDoc.get(), title.get()));
174   }
175   {
176     // Circular bookmarks in breadth.
177     auto bookmarks = CreateDictObjs(4);
178 
179     bookmarks[1].obj->SetNewFor<CPDF_String>("Title", L"Chapter 1");
180     bookmarks[1].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
181                                                 bookmarks[0].num);
182     bookmarks[1].obj->SetNewFor<CPDF_Reference>("Next", m_pIndirectObjs.Get(),
183                                                 bookmarks[2].num);
184 
185     bookmarks[2].obj->SetNewFor<CPDF_String>("Title", L"Chapter 2");
186     bookmarks[2].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
187                                                 bookmarks[0].num);
188     bookmarks[2].obj->SetNewFor<CPDF_Reference>("Next", m_pIndirectObjs.Get(),
189                                                 bookmarks[3].num);
190 
191     bookmarks[3].obj->SetNewFor<CPDF_String>("Title", L"Chapter 3");
192     bookmarks[3].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
193                                                 bookmarks[0].num);
194     bookmarks[3].obj->SetNewFor<CPDF_Reference>("Next", m_pIndirectObjs.Get(),
195                                                 bookmarks[1].num);
196 
197     bookmarks[0].obj->SetNewFor<CPDF_Name>("Type", "Outlines");
198     bookmarks[0].obj->SetNewFor<CPDF_Number>("Count", 2);
199     bookmarks[0].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(),
200                                                 bookmarks[1].num);
201     bookmarks[0].obj->SetNewFor<CPDF_Reference>("Last", m_pIndirectObjs.Get(),
202                                                 bookmarks[2].num);
203 
204     m_pRootObj->SetNewFor<CPDF_Reference>("Outlines", m_pIndirectObjs.Get(),
205                                           bookmarks[0].num);
206 
207     // Title with no match.
208     ScopedFPDFWideString title = GetFPDFWideString(L"Chapter 8");
209     EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
210 
211     // Title with a match.
212     title = GetFPDFWideString(L"Chapter 3");
213     EXPECT_EQ(FPDFBookmarkFromCPDFDictionary(bookmarks[3].obj),
214               FPDFBookmark_Find(m_pDoc.get(), title.get()));
215   }
216 }
217 
TEST_F(PDFDocTest,GetLocationInPage)218 TEST_F(PDFDocTest, GetLocationInPage) {
219   auto array = pdfium::MakeRetain<CPDF_Array>();
220   array->AddNew<CPDF_Number>(0);  // Page Index.
221   array->AddNew<CPDF_Name>("XYZ");
222   array->AddNew<CPDF_Number>(4);  // X
223   array->AddNew<CPDF_Number>(5);  // Y
224   array->AddNew<CPDF_Number>(6);  // Zoom.
225 
226   FPDF_BOOL hasX;
227   FPDF_BOOL hasY;
228   FPDF_BOOL hasZoom;
229   FS_FLOAT x;
230   FS_FLOAT y;
231   FS_FLOAT zoom;
232 
233   EXPECT_TRUE(FPDFDest_GetLocationInPage(FPDFDestFromCPDFArray(array.Get()),
234                                          &hasX, &hasY, &hasZoom, &x, &y,
235                                          &zoom));
236   EXPECT_TRUE(hasX);
237   EXPECT_TRUE(hasY);
238   EXPECT_TRUE(hasZoom);
239   EXPECT_EQ(4, x);
240   EXPECT_EQ(5, y);
241   EXPECT_EQ(6, zoom);
242 
243   array->SetNewAt<CPDF_Null>(2);
244   array->SetNewAt<CPDF_Null>(3);
245   array->SetNewAt<CPDF_Null>(4);
246   EXPECT_TRUE(FPDFDest_GetLocationInPage(FPDFDestFromCPDFArray(array.Get()),
247                                          &hasX, &hasY, &hasZoom, &x, &y,
248                                          &zoom));
249   EXPECT_FALSE(hasX);
250   EXPECT_FALSE(hasY);
251   EXPECT_FALSE(hasZoom);
252 
253   array = pdfium::MakeRetain<CPDF_Array>();
254   EXPECT_FALSE(FPDFDest_GetLocationInPage(FPDFDestFromCPDFArray(array.Get()),
255                                           &hasX, &hasY, &hasZoom, &x, &y,
256                                           &zoom));
257 }
258