| // 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_cross_ref_avail.h" | |
| #include "core/fpdfapi/parser/cpdf_dictionary.h" | |
| #include "core/fpdfapi/parser/cpdf_read_validator.h" | |
| #include "core/fpdfapi/parser/cpdf_reference.h" | |
| #include "core/fpdfapi/parser/cpdf_syntax_parser.h" | |
| #include "core/fpdfapi/parser/fpdf_parser_utility.h" | |
| #include "third_party/base/check.h" | |
| #include "third_party/base/notreached.h" | |
| #include "third_party/base/numerics/safe_conversions.h" | |
| #include "third_party/base/stl_util.h" | |
| namespace { | |
| constexpr char kCrossRefKeyword[] = "xref"; | |
| constexpr char kTrailerKeyword[] = "trailer"; | |
| constexpr char kPrevCrossRefFieldKey[] = "Prev"; | |
| constexpr char kTypeFieldKey[] = "Type"; | |
| constexpr char kPrevCrossRefStreamOffsetFieldKey[] = "XRefStm"; | |
| constexpr char kXRefKeyword[] = "XRef"; | |
| constexpr char kEncryptKey[] = "Encrypt"; | |
| } // namespace | |
| CPDF_CrossRefAvail::CPDF_CrossRefAvail(CPDF_SyntaxParser* parser, | |
| FX_FILESIZE last_crossref_offset) | |
| : parser_(parser), last_crossref_offset_(last_crossref_offset) { | |
| DCHECK(parser_); | |
| AddCrossRefForCheck(last_crossref_offset); | |
| } | |
| CPDF_CrossRefAvail::~CPDF_CrossRefAvail() = default; | |
| CPDF_DataAvail::DocAvailStatus CPDF_CrossRefAvail::CheckAvail() { | |
| if (status_ == CPDF_DataAvail::kDataAvailable) | |
| return CPDF_DataAvail::kDataAvailable; | |
| CPDF_ReadValidator::ScopedSession read_session(GetValidator()); | |
| while (true) { | |
| bool check_result = false; | |
| switch (state_) { | |
| case State::kCrossRefCheck: | |
| check_result = CheckCrossRef(); | |
| break; | |
| case State::kCrossRefV4ItemCheck: | |
| check_result = CheckCrossRefV4Item(); | |
| break; | |
| case State::kCrossRefV4TrailerCheck: | |
| check_result = CheckCrossRefV4Trailer(); | |
| break; | |
| case State::kDone: | |
| break; | |
| default: { | |
| status_ = CPDF_DataAvail::kDataError; | |
| NOTREACHED(); | |
| break; | |
| } | |
| } | |
| if (!check_result) | |
| break; | |
| DCHECK(!GetValidator()->has_read_problems()); | |
| } | |
| return status_; | |
| } | |
| bool CPDF_CrossRefAvail::CheckReadProblems() { | |
| if (GetValidator()->read_error()) { | |
| status_ = CPDF_DataAvail::kDataError; | |
| return true; | |
| } | |
| return GetValidator()->has_unavailable_data(); | |
| } | |
| bool CPDF_CrossRefAvail::CheckCrossRef() { | |
| if (cross_refs_for_check_.empty()) { | |
| // All cross refs were checked. | |
| state_ = State::kDone; | |
| status_ = CPDF_DataAvail::kDataAvailable; | |
| return true; | |
| } | |
| parser_->SetPos(cross_refs_for_check_.front()); | |
| const ByteString first_word = parser_->PeekNextWord(nullptr); | |
| if (CheckReadProblems()) | |
| return false; | |
| const bool result = (first_word == kCrossRefKeyword) ? CheckCrossRefV4() | |
| : CheckCrossRefStream(); | |
| if (result) | |
| cross_refs_for_check_.pop(); | |
| return result; | |
| } | |
| bool CPDF_CrossRefAvail::CheckCrossRefV4() { | |
| const ByteString keyword = parser_->GetKeyword(); | |
| if (CheckReadProblems()) | |
| return false; | |
| if (keyword != kCrossRefKeyword) { | |
| status_ = CPDF_DataAvail::kDataError; | |
| return false; | |
| } | |
| state_ = State::kCrossRefV4ItemCheck; | |
| offset_ = parser_->GetPos(); | |
| return true; | |
| } | |
| bool CPDF_CrossRefAvail::CheckCrossRefV4Item() { | |
| parser_->SetPos(offset_); | |
| const ByteString keyword = parser_->GetKeyword(); | |
| if (CheckReadProblems()) | |
| return false; | |
| if (keyword.IsEmpty()) { | |
| status_ = CPDF_DataAvail::kDataError; | |
| return false; | |
| } | |
| if (keyword == kTrailerKeyword) | |
| state_ = State::kCrossRefV4TrailerCheck; | |
| // Go to next item. | |
| offset_ = parser_->GetPos(); | |
| return true; | |
| } | |
| bool CPDF_CrossRefAvail::CheckCrossRefV4Trailer() { | |
| parser_->SetPos(offset_); | |
| RetainPtr<CPDF_Dictionary> trailer = | |
| ToDictionary(parser_->GetObjectBody(nullptr)); | |
| if (CheckReadProblems()) | |
| return false; | |
| if (!trailer) { | |
| status_ = CPDF_DataAvail::kDataError; | |
| return false; | |
| } | |
| if (ToReference(trailer->GetObjectFor(kEncryptKey))) { | |
| status_ = CPDF_DataAvail::kDataError; | |
| return false; | |
| } | |
| const int32_t xrefpos = | |
| GetDirectInteger(trailer.Get(), kPrevCrossRefFieldKey); | |
| if (xrefpos && | |
| pdfium::base::IsValueInRangeForNumericType<FX_FILESIZE>(xrefpos)) | |
| AddCrossRefForCheck(static_cast<FX_FILESIZE>(xrefpos)); | |
| const int32_t stream_xref_offset = | |
| GetDirectInteger(trailer.Get(), kPrevCrossRefStreamOffsetFieldKey); | |
| if (stream_xref_offset && | |
| pdfium::base::IsValueInRangeForNumericType<FX_FILESIZE>( | |
| stream_xref_offset)) | |
| AddCrossRefForCheck(static_cast<FX_FILESIZE>(stream_xref_offset)); | |
| // Goto check next crossref | |
| state_ = State::kCrossRefCheck; | |
| return true; | |
| } | |
| bool CPDF_CrossRefAvail::CheckCrossRefStream() { | |
| auto cross_ref = | |
| parser_->GetIndirectObject(nullptr, CPDF_SyntaxParser::ParseType::kLoose); | |
| if (CheckReadProblems()) | |
| return false; | |
| const CPDF_Dictionary* trailer = | |
| cross_ref && cross_ref->IsStream() ? cross_ref->GetDict() : nullptr; | |
| if (!trailer) { | |
| status_ = CPDF_DataAvail::kDataError; | |
| return false; | |
| } | |
| if (ToReference(trailer->GetObjectFor(kEncryptKey))) { | |
| status_ = CPDF_DataAvail::kDataError; | |
| return false; | |
| } | |
| if (trailer->GetNameFor(kTypeFieldKey) == kXRefKeyword) { | |
| const int32_t xrefpos = trailer->GetIntegerFor(kPrevCrossRefFieldKey); | |
| if (xrefpos && | |
| pdfium::base::IsValueInRangeForNumericType<FX_FILESIZE>(xrefpos)) { | |
| AddCrossRefForCheck(static_cast<FX_FILESIZE>(xrefpos)); | |
| } | |
| } | |
| // Goto check next crossref | |
| state_ = State::kCrossRefCheck; | |
| return true; | |
| } | |
| void CPDF_CrossRefAvail::AddCrossRefForCheck(FX_FILESIZE crossref_offset) { | |
| if (pdfium::Contains(registered_crossrefs_, crossref_offset)) | |
| return; | |
| cross_refs_for_check_.push(crossref_offset); | |
| registered_crossrefs_.insert(crossref_offset); | |
| } | |
| RetainPtr<CPDF_ReadValidator> CPDF_CrossRefAvail::GetValidator() { | |
| return parser_->GetValidator(); | |
| } |