// 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_avail.h" #include #include #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_indirect_object_holder.h" #include "core/fpdfapi/parser/cpdf_read_validator.h" #include "core/fpdfapi/parser/cpdf_reference.h" #include "core/fpdfapi/parser/cpdf_string.h" #include "core/fxcrt/fx_stream.h" #include "testing/fx_string_testhelpers.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/base/ptr_util.h" namespace { class TestReadValidator : public CPDF_ReadValidator { public: template friend RetainPtr pdfium::MakeRetain(Args&&... args); void SimulateReadError() { ReadBlock(nullptr, 0, 1); } protected: TestReadValidator() : CPDF_ReadValidator( pdfium::MakeRetain(100), nullptr) {} ~TestReadValidator() override {} }; class TestHolder : public CPDF_IndirectObjectHolder { public: enum class ObjectState { Unavailable, Available, }; TestHolder() : validator_(pdfium::MakeRetain()) {} ~TestHolder() override {} // CPDF_IndirectObjectHolder overrides: CPDF_Object* GetOrParseIndirectObject(uint32_t objnum) override { auto it = objects_data_.find(objnum); if (it == objects_data_.end()) return nullptr; ObjectData& obj_data = it->second; if (obj_data.state == ObjectState::Unavailable) { validator_->SimulateReadError(); return nullptr; } return obj_data.object.get(); } RetainPtr GetValidator() { return validator_; } void AddObject(uint32_t objnum, std::unique_ptr object, ObjectState state) { ObjectData object_data; object_data.object = std::move(object); object_data.state = state; ASSERT(objects_data_.find(objnum) == objects_data_.end()); objects_data_[objnum] = std::move(object_data); } void SetObjectState(uint32_t objnum, ObjectState state) { auto it = objects_data_.find(objnum); ASSERT(it != objects_data_.end()); ObjectData& obj_data = it->second; obj_data.state = state; } CPDF_Object* GetTestObject(uint32_t objnum) { auto it = objects_data_.find(objnum); if (it == objects_data_.end()) return nullptr; return it->second.object.get(); } private: struct ObjectData { std::unique_ptr object; ObjectState state = ObjectState::Unavailable; }; std::map objects_data_; RetainPtr validator_; }; class CPDF_ObjectAvailFailOnExclude : public CPDF_ObjectAvail { public: using CPDF_ObjectAvail::CPDF_ObjectAvail; ~CPDF_ObjectAvailFailOnExclude() override {} bool ExcludeObject(const CPDF_Object* object) const override { NOTREACHED(); return false; } }; class CPDF_ObjectAvailExcludeArray : public CPDF_ObjectAvail { public: using CPDF_ObjectAvail::CPDF_ObjectAvail; ~CPDF_ObjectAvailExcludeArray() override {} bool ExcludeObject(const CPDF_Object* object) const override { return object->IsArray(); } }; class CPDF_ObjectAvailExcludeTypeKey : public CPDF_ObjectAvail { public: using CPDF_ObjectAvail::CPDF_ObjectAvail; ~CPDF_ObjectAvailExcludeTypeKey() override {} bool ExcludeObject(const CPDF_Object* object) const override { // The value of "Type" may be reference, and if it is not available, we can // incorrect filter objects. // In this case CPDF_ObjectAvail should wait availability of this item and // call ExcludeObject again. return object->IsDictionary() && object->GetDict()->GetStringFor("Type") == "Exclude me"; } }; } // namespace TEST(CPDF_ObjectAvailTest, OneObject) { TestHolder holder; holder.AddObject(1, pdfium::MakeUnique(nullptr, "string", false), TestHolder::ObjectState::Unavailable); CPDF_ObjectAvail avail(holder.GetValidator().Get(), &holder, 1); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataNotAvailable, avail.CheckAvail()); holder.SetObjectState(1, TestHolder::ObjectState::Available); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataAvailable, avail.CheckAvail()); } TEST(CPDF_ObjectAvailTest, OneReferencedObject) { TestHolder holder; holder.AddObject(1, pdfium::MakeUnique(&holder, 2), TestHolder::ObjectState::Unavailable); holder.AddObject(2, pdfium::MakeUnique(nullptr, "string", false), TestHolder::ObjectState::Unavailable); CPDF_ObjectAvail avail(holder.GetValidator().Get(), &holder, 1); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataNotAvailable, avail.CheckAvail()); holder.SetObjectState(1, TestHolder::ObjectState::Available); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataNotAvailable, avail.CheckAvail()); holder.SetObjectState(2, TestHolder::ObjectState::Available); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataAvailable, avail.CheckAvail()); } TEST(CPDF_ObjectAvailTest, CycledReferences) { TestHolder holder; holder.AddObject(1, pdfium::MakeUnique(&holder, 2), TestHolder::ObjectState::Unavailable); holder.AddObject(2, pdfium::MakeUnique(&holder, 3), TestHolder::ObjectState::Unavailable); holder.AddObject(3, pdfium::MakeUnique(&holder, 1), TestHolder::ObjectState::Unavailable); CPDF_ObjectAvail avail(holder.GetValidator().Get(), &holder, 1); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataNotAvailable, avail.CheckAvail()); holder.SetObjectState(1, TestHolder::ObjectState::Available); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataNotAvailable, avail.CheckAvail()); holder.SetObjectState(2, TestHolder::ObjectState::Available); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataNotAvailable, avail.CheckAvail()); holder.SetObjectState(3, TestHolder::ObjectState::Available); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataAvailable, avail.CheckAvail()); } TEST(CPDF_ObjectAvailTest, DoNotCheckParent) { TestHolder holder; holder.AddObject(1, pdfium::MakeUnique(), TestHolder::ObjectState::Unavailable); holder.AddObject(2, pdfium::MakeUnique(), TestHolder::ObjectState::Unavailable); holder.GetTestObject(2)->GetDict()->SetNewFor("Parent", &holder, 1); CPDF_ObjectAvail avail(holder.GetValidator().Get(), &holder, 2); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataNotAvailable, avail.CheckAvail()); holder.SetObjectState(2, TestHolder::ObjectState::Available); // Object should be available in case when "Parent" object is unavailable. EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataAvailable, avail.CheckAvail()); } TEST(CPDF_ObjectAvailTest, Generic) { TestHolder holder; const uint32_t kDepth = 100; for (uint32_t i = 1; i < kDepth; ++i) { holder.AddObject(i, pdfium::MakeUnique(), TestHolder::ObjectState::Unavailable); // Add ref to next dictionary. holder.GetTestObject(i)->GetDict()->SetNewFor( "Child", &holder, i + 1); } // Add final object holder.AddObject(kDepth, pdfium::MakeUnique(), TestHolder::ObjectState::Unavailable); CPDF_ObjectAvail avail(holder.GetValidator().Get(), &holder, 1); for (uint32_t i = 1; i <= kDepth; ++i) { EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataNotAvailable, avail.CheckAvail()); holder.SetObjectState(i, TestHolder::ObjectState::Available); } EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataAvailable, avail.CheckAvail()); } TEST(CPDF_ObjectAvailTest, NotExcludeRoot) { TestHolder holder; holder.AddObject(1, pdfium::MakeUnique(), TestHolder::ObjectState::Available); CPDF_ObjectAvailFailOnExclude avail(holder.GetValidator().Get(), &holder, 1); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataAvailable, avail.CheckAvail()); } TEST(CPDF_ObjectAvailTest, NotExcludeReferedRoot) { TestHolder holder; holder.AddObject(1, pdfium::MakeUnique(&holder, 2), TestHolder::ObjectState::Available); holder.AddObject(2, pdfium::MakeUnique(), TestHolder::ObjectState::Available); CPDF_ObjectAvailFailOnExclude avail(holder.GetValidator().Get(), &holder, 1); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataAvailable, avail.CheckAvail()); } TEST(CPDF_ObjectAvailTest, Exclude) { TestHolder holder; holder.AddObject(1, pdfium::MakeUnique(), TestHolder::ObjectState::Available); holder.GetTestObject(1)->GetDict()->SetNewFor("ArrayRef", &holder, 2); holder.AddObject(2, pdfium::MakeUnique(), TestHolder::ObjectState::Available); holder.GetTestObject(2)->AsArray()->AddNew(&holder, 2); // Add string, which is refered by array item. It is should not be checked. holder.AddObject( 3, pdfium::MakeUnique(nullptr, "Not available string", false), TestHolder::ObjectState::Unavailable); CPDF_ObjectAvailExcludeArray avail(holder.GetValidator().Get(), &holder, 1); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataAvailable, avail.CheckAvail()); } TEST(CPDF_ObjectAvailTest, ReadErrorOnExclude) { TestHolder holder; holder.AddObject(1, pdfium::MakeUnique(), TestHolder::ObjectState::Available); holder.GetTestObject(1)->GetDict()->SetNewFor("DictRef", &holder, 2); holder.AddObject(2, pdfium::MakeUnique(), TestHolder::ObjectState::Available); holder.GetTestObject(2)->GetDict()->SetNewFor("Type", &holder, 3); // The value of "Type" key is not available at start holder.AddObject( 3, pdfium::MakeUnique(nullptr, "Exclude me", false), TestHolder::ObjectState::Unavailable); holder.GetTestObject(2)->GetDict()->SetNewFor("OtherData", &holder, 4); // Add string, which is refered by dictionary item. It is should not be // checked, because the dictionary with it, should be skipped. holder.AddObject( 4, pdfium::MakeUnique(nullptr, "Not available string", false), TestHolder::ObjectState::Unavailable); CPDF_ObjectAvailExcludeTypeKey avail(holder.GetValidator().Get(), &holder, 1); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataNotAvailable, avail.CheckAvail()); // Make "Type" value object available. holder.SetObjectState(3, TestHolder::ObjectState::Available); // Now object should be available, although the object '4' is not available, // because it is in skipped dictionary. EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataAvailable, avail.CheckAvail()); } TEST(CPDF_ObjectAvailTest, IgnoreNotExistsObject) { TestHolder holder; holder.AddObject(1, pdfium::MakeUnique(), TestHolder::ObjectState::Available); holder.GetTestObject(1)->GetDict()->SetNewFor( "NotExistsObjRef", &holder, 2); CPDF_ObjectAvail avail(holder.GetValidator().Get(), &holder, 1); // Now object should be available, although the object '2' is not exists. But // all exists in file related data are checked. EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataAvailable, avail.CheckAvail()); } TEST(CPDF_ObjectAvailTest, CheckTwice) { TestHolder holder; holder.AddObject(1, pdfium::MakeUnique(nullptr, "string", false), TestHolder::ObjectState::Unavailable); CPDF_ObjectAvail avail(holder.GetValidator().Get(), &holder, 1); EXPECT_EQ(avail.CheckAvail(), avail.CheckAvail()); holder.SetObjectState(1, TestHolder::ObjectState::Available); EXPECT_EQ(avail.CheckAvail(), avail.CheckAvail()); } TEST(CPDF_ObjectAvailTest, SelfReferedInlinedObject) { TestHolder holder; holder.AddObject(1, pdfium::MakeUnique(), TestHolder::ObjectState::Available); holder.GetTestObject(1)->GetDict()->SetNewFor("Data", &holder, 2); auto* root = holder.GetTestObject(1)->GetDict()->SetNewFor("Dict"); root->SetNewFor("Self", &holder, 1); holder.AddObject(2, pdfium::MakeUnique(nullptr, "Data", false), TestHolder::ObjectState::Unavailable); CPDF_ObjectAvail avail(holder.GetValidator().Get(), &holder, root); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataNotAvailable, avail.CheckAvail()); holder.SetObjectState(2, TestHolder::ObjectState::Available); EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataAvailable, avail.CheckAvail()); }