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// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7#include "core/fpdfdoc/cpdf_nametree.h"
8
9#include "core/fpdfapi/parser/cpdf_array.h"
10#include "core/fpdfapi/parser/cpdf_dictionary.h"
11#include "core/fpdfapi/parser/cpdf_document.h"
12
13namespace {
14
15const int nMaxRecursion = 32;
16
17CPDF_Object* SearchNameNode(CPDF_Dictionary* pNode,
18                            const CFX_ByteString& csName,
19                            size_t& nIndex,
20                            CPDF_Array** ppFind,
21                            int nLevel = 0) {
22  if (nLevel > nMaxRecursion)
23    return nullptr;
24
25  CPDF_Array* pLimits = pNode->GetArrayFor("Limits");
26  if (pLimits) {
27    CFX_ByteString csLeft = pLimits->GetStringAt(0);
28    CFX_ByteString csRight = pLimits->GetStringAt(1);
29    if (csLeft.Compare(csRight.AsStringC()) > 0) {
30      CFX_ByteString csTmp = csRight;
31      csRight = csLeft;
32      csLeft = csTmp;
33    }
34    if (csName.Compare(csLeft.AsStringC()) < 0 ||
35        csName.Compare(csRight.AsStringC()) > 0) {
36      return nullptr;
37    }
38  }
39
40  CPDF_Array* pNames = pNode->GetArrayFor("Names");
41  if (pNames) {
42    size_t dwCount = pNames->GetCount() / 2;
43    for (size_t i = 0; i < dwCount; i++) {
44      CFX_ByteString csValue = pNames->GetStringAt(i * 2);
45      int32_t iCompare = csValue.Compare(csName.AsStringC());
46      if (iCompare <= 0) {
47        if (ppFind)
48          *ppFind = pNames;
49        if (iCompare < 0)
50          continue;
51      } else {
52        break;
53      }
54      nIndex += i;
55      return pNames->GetDirectObjectAt(i * 2 + 1);
56    }
57    nIndex += dwCount;
58    return nullptr;
59  }
60
61  CPDF_Array* pKids = pNode->GetArrayFor("Kids");
62  if (!pKids)
63    return nullptr;
64
65  for (size_t i = 0; i < pKids->GetCount(); i++) {
66    CPDF_Dictionary* pKid = pKids->GetDictAt(i);
67    if (!pKid)
68      continue;
69
70    CPDF_Object* pFound =
71        SearchNameNode(pKid, csName, nIndex, ppFind, nLevel + 1);
72    if (pFound)
73      return pFound;
74  }
75  return nullptr;
76}
77
78CPDF_Object* SearchNameNode(CPDF_Dictionary* pNode,
79                            size_t nIndex,
80                            size_t& nCurIndex,
81                            CFX_ByteString& csName,
82                            CPDF_Array** ppFind,
83                            int nLevel = 0) {
84  if (nLevel > nMaxRecursion)
85    return nullptr;
86
87  CPDF_Array* pNames = pNode->GetArrayFor("Names");
88  if (pNames) {
89    size_t nCount = pNames->GetCount() / 2;
90    if (nIndex >= nCurIndex + nCount) {
91      nCurIndex += nCount;
92      return nullptr;
93    }
94    if (ppFind)
95      *ppFind = pNames;
96    csName = pNames->GetStringAt((nIndex - nCurIndex) * 2);
97    return pNames->GetDirectObjectAt((nIndex - nCurIndex) * 2 + 1);
98  }
99  CPDF_Array* pKids = pNode->GetArrayFor("Kids");
100  if (!pKids)
101    return nullptr;
102  for (size_t i = 0; i < pKids->GetCount(); i++) {
103    CPDF_Dictionary* pKid = pKids->GetDictAt(i);
104    if (!pKid)
105      continue;
106    CPDF_Object* pFound =
107        SearchNameNode(pKid, nIndex, nCurIndex, csName, ppFind, nLevel + 1);
108    if (pFound)
109      return pFound;
110  }
111  return nullptr;
112}
113
114size_t CountNames(CPDF_Dictionary* pNode, int nLevel = 0) {
115  if (nLevel > nMaxRecursion)
116    return 0;
117
118  CPDF_Array* pNames = pNode->GetArrayFor("Names");
119  if (pNames)
120    return pNames->GetCount() / 2;
121
122  CPDF_Array* pKids = pNode->GetArrayFor("Kids");
123  if (!pKids)
124    return 0;
125
126  size_t nCount = 0;
127  for (size_t i = 0; i < pKids->GetCount(); i++) {
128    CPDF_Dictionary* pKid = pKids->GetDictAt(i);
129    if (!pKid)
130      continue;
131
132    nCount += CountNames(pKid, nLevel + 1);
133  }
134  return nCount;
135}
136
137}  // namespace
138
139CPDF_NameTree::CPDF_NameTree(CPDF_Document* pDoc,
140                             const CFX_ByteString& category)
141    : m_pRoot(nullptr) {
142  CPDF_Dictionary* pRoot = pDoc->GetRoot();
143  if (!pRoot)
144    return;
145
146  CPDF_Dictionary* pNames = pRoot->GetDictFor("Names");
147  if (!pNames)
148    return;
149
150  m_pRoot = pNames->GetDictFor(category);
151}
152
153size_t CPDF_NameTree::GetCount() const {
154  return m_pRoot ? ::CountNames(m_pRoot) : 0;
155}
156
157int CPDF_NameTree::GetIndex(const CFX_ByteString& csName) const {
158  if (!m_pRoot)
159    return -1;
160
161  size_t nIndex = 0;
162  if (!SearchNameNode(m_pRoot, csName, nIndex, nullptr))
163    return -1;
164  return nIndex;
165}
166
167CPDF_Object* CPDF_NameTree::LookupValue(int nIndex,
168                                        CFX_ByteString& csName) const {
169  if (!m_pRoot)
170    return nullptr;
171  size_t nCurIndex = 0;
172  return SearchNameNode(m_pRoot, nIndex, nCurIndex, csName, nullptr);
173}
174
175CPDF_Object* CPDF_NameTree::LookupValue(const CFX_ByteString& csName) const {
176  if (!m_pRoot)
177    return nullptr;
178  size_t nIndex = 0;
179  return SearchNameNode(m_pRoot, csName, nIndex, nullptr);
180}
181
182CPDF_Array* CPDF_NameTree::LookupNamedDest(CPDF_Document* pDoc,
183                                           const CFX_ByteString& sName) {
184  CPDF_Object* pValue = LookupValue(sName);
185  if (!pValue) {
186    CPDF_Dictionary* pDests = pDoc->GetRoot()->GetDictFor("Dests");
187    if (!pDests)
188      return nullptr;
189    pValue = pDests->GetDirectObjectFor(sName);
190  }
191  if (!pValue)
192    return nullptr;
193  if (CPDF_Array* pArray = pValue->AsArray())
194    return pArray;
195  if (CPDF_Dictionary* pDict = pValue->AsDictionary())
196    return pDict->GetArrayFor("D");
197  return nullptr;
198}
199