| // 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" |
| #include "third_party/base/check.h" |
| #include "third_party/base/containers/contains.h" |
| |
| CPDF_ObjectAvail::CPDF_ObjectAvail( |
| const RetainPtr<CPDF_ReadValidator>& validator, |
| CPDF_IndirectObjectHolder* holder, |
| CPDF_Object* root) |
| : validator_(validator), holder_(holder), root_(root) { |
| DCHECK(validator_); |
| DCHECK(holder); |
| DCHECK(root_); |
| if (!root_->IsInline()) |
| parsed_objnums_.insert(root_->GetObjNum()); |
| } |
| |
| CPDF_ObjectAvail::CPDF_ObjectAvail( |
| const RetainPtr<CPDF_ReadValidator>& validator, |
| CPDF_IndirectObjectHolder* holder, |
| uint32_t obj_num) |
| : validator_(validator), |
| holder_(holder), |
| root_(pdfium::MakeRetain<CPDF_Reference>(holder, obj_num)) { |
| DCHECK(validator_); |
| DCHECK(holder); |
| } |
| |
| CPDF_ObjectAvail::~CPDF_ObjectAvail() = default; |
| |
| CPDF_DataAvail::DocAvailStatus CPDF_ObjectAvail::CheckAvail() { |
| if (!LoadRootObject()) |
| return CPDF_DataAvail::kDataNotAvailable; |
| |
| if (CheckObjects()) { |
| CleanMemory(); |
| return CPDF_DataAvail::kDataAvailable; |
| } |
| return CPDF_DataAvail::kDataNotAvailable; |
| } |
| |
| 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; |
| } |
| |
| CPDF_ReadValidator::ScopedSession parse_session(validator_); |
| CPDF_Object* direct = holder_->GetOrParseIndirectObject(ref_obj_num); |
| if (validator_->has_read_problems()) |
| return false; |
| |
| parsed_objnums_.insert(ref_obj_num); |
| root_.Reset(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::set<uint32_t> checked_objects; |
| std::stack<uint32_t> objects_to_check = std::move(non_parsed_objects_); |
| non_parsed_objects_ = std::stack<uint32_t>(); |
| 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; |
| |
| CPDF_ReadValidator::ScopedSession parse_session(validator_); |
| const CPDF_Object* direct = holder_->GetOrParseIndirectObject(obj_num); |
| if (direct == root_) |
| 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 { |
| DCHECK(refs); |
| if (!object) |
| return true; |
| |
| CPDF_ObjectWalker walker(object); |
| while (const CPDF_Object* obj = walker.GetNext()) { |
| CPDF_ReadValidator::ScopedSession parse_session(validator_); |
| |
| // Skip if this object if it's an inlined root, the parent object or |
| // explicitily excluded. |
| const bool skip = (walker.GetParent() && obj == root_) || |
| walker.dictionary_key() == "Parent" || |
| (obj != root_ && 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 pdfium::Contains(parsed_objnums_, obj_num); |
| } |