// Copyright 2016 The PDFium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "core/fpdfapi/parser/cpdf_object.h" #include #include #include #include #include #include "constants/stream_dict_common.h" #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_boolean.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_indirect_object_holder.h" #include "core/fpdfapi/parser/cpdf_name.h" #include "core/fpdfapi/parser/cpdf_null.h" #include "core/fpdfapi/parser/cpdf_number.h" #include "core/fpdfapi/parser/cpdf_reference.h" #include "core/fpdfapi/parser/cpdf_stream.h" #include "core/fpdfapi/parser/cpdf_stream_acc.h" #include "core/fpdfapi/parser/cpdf_string.h" #include "core/fxcrt/data_vector.h" #include "core/fxcrt/fx_memory_wrappers.h" #include "testing/gtest/include/gtest/gtest.h" namespace { void TestArrayAccessors(const CPDF_Array* arr, size_t index, const char* str_val, const char* const_str_val, int int_val, float float_val, CPDF_Array* arr_val, CPDF_Dictionary* dict_val, CPDF_Stream* stream_val) { EXPECT_STREQ(str_val, arr->GetByteStringAt(index).c_str()); EXPECT_EQ(int_val, arr->GetIntegerAt(index)); EXPECT_EQ(float_val, arr->GetFloatAt(index)); EXPECT_EQ(arr_val, arr->GetArrayAt(index)); EXPECT_EQ(dict_val, arr->GetDictAt(index)); EXPECT_EQ(stream_val, arr->GetStreamAt(index)); } } // namespace class PDFObjectsTest : public testing::Test { public: void SetUp() override { // Initialize different kinds of objects. // Boolean objects. auto boolean_false_obj = pdfium::MakeRetain(false); auto boolean_true_obj = pdfium::MakeRetain(true); // Number objects. auto number_int_obj = pdfium::MakeRetain(1245); auto number_float_obj = pdfium::MakeRetain(9.00345f); // String objects. auto str_reg_obj = pdfium::MakeRetain(nullptr, L"A simple test"); auto str_spec_obj = pdfium::MakeRetain(nullptr, L"\t\n"); // Name object. auto name_obj = pdfium::MakeRetain(nullptr, "space"); // Array object. m_ArrayObj = pdfium::MakeRetain(); m_ArrayObj->InsertNewAt(0, 8902); m_ArrayObj->InsertNewAt(1, "address"); // Dictionary object. m_DictObj = pdfium::MakeRetain(); m_DictObj->SetNewFor("bool", false); m_DictObj->SetNewFor("num", 0.23f); // Stream object. static constexpr char kContents[] = "abcdefghijklmnopqrstuvwxyz"; auto pNewDict = pdfium::MakeRetain(); m_StreamDictObj = pNewDict; m_StreamDictObj->SetNewFor("key1", L" test dict"); m_StreamDictObj->SetNewFor("key2", -1); auto stream_obj = pdfium::MakeRetain( DataVector(std::begin(kContents), std::end(kContents)), std::move(pNewDict)); // Null Object. auto null_obj = pdfium::MakeRetain(); // All direct objects. CPDF_Object* objs[] = { boolean_false_obj.Get(), boolean_true_obj.Get(), number_int_obj.Get(), number_float_obj.Get(), str_reg_obj.Get(), str_spec_obj.Get(), name_obj.Get(), m_ArrayObj.Get(), m_DictObj.Get(), stream_obj.Get(), null_obj.Get()}; m_DirectObjTypes = { CPDF_Object::kBoolean, CPDF_Object::kBoolean, CPDF_Object::kNumber, CPDF_Object::kNumber, CPDF_Object::kString, CPDF_Object::kString, CPDF_Object::kName, CPDF_Object::kArray, CPDF_Object::kDictionary, CPDF_Object::kStream, CPDF_Object::kNullobj}; for (size_t i = 0; i < std::size(objs); ++i) m_DirectObjs.emplace_back(objs[i]); // Indirect references to indirect objects. m_ObjHolder = std::make_unique(); m_IndirectObjNums = { m_ObjHolder->AddIndirectObject(boolean_true_obj->Clone()), m_ObjHolder->AddIndirectObject(number_int_obj->Clone()), m_ObjHolder->AddIndirectObject(str_spec_obj->Clone()), m_ObjHolder->AddIndirectObject(name_obj->Clone()), m_ObjHolder->AddIndirectObject(m_ArrayObj->Clone()), m_ObjHolder->AddIndirectObject(m_DictObj->Clone()), m_ObjHolder->AddIndirectObject(stream_obj->Clone())}; for (uint32_t objnum : m_IndirectObjNums) { m_RefObjs.emplace_back( pdfium::MakeRetain(m_ObjHolder.get(), objnum)); } } bool Equal(const CPDF_Object* obj1, const CPDF_Object* obj2) { if (obj1 == obj2) return true; if (!obj1 || !obj2 || obj1->GetType() != obj2->GetType()) return false; switch (obj1->GetType()) { case CPDF_Object::kBoolean: return obj1->GetInteger() == obj2->GetInteger(); case CPDF_Object::kNumber: return obj1->AsNumber()->IsInteger() == obj2->AsNumber()->IsInteger() && obj1->GetInteger() == obj2->GetInteger(); case CPDF_Object::kString: case CPDF_Object::kName: return obj1->GetString() == obj2->GetString(); case CPDF_Object::kArray: { const CPDF_Array* array1 = obj1->AsArray(); const CPDF_Array* array2 = obj2->AsArray(); if (array1->size() != array2->size()) return false; for (size_t i = 0; i < array1->size(); ++i) { if (!Equal(array1->GetObjectAt(i).Get(), array2->GetObjectAt(i).Get())) { return false; } } return true; } case CPDF_Object::kDictionary: { const CPDF_Dictionary* dict1 = obj1->AsDictionary(); const CPDF_Dictionary* dict2 = obj2->AsDictionary(); if (dict1->size() != dict2->size()) return false; CPDF_DictionaryLocker locker1(dict1); for (const auto& item : locker1) { if (!Equal(item.second.Get(), dict2->GetObjectFor(item.first).Get())) return false; } return true; } case CPDF_Object::kNullobj: return true; case CPDF_Object::kStream: { RetainPtr stream1(obj1->AsStream()); RetainPtr stream2(obj2->AsStream()); if (!stream1->GetDict() && !stream2->GetDict()) return true; // Compare dictionaries. if (!Equal(stream1->GetDict().Get(), stream2->GetDict().Get())) return false; auto streamAcc1 = pdfium::MakeRetain(std::move(stream1)); streamAcc1->LoadAllDataRaw(); auto streamAcc2 = pdfium::MakeRetain(std::move(stream2)); streamAcc2->LoadAllDataRaw(); pdfium::span span1 = streamAcc1->GetSpan(); pdfium::span span2 = streamAcc2->GetSpan(); // Compare sizes. if (span1.size() != span2.size()) return false; return memcmp(span1.data(), span2.data(), span2.size()) == 0; } case CPDF_Object::kReference: return obj1->AsReference()->GetRefObjNum() == obj2->AsReference()->GetRefObjNum(); } return false; } protected: // m_ObjHolder needs to be declared first and destructed last since it also // refers to some objects in m_DirectObjs. std::unique_ptr m_ObjHolder; std::vector> m_DirectObjs; std::vector m_DirectObjTypes; std::vector> m_RefObjs; RetainPtr m_DictObj; RetainPtr m_StreamDictObj; RetainPtr m_ArrayObj; std::vector m_IndirectObjNums; }; TEST_F(PDFObjectsTest, GetString) { const char* const direct_obj_results[] = { "false", "true", "1245", "9.00345", "A simple test", "\t\n", "space", "", "", "", ""}; // Check for direct objects. for (size_t i = 0; i < m_DirectObjs.size(); ++i) EXPECT_STREQ(direct_obj_results[i], m_DirectObjs[i]->GetString().c_str()); // Check indirect references. const char* const indirect_obj_results[] = {"true", "1245", "\t\n", "space", "", "", ""}; for (size_t i = 0; i < m_RefObjs.size(); ++i) { EXPECT_STREQ(indirect_obj_results[i], m_RefObjs[i]->GetString().c_str()); } } TEST_F(PDFObjectsTest, GetUnicodeText) { const wchar_t* const direct_obj_results[] = { L"", L"", L"", L"", L"A simple test", L"\t\n", L"space", L"", L"", L"abcdefghijklmnopqrstuvwxyz", L""}; // Check for direct objects. for (size_t i = 0; i < m_DirectObjs.size(); ++i) { EXPECT_STREQ(direct_obj_results[i], m_DirectObjs[i]->GetUnicodeText().c_str()); } // Check indirect references. for (const auto& it : m_RefObjs) EXPECT_STREQ(L"", it->GetUnicodeText().c_str()); } TEST_F(PDFObjectsTest, GetNumber) { const float direct_obj_results[] = {0, 0, 1245, 9.00345f, 0, 0, 0, 0, 0, 0, 0}; // Check for direct objects. for (size_t i = 0; i < m_DirectObjs.size(); ++i) EXPECT_EQ(direct_obj_results[i], m_DirectObjs[i]->GetNumber()); // Check indirect references. const float indirect_obj_results[] = {0, 1245, 0, 0, 0, 0, 0}; for (size_t i = 0; i < m_RefObjs.size(); ++i) EXPECT_EQ(indirect_obj_results[i], m_RefObjs[i]->GetNumber()); } TEST_F(PDFObjectsTest, GetInteger) { const int direct_obj_results[] = {0, 1, 1245, 9, 0, 0, 0, 0, 0, 0, 0}; // Check for direct objects. for (size_t i = 0; i < m_DirectObjs.size(); ++i) EXPECT_EQ(direct_obj_results[i], m_DirectObjs[i]->GetInteger()); // Check indirect references. const int indirect_obj_results[] = {1, 1245, 0, 0, 0, 0, 0}; for (size_t i = 0; i < m_RefObjs.size(); ++i) EXPECT_EQ(indirect_obj_results[i], m_RefObjs[i]->GetInteger()); } TEST_F(PDFObjectsTest, GetDict) { const CPDF_Dictionary* const direct_obj_results[] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, m_DictObj.Get(), m_StreamDictObj.Get(), nullptr}; // Check for direct objects. for (size_t i = 0; i < m_DirectObjs.size(); ++i) EXPECT_EQ(direct_obj_results[i], m_DirectObjs[i]->GetDict()); // Check indirect references. const CPDF_Dictionary* const indirect_obj_results[] = {nullptr, nullptr, nullptr, nullptr, nullptr, m_DictObj.Get(), m_StreamDictObj.Get()}; for (size_t i = 0; i < m_RefObjs.size(); ++i) EXPECT_TRUE(Equal(indirect_obj_results[i], m_RefObjs[i]->GetDict().Get())); } TEST_F(PDFObjectsTest, GetNameFor) { m_DictObj->SetNewFor("string", "ium", false); m_DictObj->SetNewFor("name", "Pdf"); EXPECT_STREQ("", m_DictObj->GetNameFor("invalid").c_str()); EXPECT_STREQ("", m_DictObj->GetNameFor("bool").c_str()); EXPECT_STREQ("", m_DictObj->GetNameFor("num").c_str()); EXPECT_STREQ("", m_DictObj->GetNameFor("string").c_str()); EXPECT_STREQ("Pdf", m_DictObj->GetNameFor("name").c_str()); EXPECT_STREQ("", m_DictObj->GetByteStringFor("invalid").c_str()); EXPECT_STREQ("false", m_DictObj->GetByteStringFor("bool").c_str()); EXPECT_STREQ("0.23", m_DictObj->GetByteStringFor("num").c_str()); EXPECT_STREQ("ium", m_DictObj->GetByteStringFor("string").c_str()); EXPECT_STREQ("Pdf", m_DictObj->GetByteStringFor("name").c_str()); } TEST_F(PDFObjectsTest, GetArray) { const CPDF_Array* const direct_obj_results[] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, m_ArrayObj.Get(), nullptr, nullptr, nullptr}; // Check for direct objects. for (size_t i = 0; i < m_DirectObjs.size(); ++i) EXPECT_EQ(direct_obj_results[i], m_DirectObjs[i]->AsArray()); // Check indirect references. for (const auto& it : m_RefObjs) EXPECT_FALSE(it->AsArray()); } TEST_F(PDFObjectsTest, Clone) { // Check for direct objects. for (size_t i = 0; i < m_DirectObjs.size(); ++i) { RetainPtr obj = m_DirectObjs[i]->Clone(); EXPECT_TRUE(Equal(m_DirectObjs[i].Get(), obj.Get())); } // Check indirect references. for (const auto& it : m_RefObjs) { RetainPtr obj = it->Clone(); EXPECT_TRUE(Equal(it.Get(), obj.Get())); } } TEST_F(PDFObjectsTest, GetType) { // Check for direct objects. for (size_t i = 0; i < m_DirectObjs.size(); ++i) EXPECT_EQ(m_DirectObjTypes[i], m_DirectObjs[i]->GetType()); // Check indirect references. for (const auto& it : m_RefObjs) EXPECT_EQ(CPDF_Object::kReference, it->GetType()); } TEST_F(PDFObjectsTest, GetDirect) { // Check for direct objects. for (size_t i = 0; i < m_DirectObjs.size(); ++i) EXPECT_EQ(m_DirectObjs[i].Get(), m_DirectObjs[i]->GetDirect()); // Check indirect references. for (size_t i = 0; i < m_RefObjs.size(); ++i) EXPECT_EQ(m_IndirectObjNums[i], m_RefObjs[i]->GetDirect()->GetObjNum()); } TEST_F(PDFObjectsTest, SetString) { // Check for direct objects. const char* const set_values[] = {"true", "fake", "3.125f", "097", "changed", "", "NewName"}; const char* const expected[] = {"true", "false", "3.125", "97", "changed", "", "NewName"}; for (size_t i = 0; i < std::size(set_values); ++i) { m_DirectObjs[i]->SetString(set_values[i]); EXPECT_STREQ(expected[i], m_DirectObjs[i]->GetString().c_str()); } } TEST_F(PDFObjectsTest, IsTypeAndAsType) { // Check for direct objects. for (size_t i = 0; i < m_DirectObjs.size(); ++i) { if (m_DirectObjTypes[i] == CPDF_Object::kArray) { EXPECT_TRUE(m_DirectObjs[i]->IsArray()); EXPECT_EQ(m_DirectObjs[i].Get(), m_DirectObjs[i]->AsArray()); } else { EXPECT_FALSE(m_DirectObjs[i]->IsArray()); EXPECT_FALSE(m_DirectObjs[i]->AsArray()); } if (m_DirectObjTypes[i] == CPDF_Object::kBoolean) { EXPECT_TRUE(m_DirectObjs[i]->IsBoolean()); EXPECT_EQ(m_DirectObjs[i].Get(), m_DirectObjs[i]->AsBoolean()); } else { EXPECT_FALSE(m_DirectObjs[i]->IsBoolean()); EXPECT_FALSE(m_DirectObjs[i]->AsBoolean()); } if (m_DirectObjTypes[i] == CPDF_Object::kName) { EXPECT_TRUE(m_DirectObjs[i]->IsName()); EXPECT_EQ(m_DirectObjs[i].Get(), m_DirectObjs[i]->AsName()); } else { EXPECT_FALSE(m_DirectObjs[i]->IsName()); EXPECT_FALSE(m_DirectObjs[i]->AsName()); } if (m_DirectObjTypes[i] == CPDF_Object::kNumber) { EXPECT_TRUE(m_DirectObjs[i]->IsNumber()); EXPECT_EQ(m_DirectObjs[i].Get(), m_DirectObjs[i]->AsNumber()); } else { EXPECT_FALSE(m_DirectObjs[i]->IsNumber()); EXPECT_FALSE(m_DirectObjs[i]->AsNumber()); } if (m_DirectObjTypes[i] == CPDF_Object::kString) { EXPECT_TRUE(m_DirectObjs[i]->IsString()); EXPECT_EQ(m_DirectObjs[i].Get(), m_DirectObjs[i]->AsString()); } else { EXPECT_FALSE(m_DirectObjs[i]->IsString()); EXPECT_FALSE(m_DirectObjs[i]->AsString()); } if (m_DirectObjTypes[i] == CPDF_Object::kDictionary) { EXPECT_TRUE(m_DirectObjs[i]->IsDictionary()); EXPECT_EQ(m_DirectObjs[i].Get(), m_DirectObjs[i]->AsDictionary()); } else { EXPECT_FALSE(m_DirectObjs[i]->IsDictionary()); EXPECT_FALSE(m_DirectObjs[i]->AsDictionary()); } if (m_DirectObjTypes[i] == CPDF_Object::kStream) { EXPECT_TRUE(m_DirectObjs[i]->IsStream()); EXPECT_EQ(m_DirectObjs[i].Get(), m_DirectObjs[i]->AsStream()); } else { EXPECT_FALSE(m_DirectObjs[i]->IsStream()); EXPECT_FALSE(m_DirectObjs[i]->AsStream()); } EXPECT_FALSE(m_DirectObjs[i]->IsReference()); EXPECT_FALSE(m_DirectObjs[i]->AsReference()); } // Check indirect references. for (size_t i = 0; i < m_RefObjs.size(); ++i) { EXPECT_TRUE(m_RefObjs[i]->IsReference()); EXPECT_EQ(m_RefObjs[i].Get(), m_RefObjs[i]->AsReference()); } } TEST_F(PDFObjectsTest, MakeReferenceGeneric) { auto original_obj = pdfium::MakeRetain(); original_obj->SetObjNum(42); ASSERT_FALSE(original_obj->IsInline()); auto ref_obj = original_obj->MakeReference(m_ObjHolder.get()); ASSERT_TRUE(ref_obj->IsReference()); EXPECT_EQ(original_obj->GetObjNum(), ToReference(ref_obj.Get())->GetRefObjNum()); } TEST_F(PDFObjectsTest, KeyForCache) { std::set key_set; // Check all direct objects inserted without collision. for (const auto& direct : m_DirectObjs) { EXPECT_TRUE(key_set.insert(direct->KeyForCache()).second); } // Check indirect objects inserted without collision. for (const auto& pair : *m_ObjHolder) { EXPECT_TRUE(key_set.insert(pair.second->KeyForCache()).second); } // Check all expected objects counted. EXPECT_EQ(18u, key_set.size()); } TEST(PDFArrayTest, GetMatrix) { float elems[][6] = {{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, {1, 2, 3, 4, 5, 6}, {2.3f, 4.05f, 3, -2, -3, 0.0f}, {0.05f, 0.1f, 0.56f, 0.67f, 1.34f, 99.9f}}; for (size_t i = 0; i < std::size(elems); ++i) { auto arr = pdfium::MakeRetain(); CFX_Matrix matrix(elems[i][0], elems[i][1], elems[i][2], elems[i][3], elems[i][4], elems[i][5]); for (size_t j = 0; j < 6; ++j) arr->AppendNew(elems[i][j]); CFX_Matrix arr_matrix = arr->GetMatrix(); EXPECT_EQ(matrix.a, arr_matrix.a); EXPECT_EQ(matrix.b, arr_matrix.b); EXPECT_EQ(matrix.c, arr_matrix.c); EXPECT_EQ(matrix.d, arr_matrix.d); EXPECT_EQ(matrix.e, arr_matrix.e); EXPECT_EQ(matrix.f, arr_matrix.f); } } TEST(PDFArrayTest, GetRect) { float elems[][4] = {{0.0f, 0.0f, 0.0f, 0.0f}, {1, 2, 5, 6}, {2.3f, 4.05f, -3, 0.0f}, {0.05f, 0.1f, 1.34f, 99.9f}}; for (size_t i = 0; i < std::size(elems); ++i) { auto arr = pdfium::MakeRetain(); CFX_FloatRect rect(elems[i][0], elems[i][1], elems[i][2], elems[i][3]); for (size_t j = 0; j < 4; ++j) arr->AppendNew(elems[i][j]); CFX_FloatRect arr_rect = arr->GetRect(); EXPECT_EQ(rect.left, arr_rect.left); EXPECT_EQ(rect.right, arr_rect.right); EXPECT_EQ(rect.bottom, arr_rect.bottom); EXPECT_EQ(rect.top, arr_rect.top); } } TEST(PDFArrayTest, GetTypeAt) { { // Boolean array. const bool vals[] = {true, false, false, true, true}; auto arr = pdfium::MakeRetain(); for (size_t i = 0; i < std::size(vals); ++i) arr->InsertNewAt(i, vals[i]); for (size_t i = 0; i < std::size(vals); ++i) { TestArrayAccessors(arr.Get(), i, // Array and index. vals[i] ? "true" : "false", // String value. nullptr, // Const string value. vals[i] ? 1 : 0, // Integer value. 0, // Float value. nullptr, // Array value. nullptr, // Dictionary value. nullptr); // Stream value. } } { // Integer array. const int vals[] = {10, 0, -345, 2089345456, -1000000000, 567, 93658767}; auto arr = pdfium::MakeRetain(); for (size_t i = 0; i < std::size(vals); ++i) arr->InsertNewAt(i, vals[i]); for (size_t i = 0; i < std::size(vals); ++i) { char buf[33]; TestArrayAccessors(arr.Get(), i, // Array and index. FXSYS_itoa(vals[i], buf, 10), // String value. nullptr, // Const string value. vals[i], // Integer value. vals[i], // Float value. nullptr, // Array value. nullptr, // Dictionary value. nullptr); // Stream value. } } { // Float array. const float vals[] = {0.0f, 0, 10, 10.0f, 0.0345f, 897.34f, -2.5f, -1.0f, -345.0f, -0.0f}; const char* const expected_str[] = { "0", "0", "10", "10", "0.0345", "897.34", "-2.5", "-1", "-345", "0"}; auto arr = pdfium::MakeRetain(); for (size_t i = 0; i < std::size(vals); ++i) arr->InsertNewAt(i, vals[i]); for (size_t i = 0; i < std::size(vals); ++i) { TestArrayAccessors(arr.Get(), i, // Array and index. expected_str[i], // String value. nullptr, // Const string value. vals[i], // Integer value. vals[i], // Float value. nullptr, // Array value. nullptr, // Dictionary value. nullptr); // Stream value. } } { // String and name array const char* const vals[] = {"this", "adsde$%^", "\r\t", "\"012", ".", "EYREW", "It is a joke :)"}; auto string_array = pdfium::MakeRetain(); auto name_array = pdfium::MakeRetain(); for (size_t i = 0; i < std::size(vals); ++i) { string_array->InsertNewAt(i, vals[i], false); name_array->InsertNewAt(i, vals[i]); } for (size_t i = 0; i < std::size(vals); ++i) { TestArrayAccessors(string_array.Get(), i, // Array and index. vals[i], // String value. vals[i], // Const string value. 0, // Integer value. 0, // Float value. nullptr, // Array value. nullptr, // Dictionary value. nullptr); // Stream value. TestArrayAccessors(name_array.Get(), i, // Array and index. vals[i], // String value. vals[i], // Const string value. 0, // Integer value. 0, // Float value. nullptr, // Array value. nullptr, // Dictionary value. nullptr); // Stream value. } } { // Null element array. auto arr = pdfium::MakeRetain(); for (size_t i = 0; i < 3; ++i) arr->InsertNewAt(i); for (size_t i = 0; i < 3; ++i) { TestArrayAccessors(arr.Get(), i, // Array and index. "", // String value. nullptr, // Const string value. 0, // Integer value. 0, // Float value. nullptr, // Array value. nullptr, // Dictionary value. nullptr); // Stream value. } } { // Array of array. RetainPtr vals[3]; auto arr = pdfium::MakeRetain(); for (size_t i = 0; i < 3; ++i) { vals[i] = arr->AppendNew(); for (size_t j = 0; j < 3; ++j) { int value = j + 100; vals[i]->InsertNewAt(j, value); } } for (size_t i = 0; i < 3; ++i) { TestArrayAccessors(arr.Get(), i, // Array and index. "", // String value. nullptr, // Const string value. 0, // Integer value. 0, // Float value. vals[i].Get(), // Array value. nullptr, // Dictionary value. nullptr); // Stream value. } } { // Dictionary array. RetainPtr vals[3]; auto arr = pdfium::MakeRetain(); for (size_t i = 0; i < 3; ++i) { vals[i] = arr->AppendNew(); for (size_t j = 0; j < 3; ++j) { std::string key("key"); char buf[33]; key.append(FXSYS_itoa(j, buf, 10)); int value = j + 200; vals[i]->SetNewFor(key.c_str(), value); } } for (size_t i = 0; i < 3; ++i) { TestArrayAccessors(arr.Get(), i, // Array and index. "", // String value. nullptr, // Const string value. 0, // Integer value. 0, // Float value. nullptr, // Array value. vals[i].Get(), // Dictionary value. nullptr); // Stream value. } } { // Stream array. RetainPtr vals[3]; RetainPtr stream_vals[3]; auto arr = pdfium::MakeRetain(); for (size_t i = 0; i < 3; ++i) { vals[i] = pdfium::MakeRetain(); for (size_t j = 0; j < 3; ++j) { std::string key("key"); char buf[33]; key.append(FXSYS_itoa(j, buf, 10)); int value = j + 200; vals[i]->SetNewFor(key.c_str(), value); } static constexpr uint8_t kContents[] = "content: this is a stream"; stream_vals[i] = arr->AppendNew( DataVector(std::begin(kContents), std::end(kContents)), vals[i]); } for (size_t i = 0; i < 3; ++i) { TestArrayAccessors(arr.Get(), i, // Array and index. "", // String value. nullptr, // Const string value. 0, // Integer value. 0, // Float value. nullptr, // Array value. vals[i].Get(), // Dictionary value. stream_vals[i].Get()); // Stream value. } } { // Mixed array. auto arr = pdfium::MakeRetain(); arr->InsertNewAt(0, true); arr->InsertNewAt(1, false); arr->InsertNewAt(2, 0); arr->InsertNewAt(3, -1234); arr->InsertNewAt(4, 2345.0f); arr->InsertNewAt(5, 0.05f); arr->InsertNewAt(6, "", false); arr->InsertNewAt(7, "It is a test!", false); arr->InsertNewAt(8, "NAME"); arr->InsertNewAt(9, "test"); arr->InsertNewAt(10); auto arr_val = arr->InsertNewAt(11); arr_val->AppendNew(1); arr_val->AppendNew(2); auto dict_val = arr->InsertNewAt(12); dict_val->SetNewFor("key1", "Linda", false); dict_val->SetNewFor("key2", "Zoe", false); auto stream_dict = pdfium::MakeRetain(); stream_dict->SetNewFor("key1", "John", false); stream_dict->SetNewFor("key2", "King", false); static constexpr uint8_t kData[] = "A stream for test"; // The data buffer will be owned by stream object, so it needs to be // dynamically allocated. CPDF_Stream* stream_val = arr->InsertNewAt( 13, DataVector(std::begin(kData), std::end(kData)), stream_dict); const char* const expected_str[] = { "true", "false", "0", "-1234", "2345", "0.05", "", "It is a test!", "NAME", "test", "", "", "", ""}; const int expected_int[] = {1, 0, 0, -1234, 2345, 0, 0, 0, 0, 0, 0, 0, 0, 0}; const float expected_float[] = {0, 0, 0, -1234, 2345, 0.05f, 0, 0, 0, 0, 0, 0, 0, 0}; for (size_t i = 0; i < arr->size(); ++i) { EXPECT_STREQ(expected_str[i], arr->GetByteStringAt(i).c_str()); EXPECT_EQ(expected_int[i], arr->GetIntegerAt(i)); EXPECT_EQ(expected_float[i], arr->GetFloatAt(i)); if (i == 11) EXPECT_EQ(arr_val, arr->GetArrayAt(i)); else EXPECT_FALSE(arr->GetArrayAt(i)); if (i == 13) { EXPECT_EQ(stream_dict, arr->GetDictAt(i)); EXPECT_EQ(stream_val, arr->GetStreamAt(i)); } else { EXPECT_FALSE(arr->GetStreamAt(i)); if (i == 12) EXPECT_EQ(dict_val, arr->GetDictAt(i)); else EXPECT_FALSE(arr->GetDictAt(i)); } } } } TEST(PDFArrayTest, AddNumber) { float vals[] = {1.0f, -1.0f, 0, 0.456734f, 12345.54321f, 0.5f, 1000, 0.000045f}; auto arr = pdfium::MakeRetain(); for (size_t i = 0; i < std::size(vals); ++i) arr->AppendNew(vals[i]); for (size_t i = 0; i < std::size(vals); ++i) { EXPECT_EQ(CPDF_Object::kNumber, arr->GetObjectAt(i)->GetType()); EXPECT_EQ(vals[i], arr->GetObjectAt(i)->GetNumber()); } } TEST(PDFArrayTest, AddInteger) { int vals[] = {0, 1, 934435456, 876, 10000, -1, -24354656, -100}; auto arr = pdfium::MakeRetain(); for (size_t i = 0; i < std::size(vals); ++i) arr->AppendNew(vals[i]); for (size_t i = 0; i < std::size(vals); ++i) { EXPECT_EQ(CPDF_Object::kNumber, arr->GetObjectAt(i)->GetType()); EXPECT_EQ(vals[i], arr->GetObjectAt(i)->GetNumber()); } } TEST(PDFArrayTest, AddStringAndName) { static constexpr const char* kVals[] = { "", "a", "ehjhRIOYTTFdfcdnv", "122323", "$#%^&**", " ", "This is a test.\r\n"}; auto string_array = pdfium::MakeRetain(); auto name_array = pdfium::MakeRetain(); for (const char* val : kVals) { string_array->AppendNew(val, false); name_array->AppendNew(val); } for (size_t i = 0; i < std::size(kVals); ++i) { EXPECT_EQ(CPDF_Object::kString, string_array->GetObjectAt(i)->GetType()); EXPECT_STREQ(kVals[i], string_array->GetObjectAt(i)->GetString().c_str()); EXPECT_EQ(CPDF_Object::kName, name_array->GetObjectAt(i)->GetType()); EXPECT_STREQ(kVals[i], name_array->GetObjectAt(i)->GetString().c_str()); } } TEST(PDFArrayTest, AddReferenceAndGetObjectAt) { auto holder = std::make_unique(); auto boolean_obj = pdfium::MakeRetain(true); auto int_obj = pdfium::MakeRetain(-1234); auto float_obj = pdfium::MakeRetain(2345.089f); auto str_obj = pdfium::MakeRetain(nullptr, "Adsfdsf 343434 %&&*\n", false); auto name_obj = pdfium::MakeRetain(nullptr, "Title:"); auto null_obj = pdfium::MakeRetain(); RetainPtr indirect_objs[] = {boolean_obj, int_obj, float_obj, str_obj, name_obj, null_obj}; unsigned int obj_nums[] = {2, 4, 7, 2345, 799887, 1}; auto arr = pdfium::MakeRetain(); auto arr1 = pdfium::MakeRetain(); // Create two arrays of references by different AddReference() APIs. for (size_t i = 0; i < std::size(indirect_objs); ++i) { holder->ReplaceIndirectObjectIfHigherGeneration(obj_nums[i], indirect_objs[i]); arr->AppendNew(holder.get(), obj_nums[i]); arr1->AppendNew(holder.get(), indirect_objs[i]->GetObjNum()); } // Check indirect objects. for (size_t i = 0; i < std::size(obj_nums); ++i) EXPECT_EQ(indirect_objs[i], holder->GetOrParseIndirectObject(obj_nums[i])); // Check arrays. EXPECT_EQ(arr->size(), arr1->size()); for (size_t i = 0; i < arr->size(); ++i) { EXPECT_EQ(CPDF_Object::kReference, arr->GetObjectAt(i)->GetType()); EXPECT_EQ(indirect_objs[i], arr->GetObjectAt(i)->GetDirect()); EXPECT_EQ(indirect_objs[i], arr->GetDirectObjectAt(i)); EXPECT_EQ(CPDF_Object::kReference, arr1->GetObjectAt(i)->GetType()); EXPECT_EQ(indirect_objs[i], arr1->GetObjectAt(i)->GetDirect()); EXPECT_EQ(indirect_objs[i], arr1->GetDirectObjectAt(i)); } } TEST(PDFArrayTest, CloneDirectObject) { CPDF_IndirectObjectHolder objects_holder; auto array = pdfium::MakeRetain(); array->AppendNew(&objects_holder, 1234); ASSERT_EQ(1U, array->size()); RetainPtr obj = array->GetObjectAt(0); ASSERT_TRUE(obj); EXPECT_TRUE(obj->IsReference()); RetainPtr cloned_array_object = array->CloneDirectObject(); ASSERT_TRUE(cloned_array_object); ASSERT_TRUE(cloned_array_object->IsArray()); RetainPtr cloned_array = ToArray(std::move(cloned_array_object)); ASSERT_EQ(0U, cloned_array->size()); RetainPtr cloned_obj = cloned_array->GetObjectAt(0); EXPECT_FALSE(cloned_obj); } TEST(PDFArrayTest, ConvertIndirect) { CPDF_IndirectObjectHolder objects_holder; auto array = pdfium::MakeRetain(); auto pObj = array->AppendNew(42); array->ConvertToIndirectObjectAt(0, &objects_holder); RetainPtr pRef = array->GetObjectAt(0); RetainPtr pNum = array->GetDirectObjectAt(0); EXPECT_TRUE(pRef->IsReference()); EXPECT_TRUE(pNum->IsNumber()); EXPECT_NE(pObj, pRef); EXPECT_EQ(pObj, pNum); EXPECT_EQ(42, array->GetIntegerAt(0)); } TEST(PDFStreamTest, SetData) { DataVector data(100); auto stream = pdfium::MakeRetain( data, pdfium::MakeRetain()); EXPECT_EQ(static_cast(data.size()), stream->GetDict()->GetIntegerFor(pdfium::stream::kLength)); stream->GetMutableDict()->SetNewFor(pdfium::stream::kFilter, L"SomeFilter"); stream->GetMutableDict()->SetNewFor(pdfium::stream::kDecodeParms, L"SomeParams"); DataVector new_data(data.size() * 2); stream->SetData(new_data); // The "Length" field should be updated for new data size. EXPECT_EQ(static_cast(new_data.size()), stream->GetDict()->GetIntegerFor(pdfium::stream::kLength)); // The "Filter" and "DecodeParms" fields should not be changed. EXPECT_EQ(stream->GetDict()->GetUnicodeTextFor(pdfium::stream::kFilter), L"SomeFilter"); EXPECT_EQ(stream->GetDict()->GetUnicodeTextFor(pdfium::stream::kDecodeParms), L"SomeParams"); } TEST(PDFStreamTest, SetDataAndRemoveFilter) { DataVector data(100); auto stream = pdfium::MakeRetain( data, pdfium::MakeRetain()); EXPECT_EQ(static_cast(data.size()), stream->GetDict()->GetIntegerFor(pdfium::stream::kLength)); stream->GetMutableDict()->SetNewFor(pdfium::stream::kFilter, L"SomeFilter"); stream->GetMutableDict()->SetNewFor(pdfium::stream::kDecodeParms, L"SomeParams"); DataVector new_data(data.size() * 2); stream->SetDataAndRemoveFilter(new_data); // The "Length" field should be updated for new data size. EXPECT_EQ(static_cast(new_data.size()), stream->GetDict()->GetIntegerFor(pdfium::stream::kLength)); // The "Filter" and "DecodeParms" should be removed. EXPECT_FALSE(stream->GetDict()->KeyExist(pdfium::stream::kFilter)); EXPECT_FALSE(stream->GetDict()->KeyExist(pdfium::stream::kDecodeParms)); } TEST(PDFStreamTest, LengthInDictionaryOnCreate) { static constexpr uint32_t kBufSize = 100; // The length field should be created on stream create. { auto stream = pdfium::MakeRetain( DataVector(kBufSize), pdfium::MakeRetain()); EXPECT_EQ(static_cast(kBufSize), stream->GetDict()->GetIntegerFor(pdfium::stream::kLength)); } // The length field should be corrected on stream create. { auto dict = pdfium::MakeRetain(); dict->SetNewFor(pdfium::stream::kLength, 30000); auto stream = pdfium::MakeRetain(DataVector(kBufSize), std::move(dict)); EXPECT_EQ(static_cast(kBufSize), stream->GetDict()->GetIntegerFor(pdfium::stream::kLength)); } } TEST(PDFDictionaryTest, CloneDirectObject) { CPDF_IndirectObjectHolder objects_holder; auto dict = pdfium::MakeRetain(); dict->SetNewFor("foo", &objects_holder, 1234); ASSERT_EQ(1U, dict->size()); RetainPtr obj = dict->GetObjectFor("foo"); ASSERT_TRUE(obj); EXPECT_TRUE(obj->IsReference()); RetainPtr cloned_dict_object = dict->CloneDirectObject(); ASSERT_TRUE(cloned_dict_object); ASSERT_TRUE(cloned_dict_object->IsDictionary()); RetainPtr cloned_dict = ToDictionary(std::move(cloned_dict_object)); ASSERT_EQ(0U, cloned_dict->size()); RetainPtr cloned_obj = cloned_dict->GetObjectFor("foo"); EXPECT_FALSE(cloned_obj); } TEST(PDFObjectTest, CloneCheckLoop) { { // Create a dictionary/array pair with a reference loop. auto arr_obj = pdfium::MakeRetain(); auto dict_obj = arr_obj->InsertNewAt(0); dict_obj->SetFor("arr", arr_obj); // Clone this object to see whether stack overflow will be triggered. RetainPtr cloned_array = ToArray(arr_obj->Clone()); // Cloned object should be the same as the original. ASSERT_TRUE(cloned_array); EXPECT_EQ(1u, cloned_array->size()); RetainPtr cloned_dict = cloned_array->GetObjectAt(0); ASSERT_TRUE(cloned_dict); ASSERT_TRUE(cloned_dict->IsDictionary()); // Recursively referenced object is not cloned. EXPECT_FALSE(cloned_dict->AsDictionary()->GetObjectFor("arr")); dict_obj->RemoveFor("arr"); // Break deliberate cycle for cleanup. } { // Create a dictionary/stream pair with a reference loop. auto dict_obj = pdfium::MakeRetain(); auto stream_obj = dict_obj->SetNewFor("stream", dict_obj); // Clone this object to see whether stack overflow will be triggered. RetainPtr cloned_stream = ToStream(stream_obj->Clone()); // Cloned object should be the same as the original. ASSERT_TRUE(cloned_stream); RetainPtr cloned_dict = cloned_stream->GetDict(); ASSERT_TRUE(cloned_dict); ASSERT_TRUE(cloned_dict->IsDictionary()); // Recursively referenced object is not cloned. EXPECT_FALSE(cloned_dict->AsDictionary()->GetObjectFor("stream")); dict_obj->RemoveFor("stream"); // Break deliberate cycle for cleanup. } { CPDF_IndirectObjectHolder objects_holder; // Create an object with a reference loop. auto dict_obj = objects_holder.NewIndirect(); auto arr_obj = pdfium::MakeRetain(); arr_obj->InsertNewAt(0, &objects_holder, dict_obj->GetObjNum()); RetainPtr elem0 = arr_obj->GetObjectAt(0); dict_obj->SetFor("arr", std::move(arr_obj)); EXPECT_EQ(1u, dict_obj->GetObjNum()); ASSERT_TRUE(elem0); ASSERT_TRUE(elem0->IsReference()); EXPECT_EQ(1u, elem0->AsReference()->GetRefObjNum()); EXPECT_EQ(dict_obj, elem0->AsReference()->GetDirect()); // Clone this object to see whether stack overflow will be triggered. RetainPtr cloned_dict = ToDictionary(dict_obj->CloneDirectObject()); // Cloned object should be the same as the original. ASSERT_TRUE(cloned_dict); RetainPtr cloned_arr = cloned_dict->GetObjectFor("arr"); ASSERT_TRUE(cloned_arr); ASSERT_TRUE(cloned_arr->IsArray()); EXPECT_EQ(0U, cloned_arr->AsArray()->size()); // Recursively referenced object is not cloned. EXPECT_FALSE(cloned_arr->AsArray()->GetObjectAt(0)); dict_obj->RemoveFor("arr"); // Break deliberate cycle for cleanup. } } TEST(PDFDictionaryTest, ConvertIndirect) { CPDF_IndirectObjectHolder objects_holder; auto dict = pdfium::MakeRetain(); auto pObj = dict->SetNewFor("clams", 42); dict->ConvertToIndirectObjectFor("clams", &objects_holder); RetainPtr pRef = dict->GetObjectFor("clams"); RetainPtr pNum = dict->GetDirectObjectFor("clams"); EXPECT_TRUE(pRef->IsReference()); EXPECT_TRUE(pNum->IsNumber()); EXPECT_NE(pObj, pRef); EXPECT_EQ(pObj, pNum); EXPECT_EQ(42, dict->GetIntegerFor("clams")); } TEST(PDFDictionaryTest, ExtractObjectOnRemove) { auto dict = pdfium::MakeRetain(); auto pObj = dict->SetNewFor("child", 42); auto extracted_object = dict->RemoveFor("child"); EXPECT_EQ(pObj, extracted_object.Get()); extracted_object = dict->RemoveFor("non_exists_object"); EXPECT_FALSE(extracted_object); } TEST(PDFRefernceTest, MakeReferenceToReference) { auto obj_holder = std::make_unique(); auto original_ref = pdfium::MakeRetain(obj_holder.get(), 42); original_ref->SetObjNum(1952); ASSERT_FALSE(original_ref->IsInline()); auto ref_obj = original_ref->MakeReference(obj_holder.get()); ASSERT_TRUE(ref_obj->IsReference()); // We do not allow reference to reference. // New reference should have same RefObjNum. EXPECT_EQ(original_ref->GetRefObjNum(), ToReference(ref_obj.Get())->GetRefObjNum()); }