1 // Copyright 2014 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 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 
7 #include "public/fpdf_doc.h"
8 
9 #include <memory>
10 #include <set>
11 #include <utility>
12 
13 #include "core/fpdfapi/page/cpdf_page.h"
14 #include "core/fpdfapi/parser/cpdf_array.h"
15 #include "core/fpdfapi/parser/cpdf_dictionary.h"
16 #include "core/fpdfapi/parser/cpdf_document.h"
17 #include "core/fpdfapi/parser/cpdf_number.h"
18 #include "core/fpdfdoc/cpdf_bookmark.h"
19 #include "core/fpdfdoc/cpdf_bookmarktree.h"
20 #include "core/fpdfdoc/cpdf_dest.h"
21 #include "core/fpdfdoc/cpdf_linklist.h"
22 #include "core/fpdfdoc/cpdf_pagelabel.h"
23 #include "fpdfsdk/cpdfsdk_helpers.h"
24 #include "third_party/base/ptr_util.h"
25 #include "third_party/base/stl_util.h"
26 
27 namespace {
28 
FindBookmark(const CPDF_BookmarkTree & tree,CPDF_Bookmark bookmark,const WideString & title,std::set<const CPDF_Dictionary * > * visited)29 CPDF_Bookmark FindBookmark(const CPDF_BookmarkTree& tree,
30                            CPDF_Bookmark bookmark,
31                            const WideString& title,
32                            std::set<const CPDF_Dictionary*>* visited) {
33   // Return if already checked to avoid circular calling.
34   if (pdfium::ContainsKey(*visited, bookmark.GetDict()))
35     return CPDF_Bookmark();
36   visited->insert(bookmark.GetDict());
37 
38   if (bookmark.GetDict() &&
39       bookmark.GetTitle().CompareNoCase(title.c_str()) == 0) {
40     // First check this item.
41     return bookmark;
42   }
43 
44   // Go into children items.
45   CPDF_Bookmark child = tree.GetFirstChild(&bookmark);
46   while (child.GetDict() && !pdfium::ContainsKey(*visited, child.GetDict())) {
47     // Check this item and its children.
48     CPDF_Bookmark found = FindBookmark(tree, child, title, visited);
49     if (found.GetDict())
50       return found;
51     child = tree.GetNextSibling(&child);
52   }
53   return CPDF_Bookmark();
54 }
55 
GetLinkList(CPDF_Page * page)56 CPDF_LinkList* GetLinkList(CPDF_Page* page) {
57   CPDF_Document* pDoc = page->GetDocument();
58   auto* pList = static_cast<CPDF_LinkList*>(pDoc->GetLinksContext());
59   if (pList)
60     return pList;
61 
62   auto pNewList = pdfium::MakeUnique<CPDF_LinkList>();
63   pList = pNewList.get();
64   pDoc->SetLinksContext(std::move(pNewList));
65   return pList;
66 }
67 
68 }  // namespace
69 
70 FPDF_EXPORT FPDF_BOOKMARK FPDF_CALLCONV
FPDFBookmark_GetFirstChild(FPDF_DOCUMENT document,FPDF_BOOKMARK bookmark)71 FPDFBookmark_GetFirstChild(FPDF_DOCUMENT document, FPDF_BOOKMARK bookmark) {
72   CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
73   if (!pDoc)
74     return nullptr;
75   CPDF_BookmarkTree tree(pDoc);
76   CPDF_Bookmark cBookmark(CPDFDictionaryFromFPDFBookmark(bookmark));
77   return FPDFBookmarkFromCPDFDictionary(
78       tree.GetFirstChild(&cBookmark).GetDict());
79 }
80 
81 FPDF_EXPORT FPDF_BOOKMARK FPDF_CALLCONV
FPDFBookmark_GetNextSibling(FPDF_DOCUMENT document,FPDF_BOOKMARK bookmark)82 FPDFBookmark_GetNextSibling(FPDF_DOCUMENT document, FPDF_BOOKMARK bookmark) {
83   CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
84   if (!pDoc)
85     return nullptr;
86 
87   if (!bookmark)
88     return nullptr;
89 
90   CPDF_BookmarkTree tree(pDoc);
91   CPDF_Bookmark cBookmark(CPDFDictionaryFromFPDFBookmark(bookmark));
92   return FPDFBookmarkFromCPDFDictionary(
93       tree.GetNextSibling(&cBookmark).GetDict());
94 }
95 
96 FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFBookmark_GetTitle(FPDF_BOOKMARK bookmark,void * buffer,unsigned long buflen)97 FPDFBookmark_GetTitle(FPDF_BOOKMARK bookmark,
98                       void* buffer,
99                       unsigned long buflen) {
100   if (!bookmark)
101     return 0;
102   CPDF_Bookmark cBookmark(CPDFDictionaryFromFPDFBookmark(bookmark));
103   WideString title = cBookmark.GetTitle();
104   return Utf16EncodeMaybeCopyAndReturnLength(title, buffer, buflen);
105 }
106 
107 FPDF_EXPORT FPDF_BOOKMARK FPDF_CALLCONV
FPDFBookmark_Find(FPDF_DOCUMENT document,FPDF_WIDESTRING title)108 FPDFBookmark_Find(FPDF_DOCUMENT document, FPDF_WIDESTRING title) {
109   CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
110   if (!pDoc)
111     return nullptr;
112 
113   WideString encodedTitle = WideStringFromFPDFWideString(title);
114   if (encodedTitle.IsEmpty())
115     return nullptr;
116 
117   CPDF_BookmarkTree tree(pDoc);
118   std::set<const CPDF_Dictionary*> visited;
119   return FPDFBookmarkFromCPDFDictionary(
120       FindBookmark(tree, CPDF_Bookmark(), encodedTitle, &visited).GetDict());
121 }
122 
123 FPDF_EXPORT FPDF_DEST FPDF_CALLCONV
FPDFBookmark_GetDest(FPDF_DOCUMENT document,FPDF_BOOKMARK bookmark)124 FPDFBookmark_GetDest(FPDF_DOCUMENT document, FPDF_BOOKMARK bookmark) {
125   CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
126   if (!pDoc)
127     return nullptr;
128 
129   if (!bookmark)
130     return nullptr;
131 
132   CPDF_Bookmark cBookmark(CPDFDictionaryFromFPDFBookmark(bookmark));
133   CPDF_Dest dest = cBookmark.GetDest(pDoc);
134   if (dest.GetArray())
135     return FPDFDestFromCPDFArray(dest.GetArray());
136   // If this bookmark is not directly associated with a dest, we try to get
137   // action
138   CPDF_Action action = cBookmark.GetAction();
139   if (!action.GetDict())
140     return nullptr;
141   return FPDFDestFromCPDFArray(action.GetDest(pDoc).GetArray());
142 }
143 
144 FPDF_EXPORT FPDF_ACTION FPDF_CALLCONV
FPDFBookmark_GetAction(FPDF_BOOKMARK bookmark)145 FPDFBookmark_GetAction(FPDF_BOOKMARK bookmark) {
146   if (!bookmark)
147     return nullptr;
148 
149   CPDF_Bookmark cBookmark(CPDFDictionaryFromFPDFBookmark(bookmark));
150   return FPDFActionFromCPDFDictionary(cBookmark.GetAction().GetDict());
151 }
152 
FPDFAction_GetType(FPDF_ACTION action)153 FPDF_EXPORT unsigned long FPDF_CALLCONV FPDFAction_GetType(FPDF_ACTION action) {
154   if (!action)
155     return PDFACTION_UNSUPPORTED;
156 
157   CPDF_Action cAction(CPDFDictionaryFromFPDFAction(action));
158   CPDF_Action::ActionType type = cAction.GetType();
159   switch (type) {
160     case CPDF_Action::GoTo:
161       return PDFACTION_GOTO;
162     case CPDF_Action::GoToR:
163       return PDFACTION_REMOTEGOTO;
164     case CPDF_Action::URI:
165       return PDFACTION_URI;
166     case CPDF_Action::Launch:
167       return PDFACTION_LAUNCH;
168     default:
169       return PDFACTION_UNSUPPORTED;
170   }
171 }
172 
FPDFAction_GetDest(FPDF_DOCUMENT document,FPDF_ACTION action)173 FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDFAction_GetDest(FPDF_DOCUMENT document,
174                                                        FPDF_ACTION action) {
175   CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
176   if (!pDoc)
177     return nullptr;
178 
179   unsigned long type = FPDFAction_GetType(action);
180   if (type != PDFACTION_GOTO && type != PDFACTION_REMOTEGOTO)
181     return nullptr;
182 
183   CPDF_Action cAction(CPDFDictionaryFromFPDFAction(action));
184   return FPDFDestFromCPDFArray(cAction.GetDest(pDoc).GetArray());
185 }
186 
187 FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFAction_GetFilePath(FPDF_ACTION action,void * buffer,unsigned long buflen)188 FPDFAction_GetFilePath(FPDF_ACTION action, void* buffer, unsigned long buflen) {
189   unsigned long type = FPDFAction_GetType(action);
190   if (type != PDFACTION_REMOTEGOTO && type != PDFACTION_LAUNCH)
191     return 0;
192 
193   CPDF_Action cAction(CPDFDictionaryFromFPDFAction(action));
194   ByteString path = cAction.GetFilePath().ToUTF8();
195   unsigned long len = path.GetLength() + 1;
196   if (buffer && len <= buflen)
197     memcpy(buffer, path.c_str(), len);
198   return len;
199 }
200 
201 FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFAction_GetURIPath(FPDF_DOCUMENT document,FPDF_ACTION action,void * buffer,unsigned long buflen)202 FPDFAction_GetURIPath(FPDF_DOCUMENT document,
203                       FPDF_ACTION action,
204                       void* buffer,
205                       unsigned long buflen) {
206   CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
207   if (!pDoc)
208     return 0;
209 
210   unsigned long type = FPDFAction_GetType(action);
211   if (type != PDFACTION_URI)
212     return 0;
213 
214   CPDF_Action cAction(CPDFDictionaryFromFPDFAction(action));
215   ByteString path = cAction.GetURI(pDoc);
216   unsigned long len = path.GetLength() + 1;
217   if (buffer && len <= buflen)
218     memcpy(buffer, path.c_str(), len);
219   return len;
220 }
221 
FPDFDest_GetDestPageIndex(FPDF_DOCUMENT document,FPDF_DEST dest)222 FPDF_EXPORT int FPDF_CALLCONV FPDFDest_GetDestPageIndex(FPDF_DOCUMENT document,
223                                                         FPDF_DEST dest) {
224   CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
225   if (!pDoc)
226     return -1;
227 
228   if (!dest)
229     return -1;
230 
231   CPDF_Dest destination(CPDFArrayFromFPDFDest(dest));
232   return destination.GetDestPageIndex(pDoc);
233 }
234 
235 FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFDest_GetView(FPDF_DEST dest,unsigned long * pNumParams,FS_FLOAT * pParams)236 FPDFDest_GetView(FPDF_DEST dest, unsigned long* pNumParams, FS_FLOAT* pParams) {
237   if (!dest) {
238     *pNumParams = 0;
239     return 0;
240   }
241 
242   CPDF_Dest destination(CPDFArrayFromFPDFDest(dest));
243   unsigned long nParams = destination.GetNumParams();
244   ASSERT(nParams <= 4);
245   *pNumParams = nParams;
246   for (unsigned long i = 0; i < nParams; ++i)
247     pParams[i] = destination.GetParam(i);
248   return destination.GetZoomMode();
249 }
250 
251 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFDest_GetLocationInPage(FPDF_DEST dest,FPDF_BOOL * hasXVal,FPDF_BOOL * hasYVal,FPDF_BOOL * hasZoomVal,FS_FLOAT * x,FS_FLOAT * y,FS_FLOAT * zoom)252 FPDFDest_GetLocationInPage(FPDF_DEST dest,
253                            FPDF_BOOL* hasXVal,
254                            FPDF_BOOL* hasYVal,
255                            FPDF_BOOL* hasZoomVal,
256                            FS_FLOAT* x,
257                            FS_FLOAT* y,
258                            FS_FLOAT* zoom) {
259   if (!dest)
260     return false;
261 
262   auto destination = pdfium::MakeUnique<CPDF_Dest>(CPDFArrayFromFPDFDest(dest));
263 
264   // FPDF_BOOL is an int, GetXYZ expects bools.
265   bool bHasX;
266   bool bHasY;
267   bool bHasZoom;
268   if (!destination->GetXYZ(&bHasX, &bHasY, &bHasZoom, x, y, zoom))
269     return false;
270 
271   *hasXVal = bHasX;
272   *hasYVal = bHasY;
273   *hasZoomVal = bHasZoom;
274   return true;
275 }
276 
FPDFLink_GetLinkAtPoint(FPDF_PAGE page,double x,double y)277 FPDF_EXPORT FPDF_LINK FPDF_CALLCONV FPDFLink_GetLinkAtPoint(FPDF_PAGE page,
278                                                             double x,
279                                                             double y) {
280   CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
281   if (!pPage)
282     return nullptr;
283 
284   CPDF_LinkList* pLinkList = GetLinkList(pPage);
285   if (!pLinkList)
286     return nullptr;
287 
288   CPDF_Link link = pLinkList->GetLinkAtPoint(
289       pPage, CFX_PointF(static_cast<float>(x), static_cast<float>(y)), nullptr);
290 
291   return FPDFLinkFromCPDFDictionary(link.GetDict());
292 }
293 
FPDFLink_GetLinkZOrderAtPoint(FPDF_PAGE page,double x,double y)294 FPDF_EXPORT int FPDF_CALLCONV FPDFLink_GetLinkZOrderAtPoint(FPDF_PAGE page,
295                                                             double x,
296                                                             double y) {
297   CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
298   if (!pPage)
299     return -1;
300 
301   CPDF_LinkList* pLinkList = GetLinkList(pPage);
302   if (!pLinkList)
303     return -1;
304 
305   int z_order = -1;
306   pLinkList->GetLinkAtPoint(
307       pPage, CFX_PointF(static_cast<float>(x), static_cast<float>(y)),
308       &z_order);
309   return z_order;
310 }
311 
FPDFLink_GetDest(FPDF_DOCUMENT document,FPDF_LINK link)312 FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDFLink_GetDest(FPDF_DOCUMENT document,
313                                                      FPDF_LINK link) {
314   if (!link)
315     return nullptr;
316   CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
317   if (!pDoc)
318     return nullptr;
319   CPDF_Link cLink(CPDFDictionaryFromFPDFLink(link));
320   FPDF_DEST dest = FPDFDestFromCPDFArray(cLink.GetDest(pDoc).GetArray());
321   if (dest)
322     return dest;
323   // If this link is not directly associated with a dest, we try to get action
324   CPDF_Action action = cLink.GetAction();
325   if (!action.GetDict())
326     return nullptr;
327   return FPDFDestFromCPDFArray(action.GetDest(pDoc).GetArray());
328 }
329 
FPDFLink_GetAction(FPDF_LINK link)330 FPDF_EXPORT FPDF_ACTION FPDF_CALLCONV FPDFLink_GetAction(FPDF_LINK link) {
331   if (!link)
332     return nullptr;
333 
334   CPDF_Link cLink(CPDFDictionaryFromFPDFLink(link));
335   return FPDFActionFromCPDFDictionary(cLink.GetAction().GetDict());
336 }
337 
FPDFLink_Enumerate(FPDF_PAGE page,int * start_pos,FPDF_LINK * link_annot)338 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFLink_Enumerate(FPDF_PAGE page,
339                                                        int* start_pos,
340                                                        FPDF_LINK* link_annot) {
341   if (!start_pos || !link_annot)
342     return false;
343   CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
344   if (!pPage)
345     return false;
346   CPDF_Array* pAnnots = pPage->GetDict()->GetArrayFor("Annots");
347   if (!pAnnots)
348     return false;
349   for (size_t i = *start_pos; i < pAnnots->size(); i++) {
350     CPDF_Dictionary* pDict = ToDictionary(pAnnots->GetDirectObjectAt(i));
351     if (!pDict)
352       continue;
353     if (pDict->GetStringFor("Subtype") == "Link") {
354       *start_pos = static_cast<int>(i + 1);
355       *link_annot = FPDFLinkFromCPDFDictionary(pDict);
356       return true;
357     }
358   }
359   return false;
360 }
361 
FPDFLink_GetAnnotRect(FPDF_LINK link_annot,FS_RECTF * rect)362 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFLink_GetAnnotRect(FPDF_LINK link_annot,
363                                                           FS_RECTF* rect) {
364   if (!link_annot || !rect)
365     return false;
366 
367   CPDF_Dictionary* pAnnotDict = CPDFDictionaryFromFPDFLink(link_annot);
368   *rect = FSRectFFromCFXFloatRect(pAnnotDict->GetRectFor("Rect"));
369   return true;
370 }
371 
FPDFLink_CountQuadPoints(FPDF_LINK link_annot)372 FPDF_EXPORT int FPDF_CALLCONV FPDFLink_CountQuadPoints(FPDF_LINK link_annot) {
373   const CPDF_Array* pArray =
374       GetQuadPointsArrayFromDictionary(CPDFDictionaryFromFPDFLink(link_annot));
375   return pArray ? static_cast<int>(pArray->size() / 8) : 0;
376 }
377 
378 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFLink_GetQuadPoints(FPDF_LINK link_annot,int quad_index,FS_QUADPOINTSF * quad_points)379 FPDFLink_GetQuadPoints(FPDF_LINK link_annot,
380                        int quad_index,
381                        FS_QUADPOINTSF* quad_points) {
382   if (!quad_points || quad_index < 0)
383     return false;
384 
385   const CPDF_Dictionary* pLinkDict = CPDFDictionaryFromFPDFLink(link_annot);
386   if (!pLinkDict)
387     return false;
388 
389   const CPDF_Array* pArray = GetQuadPointsArrayFromDictionary(pLinkDict);
390   if (!pArray)
391     return false;
392 
393   return GetQuadPointsAtIndex(pArray, static_cast<size_t>(quad_index),
394                               quad_points);
395 }
396 
FPDF_GetMetaText(FPDF_DOCUMENT document,FPDF_BYTESTRING tag,void * buffer,unsigned long buflen)397 FPDF_EXPORT unsigned long FPDF_CALLCONV FPDF_GetMetaText(FPDF_DOCUMENT document,
398                                                          FPDF_BYTESTRING tag,
399                                                          void* buffer,
400                                                          unsigned long buflen) {
401   if (!tag)
402     return 0;
403   CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
404   if (!pDoc)
405     return 0;
406 
407   const CPDF_Dictionary* pInfo = pDoc->GetInfo();
408   if (!pInfo)
409     return 0;
410   WideString text = pInfo->GetUnicodeTextFor(tag);
411   return Utf16EncodeMaybeCopyAndReturnLength(text, buffer, buflen);
412 }
413 
414 FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDF_GetPageLabel(FPDF_DOCUMENT document,int page_index,void * buffer,unsigned long buflen)415 FPDF_GetPageLabel(FPDF_DOCUMENT document,
416                   int page_index,
417                   void* buffer,
418                   unsigned long buflen) {
419   if (page_index < 0)
420     return 0;
421 
422   // CPDF_PageLabel can deal with NULL |document|.
423   CPDF_PageLabel label(CPDFDocumentFromFPDFDocument(document));
424   Optional<WideString> str = label.GetLabel(page_index);
425   return str.has_value()
426              ? Utf16EncodeMaybeCopyAndReturnLength(str.value(), buffer, buflen)
427              : 0;
428 }
429