summaryrefslogtreecommitdiff
path: root/core/fpdfapi
diff options
context:
space:
mode:
Diffstat (limited to 'core/fpdfapi')
-rw-r--r--core/fpdfapi/parser/cpdf_data_avail.cpp13
-rw-r--r--core/fpdfapi/parser/cpdf_indirect_object_holder.h2
-rw-r--r--core/fpdfapi/parser/cpdf_object_avail.cpp148
-rw-r--r--core/fpdfapi/parser/cpdf_object_avail.h52
-rw-r--r--core/fpdfapi/parser/cpdf_object_avail_unittest.cpp363
-rw-r--r--core/fpdfapi/parser/cpdf_page_object_avail.cpp17
-rw-r--r--core/fpdfapi/parser/cpdf_page_object_avail.h21
-rw-r--r--core/fpdfapi/parser/cpdf_page_object_avail_unittest.cpp137
-rw-r--r--core/fpdfapi/parser/cpdf_read_validator.h3
9 files changed, 746 insertions, 10 deletions
diff --git a/core/fpdfapi/parser/cpdf_data_avail.cpp b/core/fpdfapi/parser/cpdf_data_avail.cpp
index 43897a464c..c9cb1d75fc 100644
--- a/core/fpdfapi/parser/cpdf_data_avail.cpp
+++ b/core/fpdfapi/parser/cpdf_data_avail.cpp
@@ -18,6 +18,7 @@
#include "core/fpdfapi/parser/cpdf_linearized_header.h"
#include "core/fpdfapi/parser/cpdf_name.h"
#include "core/fpdfapi/parser/cpdf_number.h"
+#include "core/fpdfapi/parser/cpdf_page_object_avail.h"
#include "core/fpdfapi/parser/cpdf_read_validator.h"
#include "core/fpdfapi/parser/cpdf_reference.h"
#include "core/fpdfapi/parser/cpdf_stream.h"
@@ -1514,10 +1515,8 @@ bool CPDF_DataAvail::ValidatePage(uint32_t dwPage) {
CPDF_Dictionary* pPageDict = m_pDocument->GetPage(safePage.ValueOrDie());
if (!pPageDict)
return false;
- std::vector<CPDF_Object*> obj_array;
- obj_array.push_back(pPageDict);
- std::vector<CPDF_Object*> dummy;
- return AreObjectsAvailable(obj_array, true, dummy);
+ CPDF_PageObjectAvail obj_avail(GetValidator().Get(), m_pDocument, pPageDict);
+ return obj_avail.CheckAvail() == DocAvailStatus::DataAvailable;
}
bool CPDF_DataAvail::ValidateForm() {
@@ -1527,10 +1526,8 @@ bool CPDF_DataAvail::ValidateForm() {
CPDF_Object* pAcroForm = pRoot->GetObjectFor("AcroForm");
if (!pAcroForm)
return false;
- std::vector<CPDF_Object*> obj_array;
- obj_array.push_back(pAcroForm);
- std::vector<CPDF_Object*> dummy;
- return AreObjectsAvailable(obj_array, true, dummy);
+ CPDF_PageObjectAvail obj_avail(GetValidator().Get(), m_pDocument, pAcroForm);
+ return obj_avail.CheckAvail() == DocAvailStatus::DataAvailable;
}
CPDF_DataAvail::PageNode::PageNode() : m_type(PDF_PAGENODE_UNKNOWN) {}
diff --git a/core/fpdfapi/parser/cpdf_indirect_object_holder.h b/core/fpdfapi/parser/cpdf_indirect_object_holder.h
index b6d33a3cd5..b82377e5b7 100644
--- a/core/fpdfapi/parser/cpdf_indirect_object_holder.h
+++ b/core/fpdfapi/parser/cpdf_indirect_object_holder.h
@@ -28,7 +28,7 @@ class CPDF_IndirectObjectHolder {
virtual ~CPDF_IndirectObjectHolder();
CPDF_Object* GetIndirectObject(uint32_t objnum) const;
- CPDF_Object* GetOrParseIndirectObject(uint32_t objnum);
+ virtual CPDF_Object* GetOrParseIndirectObject(uint32_t objnum);
void DeleteIndirectObject(uint32_t objnum);
// Creates and adds a new object owned by the indirect object holder,
diff --git a/core/fpdfapi/parser/cpdf_object_avail.cpp b/core/fpdfapi/parser/cpdf_object_avail.cpp
new file mode 100644
index 0000000000..35e7f45850
--- /dev/null
+++ b/core/fpdfapi/parser/cpdf_object_avail.cpp
@@ -0,0 +1,148 @@
+// 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 <utility>
+
+#include "core/fpdfapi/parser/cpdf_dictionary.h"
+#include "core/fpdfapi/parser/cpdf_indirect_object_holder.h"
+#include "core/fpdfapi/parser/cpdf_object_walker.h"
+#include "core/fpdfapi/parser/cpdf_read_validator.h"
+#include "core/fpdfapi/parser/cpdf_reference.h"
+
+CPDF_ObjectAvail::CPDF_ObjectAvail(CPDF_ReadValidator* validator,
+ CPDF_IndirectObjectHolder* holder,
+ const CPDF_Object* root)
+ : validator_(validator), holder_(holder), root_(root) {
+ ASSERT(validator_);
+ ASSERT(holder);
+ ASSERT(root_);
+ if (!root_->IsInline())
+ parsed_objnums_.insert(root_->GetObjNum());
+}
+
+CPDF_ObjectAvail::CPDF_ObjectAvail(CPDF_ReadValidator* validator,
+ CPDF_IndirectObjectHolder* holder,
+ uint32_t obj_num)
+ : validator_(validator),
+ holder_(holder),
+ root_(pdfium::MakeUnique<CPDF_Reference>(holder, obj_num)) {
+ ASSERT(validator_);
+ ASSERT(holder);
+}
+
+CPDF_ObjectAvail::~CPDF_ObjectAvail() {}
+
+CPDF_DataAvail::DocAvailStatus CPDF_ObjectAvail::CheckAvail() {
+ if (!LoadRootObject())
+ return CPDF_DataAvail::DocAvailStatus::DataNotAvailable;
+
+ if (CheckObjects()) {
+ CleanMemory();
+ return CPDF_DataAvail::DocAvailStatus::DataAvailable;
+ }
+ return CPDF_DataAvail::DocAvailStatus::DataNotAvailable;
+}
+
+bool CPDF_ObjectAvail::LoadRootObject() {
+ if (!non_parsed_objects_.empty())
+ return true;
+
+ while (root_ && root_->IsReference()) {
+ const uint32_t ref_obj_num = root_->AsReference()->GetRefObjNum();
+ if (HasObjectParsed(ref_obj_num)) {
+ root_ = nullptr;
+ return true;
+ }
+
+ const CPDF_ReadValidator::Session parse_session(validator_.Get());
+ const CPDF_Object* direct = holder_->GetOrParseIndirectObject(ref_obj_num);
+ if (validator_->has_read_problems())
+ return false;
+
+ parsed_objnums_.insert(ref_obj_num);
+ root_ = direct;
+ }
+ std::stack<uint32_t> non_parsed_objects_in_root;
+ if (AppendObjectSubRefs(root_.Get(), &non_parsed_objects_in_root)) {
+ non_parsed_objects_ = std::move(non_parsed_objects_in_root);
+ return true;
+ }
+ return false;
+}
+
+bool CPDF_ObjectAvail::CheckObjects() {
+ std::stack<uint32_t> objects_to_check = std::move(non_parsed_objects_);
+ std::set<uint32_t> checked_objects;
+ while (!objects_to_check.empty()) {
+ const uint32_t obj_num = objects_to_check.top();
+ objects_to_check.pop();
+
+ if (HasObjectParsed(obj_num))
+ continue;
+
+ if (!checked_objects.insert(obj_num).second)
+ continue;
+
+ const CPDF_ReadValidator::Session parse_session(validator_.Get());
+ const CPDF_Object* direct = holder_->GetOrParseIndirectObject(obj_num);
+ if (direct == root_.Get())
+ continue;
+
+ if (validator_->has_read_problems() ||
+ !AppendObjectSubRefs(direct, &objects_to_check)) {
+ non_parsed_objects_.push(obj_num);
+ continue;
+ }
+ parsed_objnums_.insert(obj_num);
+ }
+ return non_parsed_objects_.empty();
+}
+
+bool CPDF_ObjectAvail::AppendObjectSubRefs(const CPDF_Object* object,
+ std::stack<uint32_t>* refs) const {
+ ASSERT(refs);
+ if (!object)
+ return true;
+
+ CPDF_ObjectWalker walker(object);
+ while (const CPDF_Object* obj = walker.GetNext()) {
+ const CPDF_ReadValidator::Session parse_session(validator_.Get());
+
+ // Skip if this object if it's an inlined root, the parent object or
+ // explicitily excluded.
+ const bool skip = (walker.GetParent() && obj == root_.Get()) ||
+ walker.dictionary_key() == "Parent" ||
+ (obj != root_.Get() && ExcludeObject(obj));
+
+ // We need to parse the object before we can do the exclusion check.
+ // This is because the exclusion check may check against a referenced
+ // field of the object which we need to make sure is loaded.
+ if (validator_->has_read_problems())
+ return false;
+
+ if (skip) {
+ walker.SkipWalkIntoCurrentObject();
+ continue;
+ }
+
+ if (obj->IsReference())
+ refs->push(obj->AsReference()->GetRefObjNum());
+ }
+ return true;
+}
+
+void CPDF_ObjectAvail::CleanMemory() {
+ root_.Reset();
+ parsed_objnums_.clear();
+}
+
+bool CPDF_ObjectAvail::ExcludeObject(const CPDF_Object* object) const {
+ return false;
+}
+
+bool CPDF_ObjectAvail::HasObjectParsed(uint32_t obj_num) const {
+ return parsed_objnums_.count(obj_num) > 0;
+}
diff --git a/core/fpdfapi/parser/cpdf_object_avail.h b/core/fpdfapi/parser/cpdf_object_avail.h
new file mode 100644
index 0000000000..233d180c94
--- /dev/null
+++ b/core/fpdfapi/parser/cpdf_object_avail.h
@@ -0,0 +1,52 @@
+// 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.
+
+#ifndef CORE_FPDFAPI_PARSER_CPDF_OBJECT_AVAIL_H_
+#define CORE_FPDFAPI_PARSER_CPDF_OBJECT_AVAIL_H_
+
+#include <memory>
+#include <set>
+#include <stack>
+
+#include "core/fpdfapi/parser/cpdf_data_avail.h"
+#include "core/fxcrt/cfx_maybe_owned.h"
+#include "core/fxcrt/cfx_unowned_ptr.h"
+
+class CPDF_Object;
+class CPDF_Reference;
+class CPDF_IndirectObjectHolder;
+class CPDF_ReadValidator;
+
+// Helper for check availability of object tree.
+class CPDF_ObjectAvail {
+ public:
+ CPDF_ObjectAvail(CPDF_ReadValidator* validator,
+ CPDF_IndirectObjectHolder* holder,
+ const CPDF_Object* root);
+ CPDF_ObjectAvail(CPDF_ReadValidator* validator,
+ CPDF_IndirectObjectHolder* holder,
+ uint32_t obj_num);
+ virtual ~CPDF_ObjectAvail();
+
+ CPDF_DataAvail::DocAvailStatus CheckAvail();
+
+ protected:
+ virtual bool ExcludeObject(const CPDF_Object* object) const;
+
+ private:
+ bool LoadRootObject();
+ bool CheckObjects();
+ bool AppendObjectSubRefs(const CPDF_Object* object,
+ std::stack<uint32_t>* refs) const;
+ void CleanMemory();
+ bool HasObjectParsed(uint32_t obj_num) const;
+
+ CFX_UnownedPtr<CPDF_ReadValidator> validator_;
+ CFX_UnownedPtr<CPDF_IndirectObjectHolder> holder_;
+ CFX_MaybeOwned<const CPDF_Object> root_;
+ std::set<uint32_t> parsed_objnums_;
+ std::stack<uint32_t> non_parsed_objects_;
+};
+
+#endif // CORE_FPDFAPI_PARSER_CPDF_OBJECT_AVAIL_H_
diff --git a/core/fpdfapi/parser/cpdf_object_avail_unittest.cpp b/core/fpdfapi/parser/cpdf_object_avail_unittest.cpp
new file mode 100644
index 0000000000..cb9ceac101
--- /dev/null
+++ b/core/fpdfapi/parser/cpdf_object_avail_unittest.cpp
@@ -0,0 +1,363 @@
+// 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 <map>
+#include <utility>
+
+#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/gtest/include/gtest/gtest.h"
+#include "third_party/base/ptr_util.h"
+
+namespace {
+
+class InvalidReader : public IFX_SeekableReadStream {
+ public:
+ template <typename T, typename... Args>
+ friend CFX_RetainPtr<T> pdfium::MakeRetain(Args&&... args);
+
+ // IFX_SeekableReadStream overrides:
+ bool ReadBlock(void* buffer, FX_FILESIZE offset, size_t size) override {
+ return false;
+ }
+ FX_FILESIZE GetSize() override { return 100; }
+
+ private:
+ InvalidReader() {}
+ ~InvalidReader() override {}
+};
+
+class TestReadValidator : public CPDF_ReadValidator {
+ public:
+ template <typename T, typename... Args>
+ friend CFX_RetainPtr<T> pdfium::MakeRetain(Args&&... args);
+
+ void SimulateReadError() { ReadBlock(nullptr, 0, 1); }
+
+ protected:
+ TestReadValidator()
+ : CPDF_ReadValidator(pdfium::MakeRetain<InvalidReader>(), nullptr) {}
+ ~TestReadValidator() override {}
+};
+
+class TestHolder : public CPDF_IndirectObjectHolder {
+ public:
+ enum class ObjectState {
+ Unavailable,
+ Available,
+ };
+ TestHolder() : validator_(pdfium::MakeRetain<TestReadValidator>()) {}
+ ~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();
+ }
+
+ CFX_RetainPtr<CPDF_ReadValidator> GetValidator() { return validator_; }
+
+ void AddObject(uint32_t objnum,
+ std::unique_ptr<CPDF_Object> 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<CPDF_Object> object;
+ ObjectState state = ObjectState::Unavailable;
+ };
+ std::map<uint32_t, ObjectData> objects_data_;
+ CFX_RetainPtr<TestReadValidator> 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<CPDF_String>(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<CPDF_Reference>(&holder, 2),
+ TestHolder::ObjectState::Unavailable);
+ holder.AddObject(2, pdfium::MakeUnique<CPDF_String>(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<CPDF_Reference>(&holder, 2),
+ TestHolder::ObjectState::Unavailable);
+ holder.AddObject(2, pdfium::MakeUnique<CPDF_Reference>(&holder, 3),
+ TestHolder::ObjectState::Unavailable);
+ holder.AddObject(3, pdfium::MakeUnique<CPDF_Reference>(&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<CPDF_Dictionary>(),
+ TestHolder::ObjectState::Unavailable);
+ holder.AddObject(2, pdfium::MakeUnique<CPDF_Dictionary>(),
+ TestHolder::ObjectState::Unavailable);
+
+ holder.GetTestObject(2)->GetDict()->SetNewFor<CPDF_Reference>("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<CPDF_Dictionary>(),
+ TestHolder::ObjectState::Unavailable);
+ // Add ref to next dictionary.
+ holder.GetTestObject(i)->GetDict()->SetNewFor<CPDF_Reference>(
+ "Child", &holder, i + 1);
+ }
+ // Add final object
+ holder.AddObject(kDepth, pdfium::MakeUnique<CPDF_Dictionary>(),
+ 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<CPDF_Dictionary>(),
+ 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<CPDF_Reference>(&holder, 2),
+ TestHolder::ObjectState::Available);
+ holder.AddObject(2, pdfium::MakeUnique<CPDF_Dictionary>(),
+ 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<CPDF_Dictionary>(),
+ TestHolder::ObjectState::Available);
+ holder.GetTestObject(1)->GetDict()->SetNewFor<CPDF_Reference>("ArrayRef",
+ &holder, 2);
+ holder.AddObject(2, pdfium::MakeUnique<CPDF_Array>(),
+ TestHolder::ObjectState::Available);
+ holder.GetTestObject(2)->AsArray()->AddNew<CPDF_Reference>(&holder, 2);
+
+ // Add string, which is refered by array item. It is should not be checked.
+ holder.AddObject(
+ 3,
+ pdfium::MakeUnique<CPDF_String>(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<CPDF_Dictionary>(),
+ TestHolder::ObjectState::Available);
+ holder.GetTestObject(1)->GetDict()->SetNewFor<CPDF_Reference>("DictRef",
+ &holder, 2);
+ holder.AddObject(2, pdfium::MakeUnique<CPDF_Dictionary>(),
+ TestHolder::ObjectState::Available);
+
+ holder.GetTestObject(2)->GetDict()->SetNewFor<CPDF_Reference>("Type", &holder,
+ 3);
+ // The value of "Type" key is not available at start
+ holder.AddObject(
+ 3, pdfium::MakeUnique<CPDF_String>(nullptr, "Exclude me", false),
+ TestHolder::ObjectState::Unavailable);
+
+ holder.GetTestObject(2)->GetDict()->SetNewFor<CPDF_Reference>("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<CPDF_String>(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<CPDF_Dictionary>(),
+ TestHolder::ObjectState::Available);
+ holder.GetTestObject(1)->GetDict()->SetNewFor<CPDF_Reference>(
+ "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<CPDF_String>(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<CPDF_Dictionary>(),
+ TestHolder::ObjectState::Available);
+
+ holder.GetTestObject(1)->GetDict()->SetNewFor<CPDF_Reference>("Data", &holder,
+ 2);
+ auto* root =
+ holder.GetTestObject(1)->GetDict()->SetNewFor<CPDF_Dictionary>("Dict");
+
+ root->SetNewFor<CPDF_Reference>("Self", &holder, 1);
+
+ holder.AddObject(2, pdfium::MakeUnique<CPDF_String>(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());
+}
diff --git a/core/fpdfapi/parser/cpdf_page_object_avail.cpp b/core/fpdfapi/parser/cpdf_page_object_avail.cpp
new file mode 100644
index 0000000000..6673885f61
--- /dev/null
+++ b/core/fpdfapi/parser/cpdf_page_object_avail.cpp
@@ -0,0 +1,17 @@
+// 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_page_object_avail.h"
+
+#include "core/fpdfapi/parser/cpdf_dictionary.h"
+
+CPDF_PageObjectAvail::~CPDF_PageObjectAvail() {}
+
+bool CPDF_PageObjectAvail::ExcludeObject(const CPDF_Object* object) const {
+ if (CPDF_ObjectAvail::ExcludeObject(object))
+ return true;
+
+ return object->IsDictionary() &&
+ object->GetDict()->GetStringFor("Type") == "Page";
+}
diff --git a/core/fpdfapi/parser/cpdf_page_object_avail.h b/core/fpdfapi/parser/cpdf_page_object_avail.h
new file mode 100644
index 0000000000..8c740ed045
--- /dev/null
+++ b/core/fpdfapi/parser/cpdf_page_object_avail.h
@@ -0,0 +1,21 @@
+// 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.
+
+#ifndef CORE_FPDFAPI_PARSER_CPDF_PAGE_OBJECT_AVAIL_H_
+#define CORE_FPDFAPI_PARSER_CPDF_PAGE_OBJECT_AVAIL_H_
+
+#include "core/fpdfapi/parser/cpdf_object_avail.h"
+
+// Helper for check availability of page's object tree.
+// Exclude references to pages.
+class CPDF_PageObjectAvail : public CPDF_ObjectAvail {
+ public:
+ using CPDF_ObjectAvail::CPDF_ObjectAvail;
+ ~CPDF_PageObjectAvail() override;
+
+ protected:
+ bool ExcludeObject(const CPDF_Object* object) const override;
+};
+
+#endif // CORE_FPDFAPI_PARSER_CPDF_PAGE_OBJECT_AVAIL_H_
diff --git a/core/fpdfapi/parser/cpdf_page_object_avail_unittest.cpp b/core/fpdfapi/parser/cpdf_page_object_avail_unittest.cpp
new file mode 100644
index 0000000000..a6454cd9fe
--- /dev/null
+++ b/core/fpdfapi/parser/cpdf_page_object_avail_unittest.cpp
@@ -0,0 +1,137 @@
+// 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_page_object_avail.h"
+
+#include <map>
+#include <memory>
+#include <utility>
+
+#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/gtest/include/gtest/gtest.h"
+#include "third_party/base/ptr_util.h"
+
+namespace {
+
+class InvalidReader : public IFX_SeekableReadStream {
+ public:
+ template <typename T, typename... Args>
+ friend CFX_RetainPtr<T> pdfium::MakeRetain(Args&&... args);
+
+ // IFX_SeekableReadStream overrides:
+ bool ReadBlock(void* buffer, FX_FILESIZE offset, size_t size) override {
+ return false;
+ }
+ FX_FILESIZE GetSize() override { return 100; }
+
+ private:
+ InvalidReader() {}
+ ~InvalidReader() override {}
+};
+
+class TestReadValidator : public CPDF_ReadValidator {
+ public:
+ template <typename T, typename... Args>
+ friend CFX_RetainPtr<T> pdfium::MakeRetain(Args&&... args);
+
+ void SimulateReadError() { ReadBlock(nullptr, 0, 1); }
+
+ protected:
+ TestReadValidator()
+ : CPDF_ReadValidator(pdfium::MakeRetain<InvalidReader>(), nullptr) {}
+ ~TestReadValidator() override {}
+};
+
+class TestHolder : public CPDF_IndirectObjectHolder {
+ public:
+ enum class ObjectState {
+ Unavailable,
+ Available,
+ };
+ TestHolder() : validator_(pdfium::MakeRetain<TestReadValidator>()) {}
+ ~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();
+ }
+
+ CFX_RetainPtr<CPDF_ReadValidator> GetValidator() { return validator_; }
+
+ void AddObject(uint32_t objnum,
+ std::unique_ptr<CPDF_Object> 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<CPDF_Object> object;
+ ObjectState state = ObjectState::Unavailable;
+ };
+ std::map<uint32_t, ObjectData> objects_data_;
+ CFX_RetainPtr<TestReadValidator> validator_;
+};
+
+} // namespace
+
+TEST(CPDF_PageObjectAvailTest, ExcludePages) {
+ TestHolder holder;
+ holder.AddObject(1, pdfium::MakeUnique<CPDF_Dictionary>(),
+ TestHolder::ObjectState::Available);
+ holder.GetTestObject(1)->GetDict()->SetNewFor<CPDF_Reference>("Kids", &holder,
+ 2);
+ holder.AddObject(2, pdfium::MakeUnique<CPDF_Array>(),
+ TestHolder::ObjectState::Available);
+ holder.GetTestObject(2)->AsArray()->AddNew<CPDF_Reference>(&holder, 3);
+
+ holder.AddObject(3, pdfium::MakeUnique<CPDF_Dictionary>(),
+ TestHolder::ObjectState::Available);
+ holder.GetTestObject(3)->GetDict()->SetFor(
+ "Type", pdfium::MakeUnique<CPDF_String>(nullptr, "Page", false));
+ holder.GetTestObject(3)->GetDict()->SetNewFor<CPDF_Reference>("OtherPageData",
+ &holder, 4);
+ // Add unavailable object related to other page.
+ holder.AddObject(
+ 4, pdfium::MakeUnique<CPDF_String>(nullptr, "Other page data", false),
+ TestHolder::ObjectState::Unavailable);
+
+ CPDF_PageObjectAvail avail(holder.GetValidator().Get(), &holder, 1);
+ // Now object should be available, although the object '4' is not available,
+ // because it is in skipped other page.
+ EXPECT_EQ(CPDF_DataAvail::DocAvailStatus::DataAvailable, avail.CheckAvail());
+}
diff --git a/core/fpdfapi/parser/cpdf_read_validator.h b/core/fpdfapi/parser/cpdf_read_validator.h
index 106f6e437b..20ec866a59 100644
--- a/core/fpdfapi/parser/cpdf_read_validator.h
+++ b/core/fpdfapi/parser/cpdf_read_validator.h
@@ -46,11 +46,12 @@ class CPDF_ReadValidator : public IFX_SeekableReadStream {
bool ReadBlock(void* buffer, FX_FILESIZE offset, size_t size) override;
FX_FILESIZE GetSize() override;
- private:
+ protected:
CPDF_ReadValidator(const CFX_RetainPtr<IFX_SeekableReadStream>& file_read,
CPDF_DataAvail::FileAvail* file_avail);
~CPDF_ReadValidator() override;
+ private:
void ScheduleDownload(FX_FILESIZE offset, size_t size);
CFX_RetainPtr<IFX_SeekableReadStream> file_read_;