blob: d3b8b7928d5c0f92d745ac3d5b0baaf35d148ced [file] [log] [blame]
// 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::DataAvailable)
return CPDF_DataAvail::DataAvailable;
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::DataError;
NOTREACHED();
break;
}
}
if (!check_result)
break;
DCHECK(!GetValidator()->has_read_problems());
}
return status_;
}
bool CPDF_CrossRefAvail::CheckReadProblems() {
if (GetValidator()->read_error()) {
status_ = CPDF_DataAvail::DataError;
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::DataAvailable;
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::DataError;
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::DataError;
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::DataError;
return false;
}
if (ToReference(trailer->GetObjectFor(kEncryptKey))) {
status_ = CPDF_DataAvail::DataError;
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::DataError;
return false;
}
if (ToReference(trailer->GetObjectFor(kEncryptKey))) {
status_ = CPDF_DataAvail::DataError;
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();
}