• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 The PDFium Authors
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 <set>
10 #include <utility>
11 #include <vector>
12 
13 #include "core/fpdfapi/parser/cpdf_array.h"
14 #include "core/fpdfapi/parser/cpdf_dictionary.h"
15 #include "core/fpdfapi/parser/cpdf_document.h"
16 #include "core/fpdfapi/parser/cpdf_reference.h"
17 #include "core/fpdfapi/parser/cpdf_string.h"
18 #include "core/fpdfapi/parser/fpdf_parser_decode.h"
19 #include "core/fxcrt/check.h"
20 #include "core/fxcrt/ptr_util.h"
21 #include "core/fxcrt/stl_util.h"
22 
23 namespace {
24 
25 constexpr int kNameTreeMaxRecursion = 32;
26 
GetNodeLimitsAndSanitize(CPDF_Array * pLimits)27 std::pair<WideString, WideString> GetNodeLimitsAndSanitize(
28     CPDF_Array* pLimits) {
29   DCHECK(pLimits);
30   WideString csLeft = pLimits->GetUnicodeTextAt(0);
31   WideString csRight = pLimits->GetUnicodeTextAt(1);
32   // If the lower limit is greater than the upper limit, swap them.
33   if (csLeft.Compare(csRight) > 0) {
34     pLimits->SetNewAt<CPDF_String>(0, csRight.AsStringView());
35     pLimits->SetNewAt<CPDF_String>(1, csLeft.AsStringView());
36     csLeft = pLimits->GetUnicodeTextAt(0);
37     csRight = pLimits->GetUnicodeTextAt(1);
38   }
39   while (pLimits->size() > 2)
40     pLimits->RemoveAt(pLimits->size() - 1);
41   return {csLeft, csRight};
42 }
43 
44 // Get the limit arrays that leaf array |pFind| is under in the tree with root
45 // |pNode|. |pLimits| will hold all the limit arrays from the leaf up to before
46 // the root. Return true if successful.
GetNodeAncestorsLimitsInternal(const RetainPtr<CPDF_Dictionary> & pNode,const CPDF_Array * pFind,int nLevel,std::vector<CPDF_Array * > * pLimits)47 bool GetNodeAncestorsLimitsInternal(const RetainPtr<CPDF_Dictionary>& pNode,
48                                     const CPDF_Array* pFind,
49                                     int nLevel,
50                                     std::vector<CPDF_Array*>* pLimits) {
51   if (nLevel > kNameTreeMaxRecursion)
52     return false;
53 
54   if (pNode->GetArrayFor("Names") == pFind) {
55     pLimits->push_back(pNode->GetMutableArrayFor("Limits").Get());
56     return true;
57   }
58 
59   RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
60   if (!pKids)
61     return false;
62 
63   for (size_t i = 0; i < pKids->size(); ++i) {
64     RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
65     if (!pKid)
66       continue;
67 
68     if (GetNodeAncestorsLimitsInternal(pKid, pFind, nLevel + 1, pLimits)) {
69       pLimits->push_back(pNode->GetMutableArrayFor("Limits").Get());
70       return true;
71     }
72   }
73   return false;
74 }
75 
76 // Wrapper for GetNodeAncestorsLimitsInternal() so callers do not need to know
77 // about the details.
GetNodeAncestorsLimits(const RetainPtr<CPDF_Dictionary> & pNode,const CPDF_Array * pFind)78 std::vector<CPDF_Array*> GetNodeAncestorsLimits(
79     const RetainPtr<CPDF_Dictionary>& pNode,
80     const CPDF_Array* pFind) {
81   std::vector<CPDF_Array*> results;
82   GetNodeAncestorsLimitsInternal(pNode, pFind, 0, &results);
83   return results;
84 }
85 
86 // Upon the deletion of |csName| from leaf array |pFind|, update the ancestors
87 // of |pFind|. Specifically, the limits of |pFind|'s ancestors will be updated
88 // if needed, and any ancestors that are now empty will be removed.
UpdateNodesAndLimitsUponDeletion(CPDF_Dictionary * pNode,const CPDF_Array * pFind,const WideString & csName,int nLevel)89 bool UpdateNodesAndLimitsUponDeletion(CPDF_Dictionary* pNode,
90                                       const CPDF_Array* pFind,
91                                       const WideString& csName,
92                                       int nLevel) {
93   if (nLevel > kNameTreeMaxRecursion)
94     return false;
95 
96   RetainPtr<CPDF_Array> pLimits = pNode->GetMutableArrayFor("Limits");
97   WideString csLeft;
98   WideString csRight;
99   if (pLimits)
100     std::tie(csLeft, csRight) = GetNodeLimitsAndSanitize(pLimits.Get());
101 
102   RetainPtr<const CPDF_Array> pNames = pNode->GetArrayFor("Names");
103   if (pNames) {
104     if (pNames != pFind)
105       return false;
106     if (pNames->IsEmpty() || !pLimits)
107       return true;
108     if (csLeft != csName && csRight != csName)
109       return true;
110 
111     // Since |csName| defines |pNode|'s limits, we need to loop through the
112     // names to find the new lower and upper limits.
113     WideString csNewLeft = csRight;
114     WideString csNewRight = csLeft;
115     for (size_t i = 0; i < pNames->size() / 2; ++i) {
116       WideString wsName = pNames->GetUnicodeTextAt(i * 2);
117       if (wsName.Compare(csNewLeft) < 0)
118         csNewLeft = wsName;
119       if (wsName.Compare(csNewRight) > 0)
120         csNewRight = wsName;
121     }
122     pLimits->SetNewAt<CPDF_String>(0, csNewLeft.AsStringView());
123     pLimits->SetNewAt<CPDF_String>(1, csNewRight.AsStringView());
124     return true;
125   }
126 
127   RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
128   if (!pKids)
129     return false;
130 
131   // Loop through the kids to find the leaf array |pFind|.
132   for (size_t i = 0; i < pKids->size(); ++i) {
133     RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
134     if (!pKid)
135       continue;
136     if (!UpdateNodesAndLimitsUponDeletion(pKid.Get(), pFind, csName,
137                                           nLevel + 1)) {
138       continue;
139     }
140     // Remove this child node if it's empty.
141     if ((pKid->KeyExist("Names") && pKid->GetArrayFor("Names")->IsEmpty()) ||
142         (pKid->KeyExist("Kids") && pKid->GetArrayFor("Kids")->IsEmpty())) {
143       pKids->RemoveAt(i);
144     }
145     if (pKids->IsEmpty() || !pLimits)
146       return true;
147     if (csLeft != csName && csRight != csName)
148       return true;
149 
150     // Since |csName| defines |pNode|'s limits, we need to loop through the
151     // kids to find the new lower and upper limits.
152     WideString csNewLeft = csRight;
153     WideString csNewRight = csLeft;
154     for (size_t j = 0; j < pKids->size(); ++j) {
155       RetainPtr<const CPDF_Array> pKidLimits =
156           pKids->GetDictAt(j)->GetArrayFor("Limits");
157       DCHECK(pKidLimits);
158       if (pKidLimits->GetUnicodeTextAt(0).Compare(csNewLeft) < 0)
159         csNewLeft = pKidLimits->GetUnicodeTextAt(0);
160       if (pKidLimits->GetUnicodeTextAt(1).Compare(csNewRight) > 0)
161         csNewRight = pKidLimits->GetUnicodeTextAt(1);
162     }
163     pLimits->SetNewAt<CPDF_String>(0, csNewLeft.AsStringView());
164     pLimits->SetNewAt<CPDF_String>(1, csNewRight.AsStringView());
165     return true;
166   }
167   return false;
168 }
169 
IsTraversedObject(const CPDF_Object * obj,std::set<uint32_t> * seen_obj_nums)170 bool IsTraversedObject(const CPDF_Object* obj,
171                        std::set<uint32_t>* seen_obj_nums) {
172   uint32_t obj_num = obj->GetObjNum();
173   if (!obj_num)
174     return false;
175 
176   bool inserted = seen_obj_nums->insert(obj_num).second;
177   return !inserted;
178 }
179 
IsArrayWithTraversedObject(const CPDF_Array * array,std::set<uint32_t> * seen_obj_nums)180 bool IsArrayWithTraversedObject(const CPDF_Array* array,
181                                 std::set<uint32_t>* seen_obj_nums) {
182   if (IsTraversedObject(array, seen_obj_nums))
183     return true;
184 
185   CPDF_ArrayLocker locker(array);
186   for (const auto& item : locker) {
187     if (IsTraversedObject(item.Get(), seen_obj_nums))
188       return true;
189   }
190   return false;
191 }
192 
193 // Search for |csName| in the tree with root |pNode|. If successful, return the
194 // value that |csName| points to; |nIndex| will be the index of |csName|,
195 // |ppFind| will be the leaf array that |csName| is found in, and |pFindIndex|
196 // will be the index of |csName| in |ppFind|. If |csName| is not found, |ppFind|
197 // will be the leaf array that |csName| should be added to, and |pFindIndex|
198 // will be the index that it should be added at.
SearchNameNodeByNameInternal(const RetainPtr<CPDF_Dictionary> & pNode,const WideString & csName,int nLevel,size_t * nIndex,RetainPtr<CPDF_Array> * ppFind,int * pFindIndex,std::set<uint32_t> * seen_obj_nums)199 RetainPtr<const CPDF_Object> SearchNameNodeByNameInternal(
200     const RetainPtr<CPDF_Dictionary>& pNode,
201     const WideString& csName,
202     int nLevel,
203     size_t* nIndex,
204     RetainPtr<CPDF_Array>* ppFind,
205     int* pFindIndex,
206     std::set<uint32_t>* seen_obj_nums) {
207   if (nLevel > kNameTreeMaxRecursion)
208     return nullptr;
209 
210   RetainPtr<CPDF_Array> pLimits = pNode->GetMutableArrayFor("Limits");
211   RetainPtr<CPDF_Array> pNames = pNode->GetMutableArrayFor("Names");
212   if (pNames && IsArrayWithTraversedObject(pNames.Get(), seen_obj_nums))
213     pNames.Reset();
214   if (pLimits && IsArrayWithTraversedObject(pLimits.Get(), seen_obj_nums))
215     pLimits.Reset();
216 
217   if (pLimits) {
218     auto [csLeft, csRight] = GetNodeLimitsAndSanitize(pLimits.Get());
219     // Skip this node if the name to look for is smaller than its lower limit.
220     if (csName.Compare(csLeft) < 0)
221       return nullptr;
222 
223     // Skip this node if the name to look for is greater than its higher limit,
224     // and the node itself is a leaf node.
225     if (csName.Compare(csRight) > 0 && pNames) {
226       if (ppFind)
227         *ppFind = pNames;
228       if (pFindIndex)
229         *pFindIndex = fxcrt::CollectionSize<int32_t>(*pNames) / 2 - 1;
230       return nullptr;
231     }
232   }
233 
234   // If the node is a leaf node, look for the name in its names array.
235   if (pNames) {
236     size_t dwCount = pNames->size() / 2;
237     for (size_t i = 0; i < dwCount; i++) {
238       WideString csValue = pNames->GetUnicodeTextAt(i * 2);
239       int32_t iCompare = csValue.Compare(csName);
240       if (iCompare > 0)
241         break;
242       if (ppFind)
243         *ppFind = pNames;
244       if (pFindIndex)
245         *pFindIndex = pdfium::checked_cast<int32_t>(i);
246       if (iCompare < 0)
247         continue;
248 
249       *nIndex += i;
250       return pNames->GetDirectObjectAt(i * 2 + 1);
251     }
252     *nIndex += dwCount;
253     return nullptr;
254   }
255 
256   // Search through the node's children.
257   RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
258   if (!pKids || IsTraversedObject(pKids.Get(), seen_obj_nums))
259     return nullptr;
260 
261   for (size_t i = 0; i < pKids->size(); i++) {
262     RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
263     if (!pKid || IsTraversedObject(pKid.Get(), seen_obj_nums))
264       continue;
265 
266     RetainPtr<const CPDF_Object> pFound = SearchNameNodeByNameInternal(
267         pKid, csName, nLevel + 1, nIndex, ppFind, pFindIndex, seen_obj_nums);
268     if (pFound)
269       return pFound;
270   }
271   return nullptr;
272 }
273 
274 // Wrapper for SearchNameNodeByNameInternal() so callers do not need to know
275 // about the details.
SearchNameNodeByName(const RetainPtr<CPDF_Dictionary> & pNode,const WideString & csName,RetainPtr<CPDF_Array> * ppFind,int * pFindIndex)276 RetainPtr<const CPDF_Object> SearchNameNodeByName(
277     const RetainPtr<CPDF_Dictionary>& pNode,
278     const WideString& csName,
279     RetainPtr<CPDF_Array>* ppFind,
280     int* pFindIndex) {
281   size_t nIndex = 0;
282   std::set<uint32_t> seen_obj_nums;
283   return SearchNameNodeByNameInternal(pNode, csName, 0, &nIndex, ppFind,
284                                       pFindIndex, &seen_obj_nums);
285 }
286 
287 struct IndexSearchResult {
288   // For the n-th object in a tree, the key and value.
289   WideString key;
290   RetainPtr<CPDF_Object> value;
291   // The leaf node that holds `key` and `value`.
292   RetainPtr<CPDF_Array> container;
293   // The index for `key` in `container`. Must be even.
294   size_t index;
295 };
296 
297 // Find the `nTargetPairIndex` node in the tree with root `pNode`. `nLevel`
298 // tracks the recursion level and `nCurPairIndex` tracks the progress towards
299 // `nTargetPairIndex`.
SearchNameNodeByIndexInternal(CPDF_Dictionary * pNode,size_t nTargetPairIndex,int nLevel,size_t * nCurPairIndex)300 std::optional<IndexSearchResult> SearchNameNodeByIndexInternal(
301     CPDF_Dictionary* pNode,
302     size_t nTargetPairIndex,
303     int nLevel,
304     size_t* nCurPairIndex) {
305   if (nLevel > kNameTreeMaxRecursion)
306     return std::nullopt;
307 
308   RetainPtr<CPDF_Array> pNames = pNode->GetMutableArrayFor("Names");
309   if (pNames) {
310     size_t nCount = pNames->size() / 2;
311     if (nTargetPairIndex >= *nCurPairIndex + nCount) {
312       *nCurPairIndex += nCount;
313       return std::nullopt;
314     }
315 
316     size_t index = 2 * (nTargetPairIndex - *nCurPairIndex);
317     RetainPtr<CPDF_Object> value = pNames->GetMutableDirectObjectAt(index + 1);
318     if (!value)
319       return std::nullopt;
320 
321     IndexSearchResult result;
322     result.key = pNames->GetUnicodeTextAt(index);
323     result.value = std::move(value);
324     result.container = std::move(pNames);
325     result.index = index;
326     return result;
327   }
328 
329   RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
330   if (!pKids)
331     return std::nullopt;
332 
333   for (size_t i = 0; i < pKids->size(); i++) {
334     RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
335     if (!pKid)
336       continue;
337     std::optional<IndexSearchResult> result = SearchNameNodeByIndexInternal(
338         pKid.Get(), nTargetPairIndex, nLevel + 1, nCurPairIndex);
339     if (result.has_value())
340       return result;
341   }
342   return std::nullopt;
343 }
344 
345 // Wrapper for SearchNameNodeByIndexInternal() so callers do not need to know
346 // about the details.
SearchNameNodeByIndex(CPDF_Dictionary * pNode,size_t nTargetPairIndex)347 std::optional<IndexSearchResult> SearchNameNodeByIndex(
348     CPDF_Dictionary* pNode,
349     size_t nTargetPairIndex) {
350   size_t nCurPairIndex = 0;
351   return SearchNameNodeByIndexInternal(pNode, nTargetPairIndex, 0,
352                                        &nCurPairIndex);
353 }
354 
355 // Get the total number of key-value pairs in the tree with root |pNode|.
CountNamesInternal(const CPDF_Dictionary * pNode,int nLevel,std::set<const CPDF_Dictionary * > & seen)356 size_t CountNamesInternal(const CPDF_Dictionary* pNode,
357                           int nLevel,
358                           std::set<const CPDF_Dictionary*>& seen) {
359   if (nLevel > kNameTreeMaxRecursion)
360     return 0;
361 
362   const bool inserted = seen.insert(pNode).second;
363   if (!inserted)
364     return 0;
365 
366   RetainPtr<const CPDF_Array> pNames = pNode->GetArrayFor("Names");
367   if (pNames)
368     return pNames->size() / 2;
369 
370   RetainPtr<const CPDF_Array> pKids = pNode->GetArrayFor("Kids");
371   if (!pKids)
372     return 0;
373 
374   size_t nCount = 0;
375   for (size_t i = 0; i < pKids->size(); i++) {
376     RetainPtr<const CPDF_Dictionary> pKid = pKids->GetDictAt(i);
377     if (!pKid)
378       continue;
379 
380     nCount += CountNamesInternal(pKid.Get(), nLevel + 1, seen);
381   }
382   return nCount;
383 }
384 
GetNamedDestFromObject(RetainPtr<const CPDF_Object> obj)385 RetainPtr<const CPDF_Array> GetNamedDestFromObject(
386     RetainPtr<const CPDF_Object> obj) {
387   RetainPtr<const CPDF_Array> array = ToArray(obj);
388   if (array)
389     return array;
390   RetainPtr<const CPDF_Dictionary> dict = ToDictionary(obj);
391   if (dict)
392     return dict->GetArrayFor("D");
393   return nullptr;
394 }
395 
LookupOldStyleNamedDest(CPDF_Document * pDoc,const ByteString & name)396 RetainPtr<const CPDF_Array> LookupOldStyleNamedDest(CPDF_Document* pDoc,
397                                                     const ByteString& name) {
398   RetainPtr<const CPDF_Dictionary> pDests =
399       pDoc->GetRoot()->GetDictFor("Dests");
400   if (!pDests)
401     return nullptr;
402   return GetNamedDestFromObject(pDests->GetDirectObjectFor(name));
403 }
404 
405 }  // namespace
406 
CPDF_NameTree(RetainPtr<CPDF_Dictionary> pRoot)407 CPDF_NameTree::CPDF_NameTree(RetainPtr<CPDF_Dictionary> pRoot)
408     : m_pRoot(std::move(pRoot)) {
409   DCHECK(m_pRoot);
410 }
411 
412 CPDF_NameTree::~CPDF_NameTree() = default;
413 
414 // static
Create(CPDF_Document * pDoc,const ByteString & category)415 std::unique_ptr<CPDF_NameTree> CPDF_NameTree::Create(
416     CPDF_Document* pDoc,
417     const ByteString& category) {
418   RetainPtr<CPDF_Dictionary> pRoot = pDoc->GetMutableRoot();
419   if (!pRoot)
420     return nullptr;
421 
422   RetainPtr<CPDF_Dictionary> pNames = pRoot->GetMutableDictFor("Names");
423   if (!pNames)
424     return nullptr;
425 
426   RetainPtr<CPDF_Dictionary> pCategory = pNames->GetMutableDictFor(category);
427   if (!pCategory)
428     return nullptr;
429 
430   return pdfium::WrapUnique(
431       new CPDF_NameTree(std::move(pCategory)));  // Private ctor.
432 }
433 
434 // static
CreateWithRootNameArray(CPDF_Document * pDoc,const ByteString & category)435 std::unique_ptr<CPDF_NameTree> CPDF_NameTree::CreateWithRootNameArray(
436     CPDF_Document* pDoc,
437     const ByteString& category) {
438   RetainPtr<CPDF_Dictionary> pRoot = pDoc->GetMutableRoot();
439   if (!pRoot)
440     return nullptr;
441 
442   // Retrieve the document's Names dictionary; create it if missing.
443   RetainPtr<CPDF_Dictionary> pNames = pRoot->GetMutableDictFor("Names");
444   if (!pNames) {
445     pNames = pDoc->NewIndirect<CPDF_Dictionary>();
446     pRoot->SetNewFor<CPDF_Reference>("Names", pDoc, pNames->GetObjNum());
447   }
448 
449   // Create the |category| dictionary if missing.
450   RetainPtr<CPDF_Dictionary> pCategory = pNames->GetMutableDictFor(category);
451   if (!pCategory) {
452     pCategory = pDoc->NewIndirect<CPDF_Dictionary>();
453     pCategory->SetNewFor<CPDF_Array>("Names");
454     pNames->SetNewFor<CPDF_Reference>(category, pDoc, pCategory->GetObjNum());
455   }
456 
457   return pdfium::WrapUnique(new CPDF_NameTree(pCategory));  // Private ctor.
458 }
459 
460 // static
CreateForTesting(CPDF_Dictionary * pRoot)461 std::unique_ptr<CPDF_NameTree> CPDF_NameTree::CreateForTesting(
462     CPDF_Dictionary* pRoot) {
463   return pdfium::WrapUnique(
464       new CPDF_NameTree(pdfium::WrapRetain(pRoot)));  // Private ctor.
465 }
466 
467 // static
LookupNamedDest(CPDF_Document * pDoc,const ByteString & name)468 RetainPtr<const CPDF_Array> CPDF_NameTree::LookupNamedDest(
469     CPDF_Document* pDoc,
470     const ByteString& name) {
471   RetainPtr<const CPDF_Array> dest_array;
472   std::unique_ptr<CPDF_NameTree> name_tree = Create(pDoc, "Dests");
473   if (name_tree)
474     dest_array = name_tree->LookupNewStyleNamedDest(name);
475   if (!dest_array)
476     dest_array = LookupOldStyleNamedDest(pDoc, name);
477   return dest_array;
478 }
479 
GetCount() const480 size_t CPDF_NameTree::GetCount() const {
481   std::set<const CPDF_Dictionary*> seen;
482   return CountNamesInternal(m_pRoot.Get(), 0, seen);
483 }
484 
AddValueAndName(RetainPtr<CPDF_Object> pObj,const WideString & name)485 bool CPDF_NameTree::AddValueAndName(RetainPtr<CPDF_Object> pObj,
486                                     const WideString& name) {
487   RetainPtr<CPDF_Array> pFind;
488   int nFindIndex = -1;
489   // Handle the corner case where the root node is empty. i.e. No kids and no
490   // names. In which case, just insert into it and skip all the searches.
491   RetainPtr<CPDF_Array> pNames = m_pRoot->GetMutableArrayFor("Names");
492   if (pNames && pNames->IsEmpty() && !m_pRoot->GetArrayFor("Kids"))
493     pFind = pNames;
494 
495   if (!pFind) {
496     // Fail if the tree already contains this name or if the tree is too deep.
497     if (SearchNameNodeByName(m_pRoot, name, &pFind, &nFindIndex))
498       return false;
499   }
500 
501   // If the returned |pFind| is a nullptr, then |name| is smaller than all
502   // existing entries in the tree, and we did not find a leaf array to place
503   // |name| into. We instead will find the leftmost leaf array in which to place
504   // |name| and |pObj|.
505   if (!pFind) {
506     std::optional<IndexSearchResult> result =
507         SearchNameNodeByIndex(m_pRoot.Get(), 0);
508     if (!result.has_value()) {
509       // Give up if that fails too.
510       return false;
511     }
512 
513     pFind = result.value().container;
514     DCHECK(pFind);
515   }
516 
517   // Insert the name and the object into the leaf array found. Note that the
518   // insertion position is right after the key-value pair returned by |index|.
519   size_t nNameIndex = (nFindIndex + 1) * 2;
520   size_t nValueIndex = nNameIndex + 1;
521   pFind->InsertNewAt<CPDF_String>(nNameIndex, name.AsStringView());
522   pFind->InsertAt(nValueIndex, std::move(pObj));
523 
524   // Expand the limits that the newly added name is under, if the name falls
525   // outside of the limits of its leaf array or any arrays above it.
526   std::vector<CPDF_Array*> all_limits =
527       GetNodeAncestorsLimits(m_pRoot, pFind.Get());
528   for (auto* pLimits : all_limits) {
529     if (!pLimits)
530       continue;
531 
532     if (name.Compare(pLimits->GetUnicodeTextAt(0)) < 0)
533       pLimits->SetNewAt<CPDF_String>(0, name.AsStringView());
534 
535     if (name.Compare(pLimits->GetUnicodeTextAt(1)) > 0)
536       pLimits->SetNewAt<CPDF_String>(1, name.AsStringView());
537   }
538   return true;
539 }
540 
DeleteValueAndName(size_t nIndex)541 bool CPDF_NameTree::DeleteValueAndName(size_t nIndex) {
542   std::optional<IndexSearchResult> result =
543       SearchNameNodeByIndex(m_pRoot.Get(), nIndex);
544   if (!result) {
545     // Fail if the tree does not contain |nIndex|.
546     return false;
547   }
548 
549   // Remove the name and the object from the leaf array |pFind|.
550   RetainPtr<CPDF_Array> pFind = result.value().container;
551   pFind->RemoveAt(result.value().index + 1);
552   pFind->RemoveAt(result.value().index);
553 
554   // Delete empty nodes and update the limits of |pFind|'s ancestors as needed.
555   UpdateNodesAndLimitsUponDeletion(m_pRoot.Get(), pFind.Get(),
556                                    result.value().key, 0);
557   return true;
558 }
559 
LookupValueAndName(size_t nIndex,WideString * csName) const560 RetainPtr<CPDF_Object> CPDF_NameTree::LookupValueAndName(
561     size_t nIndex,
562     WideString* csName) const {
563   std::optional<IndexSearchResult> result =
564       SearchNameNodeByIndex(m_pRoot.Get(), nIndex);
565   if (!result) {
566     csName->clear();
567     return nullptr;
568   }
569 
570   *csName = std::move(result.value().key);
571   return result.value().value;
572 }
573 
LookupValue(const WideString & csName) const574 RetainPtr<const CPDF_Object> CPDF_NameTree::LookupValue(
575     const WideString& csName) const {
576   return SearchNameNodeByName(m_pRoot, csName, nullptr, nullptr);
577 }
578 
LookupNewStyleNamedDest(const ByteString & sName)579 RetainPtr<const CPDF_Array> CPDF_NameTree::LookupNewStyleNamedDest(
580     const ByteString& sName) {
581   return GetNamedDestFromObject(
582       LookupValue(PDF_DecodeText(sName.unsigned_span())));
583 }
584