Fix insertions into empty name trees.

When a name tree is empty, CPDF_NameTree::AddValueAndName() tries to
find a leaf node to insert into and fails. Instead, just insert into the
root node of the name tree. Add a unit test for this case.

This fixes FPDFDoc_AddAttachment() for the case where the EmbeddedFiles
name tree is empty. Fix the known to be broken
AddAttachmentsToFileWithNoAttachments embedder test case and do further
testing.

Bug: pdfium:1502
Change-Id: Iab189ce612070e0ef268ac6e01a1cbcce96c8d2b
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/67990
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfdoc/cpdf_nametree.cpp b/core/fpdfdoc/cpdf_nametree.cpp
index b232540..80bb5de 100644
--- a/core/fpdfdoc/cpdf_nametree.cpp
+++ b/core/fpdfdoc/cpdf_nametree.cpp
@@ -310,7 +310,7 @@
   m_pRoot.Reset(pNames->GetDictFor(category));
 }
 
-CPDF_NameTree::~CPDF_NameTree() {}
+CPDF_NameTree::~CPDF_NameTree() = default;
 
 size_t CPDF_NameTree::GetCount() const {
   return m_pRoot ? CountNamesInternal(m_pRoot.Get(), 0) : 0;
@@ -324,10 +324,18 @@
   size_t nIndex = 0;
   CPDF_Array* pFind = nullptr;
   int nFindIndex = -1;
-  // Fail if the tree already contains this name or if the tree is too deep.
-  if (SearchNameNodeByName(m_pRoot.Get(), name, 0, &nIndex, &pFind,
-                           &nFindIndex)) {
-    return false;
+  // Handle the corner case where the root node is empty. i.e. No kids and no
+  // names. In which case, just insert into it and skip all the searches.
+  CPDF_Array* pNames = m_pRoot->GetArrayFor("Names");
+  if (pNames && pNames->IsEmpty() && !m_pRoot->GetArrayFor("Kids"))
+    pFind = pNames;
+
+  if (!pFind) {
+    // Fail if the tree already contains this name or if the tree is too deep.
+    if (SearchNameNodeByName(m_pRoot.Get(), name, 0, &nIndex, &pFind,
+                             &nFindIndex)) {
+      return false;
+    }
   }
 
   // If the returned |pFind| is a nullptr, then |name| is smaller than all
diff --git a/core/fpdfdoc/cpdf_nametree_unittest.cpp b/core/fpdfdoc/cpdf_nametree_unittest.cpp
index 595a7f1..ae90980 100644
--- a/core/fpdfdoc/cpdf_nametree_unittest.cpp
+++ b/core/fpdfdoc/cpdf_nametree_unittest.cpp
@@ -109,7 +109,6 @@
   AddNameKeyValue(pNames, "7.txt", 777);
 
   CPDF_NameTree nameTree(pRootDict.Get());
-  pNames = nameTree.GetRootForTest()->GetArrayFor("Names");
 
   // Insert a name that already exists in the names array.
   EXPECT_FALSE(
@@ -135,6 +134,40 @@
   CheckNameKeyValue(pNames, 4, "9.txt", 999);
 }
 
+TEST(cpdf_nametree, AddIntoEmptyNames) {
+  // Set up a name tree with an empty Names array.
+  auto pRootDict = pdfium::MakeRetain<CPDF_Dictionary>();
+  CPDF_Array* pNames = pRootDict->SetNewFor<CPDF_Array>("Names");
+
+  CPDF_NameTree name_tree(pRootDict.Get());
+
+  // Insert a name should work.
+  EXPECT_TRUE(name_tree.AddValueAndName(pdfium::MakeRetain<CPDF_Number>(111),
+                                        L"2.txt"));
+
+  // Insert a name that already exists in the names array.
+  EXPECT_FALSE(name_tree.AddValueAndName(pdfium::MakeRetain<CPDF_Number>(111),
+                                         L"2.txt"));
+
+  // Insert in the beginning of the names array.
+  EXPECT_TRUE(name_tree.AddValueAndName(pdfium::MakeRetain<CPDF_Number>(111),
+                                        L"1.txt"));
+
+  // Insert in the middle of the names array.
+  EXPECT_TRUE(name_tree.AddValueAndName(pdfium::MakeRetain<CPDF_Number>(555),
+                                        L"5.txt"));
+
+  // Insert at the end of the names array.
+  EXPECT_TRUE(name_tree.AddValueAndName(pdfium::MakeRetain<CPDF_Number>(999),
+                                        L"9.txt"));
+
+  // Check that the names array has the expected key-value pairs.
+  CheckNameKeyValue(pNames, 0, "1.txt", 111);
+  CheckNameKeyValue(pNames, 1, "2.txt", 111);
+  CheckNameKeyValue(pNames, 2, "5.txt", 555);
+  CheckNameKeyValue(pNames, 3, "9.txt", 999);
+}
+
 TEST(cpdf_nametree, AddIntoKids) {
   // Set up a name tree with five nodes of three levels.
   auto pRootDict = pdfium::MakeRetain<CPDF_Dictionary>();
diff --git a/fpdfsdk/fpdf_attachment_embeddertest.cpp b/fpdfsdk/fpdf_attachment_embeddertest.cpp
index 675a468..08f00dc 100644
--- a/fpdfsdk/fpdf_attachment_embeddertest.cpp
+++ b/fpdfsdk/fpdf_attachment_embeddertest.cpp
@@ -122,6 +122,7 @@
   constexpr char kContents1[] = "Hello!";
   EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents1,
                                      strlen(kContents1)));
+  EXPECT_EQ(3, FPDFDoc_GetAttachmentCount(document()));
 
   // Verify the name of the new attachment (i.e. the first attachment).
   attachment = FPDFDoc_GetAttachment(document(), 0);
@@ -249,9 +250,55 @@
   ScopedFPDFWideString file_name = GetFPDFWideString(L"0.txt");
   FPDF_ATTACHMENT attachment =
       FPDFDoc_AddAttachment(document(), file_name.get());
-  // TODO(crbug.com/pdfium/1502): This should return true. Fix the bug and do
-  // additional verifications here.
-  EXPECT_FALSE(attachment);
+  ASSERT_TRUE(attachment);
+
+  // Set the new attachment's file.
+  constexpr char kContents1[] = "Hello!";
+  EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents1,
+                                     strlen(kContents1)));
+  EXPECT_EQ(1, FPDFDoc_GetAttachmentCount(document()));
+
+  // Verify the name of the new attachment (i.e. the first attachment).
+  attachment = FPDFDoc_GetAttachment(document(), 0);
+  ASSERT_TRUE(attachment);
+  unsigned long length_bytes = FPDFAttachment_GetName(attachment, nullptr, 0);
+  ASSERT_EQ(12u, length_bytes);
+  std::vector<FPDF_WCHAR> buf = GetFPDFWideStringBuffer(length_bytes);
+  EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), length_bytes));
+  EXPECT_EQ(L"0.txt", GetPlatformWString(buf.data()));
+
+  // Verify the content of the new attachment (i.e. the first attachment).
+  length_bytes = FPDFAttachment_GetFile(attachment, nullptr, 0);
+  std::vector<char> content_buf(length_bytes);
+  ASSERT_EQ(
+      6u, FPDFAttachment_GetFile(attachment, content_buf.data(), length_bytes));
+  EXPECT_EQ(std::string(kContents1), std::string(content_buf.data(), 6));
+
+  // Add an attachment to the end of the embedded file list and set its file.
+  file_name = GetFPDFWideString(L"z.txt");
+  attachment = FPDFDoc_AddAttachment(document(), file_name.get());
+  ASSERT_TRUE(attachment);
+  constexpr char kContents2[] = "World!";
+  EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents2,
+                                     strlen(kContents2)));
+  EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document()));
+
+  // Verify the name of the new attachment (i.e. the second attachment).
+  attachment = FPDFDoc_GetAttachment(document(), 1);
+  ASSERT_TRUE(attachment);
+  length_bytes = FPDFAttachment_GetName(attachment, nullptr, 0);
+  ASSERT_EQ(12u, length_bytes);
+  buf = GetFPDFWideStringBuffer(length_bytes);
+  EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), length_bytes));
+  EXPECT_EQ(L"z.txt", GetPlatformWString(buf.data()));
+
+  // Verify the content of the new attachment (i.e. the second attachment).
+  length_bytes = FPDFAttachment_GetFile(attachment, nullptr, 0);
+  content_buf.clear();
+  content_buf.resize(length_bytes);
+  ASSERT_EQ(
+      6u, FPDFAttachment_GetFile(attachment, content_buf.data(), length_bytes));
+  EXPECT_EQ(std::string(kContents2), std::string(content_buf.data(), 6));
 }
 
 TEST_F(FPDFAttachmentEmbedderTest, DeleteAttachment) {