| // Copyright 2017 PDFium Authors. All rights reserved. |
| // 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_walker.h" |
| |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| |
| #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_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_string.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/base/ptr_util.h" |
| |
| namespace { |
| |
| std::string Walk(CPDF_Object* object) { |
| std::ostringstream result; |
| CPDF_ObjectWalker walker(object); |
| while (const CPDF_Object* obj = walker.GetNext()) { |
| if (obj->IsDictionary()) |
| result << " Dict"; |
| else if (obj->IsArray()) |
| result << " Arr"; |
| else if (obj->IsString()) |
| result << " Str"; |
| else if (obj->IsBoolean()) |
| result << " Bool"; |
| else if (obj->IsStream()) |
| result << " Stream"; |
| else if (obj->IsReference()) |
| result << " Ref"; |
| else if (obj->IsNumber()) |
| result << " Num"; |
| else if (obj->IsNull()) |
| result << " Null"; |
| else |
| result << " Unknown"; |
| } |
| std::string result_str = result.str(); |
| if (!result_str.empty()) { |
| result_str.erase(result_str.begin()); // remove start space |
| } |
| return result_str; |
| } |
| |
| } // namespace |
| |
| TEST(CPDF_ObjectWalkerTest, Simple) { |
| EXPECT_EQ(Walk(pdfium::MakeUnique<CPDF_Null>().get()), "Null"); |
| EXPECT_EQ(Walk(pdfium::MakeUnique<CPDF_Dictionary>().get()), "Dict"); |
| EXPECT_EQ(Walk(pdfium::MakeUnique<CPDF_Array>().get()), "Arr"); |
| EXPECT_EQ(Walk(pdfium::MakeUnique<CPDF_String>().get()), "Str"); |
| EXPECT_EQ(Walk(pdfium::MakeUnique<CPDF_Boolean>().get()), "Bool"); |
| EXPECT_EQ(Walk(pdfium::MakeUnique<CPDF_Stream>().get()), "Stream"); |
| EXPECT_EQ(Walk(pdfium::MakeUnique<CPDF_Reference>(nullptr, 0).get()), "Ref"); |
| } |
| |
| TEST(CPDF_ObjectWalkerTest, CombinedObject) { |
| auto dict = pdfium::MakeUnique<CPDF_Dictionary>(); |
| dict->SetFor("1", pdfium::MakeUnique<CPDF_String>()); |
| dict->SetFor("2", pdfium::MakeUnique<CPDF_Boolean>()); |
| auto array = pdfium::MakeUnique<CPDF_Array>(); |
| array->Add(pdfium::MakeUnique<CPDF_Reference>(nullptr, 0)); |
| array->Add(pdfium::MakeUnique<CPDF_Null>()); |
| array->Add(pdfium::MakeUnique<CPDF_Stream>( |
| nullptr, 0, pdfium::MakeUnique<CPDF_Dictionary>())); |
| dict->SetFor("3", std::move(array)); |
| // The last number for stream length. |
| EXPECT_EQ(Walk(dict.get()), "Dict Str Bool Arr Ref Null Stream Dict Num"); |
| } |
| |
| TEST(CPDF_ObjectWalkerTest, GetParent) { |
| auto level_4 = pdfium::MakeUnique<CPDF_Number>(0); |
| auto level_3 = pdfium::MakeUnique<CPDF_Dictionary>(); |
| level_3->SetFor("Length", std::move(level_4)); |
| auto level_2 = |
| pdfium::MakeUnique<CPDF_Stream>(nullptr, 0, std::move(level_3)); |
| auto level_1 = pdfium::MakeUnique<CPDF_Array>(); |
| level_1->Add(std::move(level_2)); |
| auto level_0 = pdfium::MakeUnique<CPDF_Dictionary>(); |
| level_0->SetFor("Array", std::move(level_1)); |
| |
| // We have <</Array [ stream( << /Length 0 >>) ]>> |
| // In this case each step will increase depth. |
| // And on each step the prev object should be parent for current. |
| const CPDF_Object* cur_parent = nullptr; |
| CPDF_ObjectWalker walker(level_0.get()); |
| while (const CPDF_Object* obj = walker.GetNext()) { |
| EXPECT_EQ(cur_parent, walker.GetParent()); |
| cur_parent = obj; |
| } |
| } |
| |
| TEST(CPDF_ObjectWalkerTest, SkipWalkIntoCurrentObject) { |
| auto root_array = pdfium::MakeUnique<CPDF_Array>(); |
| // Add 2 null objects into |root_array|. [ null1, null2 ] |
| root_array->AddNew<CPDF_Null>(); |
| root_array->AddNew<CPDF_Null>(); |
| // |root_array| will contain 4 null objects after this. |
| // [ null1, null2, [ null3, null4 ] ] |
| root_array->Add(root_array->Clone()); |
| |
| int non_array_objects = 0; |
| CPDF_ObjectWalker walker(root_array.get()); |
| while (const CPDF_Object* obj = walker.GetNext()) { |
| if (obj != root_array.get() && obj->IsArray()) { |
| // skip other array except root. |
| walker.SkipWalkIntoCurrentObject(); |
| } |
| if (!obj->IsArray()) |
| ++non_array_objects; |
| } |
| // 2 objects from child array should be skipped. |
| EXPECT_EQ(2, non_array_objects); |
| } |
| |
| TEST(CPDF_ObjectWalkerTest, DictionaryKey) { |
| auto dict = pdfium::MakeUnique<CPDF_Dictionary>(); |
| dict->SetFor("1", pdfium::MakeUnique<CPDF_Null>()); |
| dict->SetFor("2", pdfium::MakeUnique<CPDF_Null>()); |
| dict->SetFor("3", pdfium::MakeUnique<CPDF_Null>()); |
| dict->SetFor("4", pdfium::MakeUnique<CPDF_Null>()); |
| dict->SetFor("5", pdfium::MakeUnique<CPDF_Null>()); |
| |
| CPDF_ObjectWalker walker(dict.get()); |
| while (const CPDF_Object* obj = walker.GetNext()) { |
| if (obj == dict.get()) { |
| // Ignore root dictinary object |
| continue; |
| } |
| // Test that, dictionary key is correct. |
| EXPECT_EQ(walker.GetParent()->AsDictionary()->GetObjectFor( |
| walker.dictionary_key()), |
| obj); |
| } |
| } |