Home | History | Annotate | Download | only in fpdfdoc
      1 // Copyright 2017 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/fpdfdoc/cpdf_nametree.h"
      6 #include "core/fpdfapi/parser/cpdf_array.h"
      7 #include "core/fpdfapi/parser/cpdf_dictionary.h"
      8 #include "core/fpdfapi/parser/cpdf_number.h"
      9 #include "core/fpdfapi/parser/cpdf_string.h"
     10 #include "testing/gtest/include/gtest/gtest.h"
     11 
     12 namespace {
     13 
     14 void AddNameKeyValue(CPDF_Array* pNames, const char* key, const int value) {
     15   pNames->AddNew<CPDF_String>(key, false);
     16   pNames->AddNew<CPDF_Number>(value);
     17 }
     18 
     19 void CheckNameKeyValue(CPDF_Array* pNames,
     20                        const int index,
     21                        const char* key,
     22                        const int value) {
     23   EXPECT_STREQ(key, pNames->GetStringAt(index * 2).c_str());
     24   EXPECT_EQ(value, pNames->GetIntegerAt(index * 2 + 1));
     25 }
     26 
     27 void AddLimitsArray(CPDF_Dictionary* pNode,
     28                     const char* least,
     29                     const char* greatest) {
     30   CPDF_Array* pLimits = pNode->SetNewFor<CPDF_Array>("Limits");
     31   pLimits->AddNew<CPDF_String>(least, false);
     32   pLimits->AddNew<CPDF_String>(greatest, false);
     33 }
     34 
     35 void CheckLimitsArray(CPDF_Dictionary* pNode,
     36                       const char* least,
     37                       const char* greatest) {
     38   CPDF_Array* pLimits = pNode->GetArrayFor("Limits");
     39   ASSERT_TRUE(pLimits);
     40   EXPECT_STREQ(least, pLimits->GetStringAt(0).c_str());
     41   EXPECT_STREQ(greatest, pLimits->GetStringAt(1).c_str());
     42 }
     43 
     44 void FillNameTreeDict(CPDF_Dictionary* pRootDict) {
     45   CPDF_Array* pKids = pRootDict->SetNewFor<CPDF_Array>("Kids");
     46   CPDF_Dictionary* pKid1 = pKids->AddNew<CPDF_Dictionary>();
     47 
     48   // Make the lower and upper limit out of order on purpose.
     49   AddLimitsArray(pKid1, "9.txt", "1.txt");
     50   pKids = pKid1->SetNewFor<CPDF_Array>("Kids");
     51   CPDF_Dictionary* pKid2 = pKids->AddNew<CPDF_Dictionary>();
     52   CPDF_Dictionary* pKid3 = pKids->AddNew<CPDF_Dictionary>();
     53 
     54   AddLimitsArray(pKid2, "1.txt", "5.txt");
     55   pKids = pKid2->SetNewFor<CPDF_Array>("Kids");
     56   CPDF_Dictionary* pKid4 = pKids->AddNew<CPDF_Dictionary>();
     57   CPDF_Dictionary* pKid5 = pKids->AddNew<CPDF_Dictionary>();
     58 
     59   AddLimitsArray(pKid3, "9.txt", "9.txt");
     60   CPDF_Array* pNames = pKid3->SetNewFor<CPDF_Array>("Names");
     61   AddNameKeyValue(pNames, "9.txt", 999);
     62 
     63   // Make the lower and upper limit out of order on purpose.
     64   AddLimitsArray(pKid4, "2.txt", "1.txt");
     65   pNames = pKid4->SetNewFor<CPDF_Array>("Names");
     66   AddNameKeyValue(pNames, "1.txt", 111);
     67   AddNameKeyValue(pNames, "2.txt", 222);
     68 
     69   AddLimitsArray(pKid5, "3.txt", "5.txt");
     70   pNames = pKid5->SetNewFor<CPDF_Array>("Names");
     71   AddNameKeyValue(pNames, "3.txt", 333);
     72   AddNameKeyValue(pNames, "5.txt", 555);
     73 }
     74 
     75 }  // namespace
     76 
     77 TEST(cpdf_nametree, GetUnicodeNameWithBOM) {
     78   // Set up the root dictionary with a Names array.
     79   auto pRootDict = pdfium::MakeUnique<CPDF_Dictionary>();
     80   CPDF_Array* pNames = pRootDict->SetNewFor<CPDF_Array>("Names");
     81 
     82   // Add the key "1" (with BOM) and value 100 into the array.
     83   std::ostringstream buf;
     84   constexpr char kData[] = "\xFE\xFF\x00\x31";
     85   for (size_t i = 0; i < sizeof(kData); ++i)
     86     buf.put(kData[i]);
     87   pNames->AddNew<CPDF_String>(ByteString(buf), true);
     88   pNames->AddNew<CPDF_Number>(100);
     89 
     90   // Check that the key is as expected.
     91   CPDF_NameTree nameTree(pRootDict.get());
     92   WideString storedName;
     93   nameTree.LookupValueAndName(0, &storedName);
     94   EXPECT_STREQ(L"1", storedName.c_str());
     95 
     96   // Check that the correct value object can be obtained by looking up "1".
     97   WideString matchName = L"1";
     98   CPDF_Object* pObj = nameTree.LookupValue(matchName);
     99   ASSERT_TRUE(pObj->IsNumber());
    100   EXPECT_EQ(100, pObj->AsNumber()->GetInteger());
    101 }
    102 
    103 TEST(cpdf_nametree, AddIntoNames) {
    104   // Set up a name tree with a single Names array.
    105   auto pRootDict = pdfium::MakeUnique<CPDF_Dictionary>();
    106   CPDF_Array* pNames = pRootDict->SetNewFor<CPDF_Array>("Names");
    107   AddNameKeyValue(pNames, "2.txt", 222);
    108   AddNameKeyValue(pNames, "7.txt", 777);
    109 
    110   CPDF_NameTree nameTree(pRootDict.get());
    111   pNames = nameTree.GetRoot()->GetArrayFor("Names");
    112 
    113   // Insert a name that already exists in the names array.
    114   EXPECT_FALSE(
    115       nameTree.AddValueAndName(pdfium::MakeUnique<CPDF_Number>(111), L"2.txt"));
    116 
    117   // Insert in the beginning of the names array.
    118   EXPECT_TRUE(
    119       nameTree.AddValueAndName(pdfium::MakeUnique<CPDF_Number>(111), L"1.txt"));
    120 
    121   // Insert in the middle of the names array.
    122   EXPECT_TRUE(
    123       nameTree.AddValueAndName(pdfium::MakeUnique<CPDF_Number>(555), L"5.txt"));
    124 
    125   // Insert at the end of the names array.
    126   EXPECT_TRUE(
    127       nameTree.AddValueAndName(pdfium::MakeUnique<CPDF_Number>(999), L"9.txt"));
    128 
    129   // Check that the names array has the expected key-value pairs.
    130   CheckNameKeyValue(pNames, 0, "1.txt", 111);
    131   CheckNameKeyValue(pNames, 1, "2.txt", 222);
    132   CheckNameKeyValue(pNames, 2, "5.txt", 555);
    133   CheckNameKeyValue(pNames, 3, "7.txt", 777);
    134   CheckNameKeyValue(pNames, 4, "9.txt", 999);
    135 }
    136 
    137 TEST(cpdf_nametree, AddIntoKids) {
    138   // Set up a name tree with five nodes of three levels.
    139   auto pRootDict = pdfium::MakeUnique<CPDF_Dictionary>();
    140   FillNameTreeDict(pRootDict.get());
    141   CPDF_NameTree nameTree(pRootDict.get());
    142 
    143   // Check that adding an existing name would fail.
    144   EXPECT_FALSE(
    145       nameTree.AddValueAndName(pdfium::MakeUnique<CPDF_Number>(444), L"9.txt"));
    146 
    147   // Add a name within the limits of a leaf node.
    148   EXPECT_TRUE(
    149       nameTree.AddValueAndName(pdfium::MakeUnique<CPDF_Number>(444), L"4.txt"));
    150   ASSERT_TRUE(nameTree.LookupValue(L"4.txt"));
    151   EXPECT_EQ(444, nameTree.LookupValue(L"4.txt")->GetInteger());
    152 
    153   // Add a name that requires changing the limits of two bottom levels.
    154   EXPECT_TRUE(
    155       nameTree.AddValueAndName(pdfium::MakeUnique<CPDF_Number>(666), L"6.txt"));
    156   ASSERT_TRUE(nameTree.LookupValue(L"6.txt"));
    157   EXPECT_EQ(666, nameTree.LookupValue(L"6.txt")->GetInteger());
    158 
    159   // Add a name that requires changing the limits of two top levels.
    160   EXPECT_TRUE(
    161       nameTree.AddValueAndName(pdfium::MakeUnique<CPDF_Number>(99), L"99.txt"));
    162   ASSERT_TRUE(nameTree.LookupValue(L"99.txt"));
    163   EXPECT_EQ(99, nameTree.LookupValue(L"99.txt")->GetInteger());
    164 
    165   // Add a name that requires changing the lower limit of all levels.
    166   EXPECT_TRUE(
    167       nameTree.AddValueAndName(pdfium::MakeUnique<CPDF_Number>(-5), L"0.txt"));
    168   ASSERT_TRUE(nameTree.LookupValue(L"0.txt"));
    169   EXPECT_EQ(-5, nameTree.LookupValue(L"0.txt")->GetInteger());
    170 
    171   // Check that the node on the first level has the expected limits.
    172   CPDF_Dictionary* pKid1 =
    173       nameTree.GetRoot()->GetArrayFor("Kids")->GetDictAt(0);
    174   ASSERT_TRUE(pKid1);
    175   CheckLimitsArray(pKid1, "0.txt", "99.txt");
    176 
    177   // Check that the nodes on the second level has the expected limits and names.
    178   CPDF_Dictionary* pKid2 = pKid1->GetArrayFor("Kids")->GetDictAt(0);
    179   ASSERT_TRUE(pKid2);
    180   CheckLimitsArray(pKid2, "0.txt", "6.txt");
    181 
    182   CPDF_Dictionary* pKid3 = pKid1->GetArrayFor("Kids")->GetDictAt(1);
    183   ASSERT_TRUE(pKid3);
    184   CheckLimitsArray(pKid3, "9.txt", "99.txt");
    185   CPDF_Array* pNames = pKid3->GetArrayFor("Names");
    186   ASSERT_TRUE(pNames);
    187   CheckNameKeyValue(pNames, 0, "9.txt", 999);
    188   CheckNameKeyValue(pNames, 1, "99.txt", 99);
    189 
    190   // Check that the nodes on the third level has the expected limits and names.
    191   CPDF_Dictionary* pKid4 = pKid2->GetArrayFor("Kids")->GetDictAt(0);
    192   ASSERT_TRUE(pKid4);
    193   CheckLimitsArray(pKid4, "0.txt", "2.txt");
    194   pNames = pKid4->GetArrayFor("Names");
    195   ASSERT_TRUE(pNames);
    196   CheckNameKeyValue(pNames, 0, "0.txt", -5);
    197   CheckNameKeyValue(pNames, 1, "1.txt", 111);
    198   CheckNameKeyValue(pNames, 2, "2.txt", 222);
    199 
    200   CPDF_Dictionary* pKid5 = pKid2->GetArrayFor("Kids")->GetDictAt(1);
    201   ASSERT_TRUE(pKid5);
    202   CheckLimitsArray(pKid5, "3.txt", "6.txt");
    203   pNames = pKid5->GetArrayFor("Names");
    204   ASSERT_TRUE(pNames);
    205   CheckNameKeyValue(pNames, 0, "3.txt", 333);
    206   CheckNameKeyValue(pNames, 1, "4.txt", 444);
    207   CheckNameKeyValue(pNames, 2, "5.txt", 555);
    208   CheckNameKeyValue(pNames, 3, "6.txt", 666);
    209 }
    210 
    211 TEST(cpdf_nametree, DeleteFromKids) {
    212   // Set up a name tree with five nodes of three levels.
    213   auto pRootDict = pdfium::MakeUnique<CPDF_Dictionary>();
    214   FillNameTreeDict(pRootDict.get());
    215   CPDF_NameTree nameTree(pRootDict.get());
    216 
    217   // Retrieve the kid dictionaries.
    218   CPDF_Dictionary* pKid1 =
    219       nameTree.GetRoot()->GetArrayFor("Kids")->GetDictAt(0);
    220   ASSERT_TRUE(pKid1);
    221   CPDF_Dictionary* pKid2 = pKid1->GetArrayFor("Kids")->GetDictAt(0);
    222   ASSERT_TRUE(pKid2);
    223   CPDF_Dictionary* pKid3 = pKid1->GetArrayFor("Kids")->GetDictAt(1);
    224   ASSERT_TRUE(pKid3);
    225   CPDF_Dictionary* pKid4 = pKid2->GetArrayFor("Kids")->GetDictAt(0);
    226   ASSERT_TRUE(pKid4);
    227   CPDF_Dictionary* pKid5 = pKid2->GetArrayFor("Kids")->GetDictAt(1);
    228   ASSERT_TRUE(pKid5);
    229 
    230   // Check that deleting an out-of-bound index would fail.
    231   EXPECT_FALSE(nameTree.DeleteValueAndName(5));
    232 
    233   // Delete the name "9.txt", and check that its node gets deleted and its
    234   // parent node's limits get updated.
    235   WideString csName;
    236   ASSERT_TRUE(nameTree.LookupValue(L"9.txt"));
    237   EXPECT_EQ(999, nameTree.LookupValue(L"9.txt")->GetInteger());
    238   EXPECT_TRUE(nameTree.LookupValueAndName(4, &csName));
    239   EXPECT_STREQ(L"9.txt", csName.c_str());
    240   EXPECT_EQ(2u, pKid1->GetArrayFor("Kids")->GetCount());
    241   EXPECT_TRUE(nameTree.DeleteValueAndName(4));
    242   EXPECT_EQ(1u, pKid1->GetArrayFor("Kids")->GetCount());
    243   CheckLimitsArray(pKid1, "1.txt", "5.txt");
    244 
    245   // Delete the name "2.txt", and check that its node does not get deleted, its
    246   // node's limits get updated, and no other limits get updated.
    247   ASSERT_TRUE(nameTree.LookupValue(L"2.txt"));
    248   EXPECT_EQ(222, nameTree.LookupValue(L"2.txt")->GetInteger());
    249   EXPECT_TRUE(nameTree.LookupValueAndName(1, &csName));
    250   EXPECT_STREQ(L"2.txt", csName.c_str());
    251   EXPECT_EQ(4u, pKid4->GetArrayFor("Names")->GetCount());
    252   EXPECT_TRUE(nameTree.DeleteValueAndName(1));
    253   EXPECT_EQ(2u, pKid4->GetArrayFor("Names")->GetCount());
    254   CheckLimitsArray(pKid4, "1.txt", "1.txt");
    255   CheckLimitsArray(pKid2, "1.txt", "5.txt");
    256   CheckLimitsArray(pKid1, "1.txt", "5.txt");
    257 
    258   // Delete the name "1.txt", and check that its node gets deleted, and its
    259   // parent's and gradparent's limits get updated.
    260   ASSERT_TRUE(nameTree.LookupValue(L"1.txt"));
    261   EXPECT_EQ(111, nameTree.LookupValue(L"1.txt")->GetInteger());
    262   EXPECT_TRUE(nameTree.LookupValueAndName(0, &csName));
    263   EXPECT_STREQ(L"1.txt", csName.c_str());
    264   EXPECT_EQ(2u, pKid2->GetArrayFor("Kids")->GetCount());
    265   EXPECT_TRUE(nameTree.DeleteValueAndName(0));
    266   EXPECT_EQ(1u, pKid2->GetArrayFor("Kids")->GetCount());
    267   CheckLimitsArray(pKid2, "3.txt", "5.txt");
    268   CheckLimitsArray(pKid1, "3.txt", "5.txt");
    269 
    270   // Delete the name "3.txt", and check that its node does not get deleted, and
    271   // its node's, its parent's, and its grandparent's limits get updated.
    272   ASSERT_TRUE(nameTree.LookupValue(L"3.txt"));
    273   EXPECT_EQ(333, nameTree.LookupValue(L"3.txt")->GetInteger());
    274   EXPECT_TRUE(nameTree.LookupValueAndName(0, &csName));
    275   EXPECT_STREQ(L"3.txt", csName.c_str());
    276   EXPECT_EQ(4u, pKid5->GetArrayFor("Names")->GetCount());
    277   EXPECT_TRUE(nameTree.DeleteValueAndName(0));
    278   EXPECT_EQ(2u, pKid5->GetArrayFor("Names")->GetCount());
    279   CheckLimitsArray(pKid5, "5.txt", "5.txt");
    280   CheckLimitsArray(pKid2, "5.txt", "5.txt");
    281   CheckLimitsArray(pKid1, "5.txt", "5.txt");
    282 
    283   // Delete the name "5.txt", and check that all nodes in the tree get deleted
    284   // since they are now all empty.
    285   ASSERT_TRUE(nameTree.LookupValue(L"5.txt"));
    286   EXPECT_EQ(555, nameTree.LookupValue(L"5.txt")->GetInteger());
    287   EXPECT_TRUE(nameTree.LookupValueAndName(0, &csName));
    288   EXPECT_STREQ(L"5.txt", csName.c_str());
    289   EXPECT_EQ(1u, nameTree.GetRoot()->GetArrayFor("Kids")->GetCount());
    290   EXPECT_TRUE(nameTree.DeleteValueAndName(0));
    291   EXPECT_EQ(0u, nameTree.GetRoot()->GetArrayFor("Kids")->GetCount());
    292 
    293   // Check that the tree is now empty.
    294   EXPECT_EQ(0u, nameTree.GetCount());
    295   EXPECT_FALSE(nameTree.LookupValueAndName(0, &csName));
    296   EXPECT_FALSE(nameTree.DeleteValueAndName(0));
    297 }
    298