• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "core/fpdfapi/parser/cpdf_cross_ref_avail.h"
6 
7 #include "core/fpdfapi/parser/cpdf_dictionary.h"
8 #include "core/fpdfapi/parser/cpdf_read_validator.h"
9 #include "core/fpdfapi/parser/cpdf_reference.h"
10 #include "core/fpdfapi/parser/cpdf_syntax_parser.h"
11 #include "core/fpdfapi/parser/fpdf_parser_utility.h"
12 #include "core/fxcrt/check.h"
13 #include "core/fxcrt/containers/contains.h"
14 #include "core/fxcrt/numerics/safe_conversions.h"
15 
16 namespace {
17 
18 constexpr char kCrossRefKeyword[] = "xref";
19 constexpr char kTrailerKeyword[] = "trailer";
20 constexpr char kPrevCrossRefFieldKey[] = "Prev";
21 constexpr char kTypeFieldKey[] = "Type";
22 constexpr char kPrevCrossRefStreamOffsetFieldKey[] = "XRefStm";
23 constexpr char kXRefKeyword[] = "XRef";
24 constexpr char kEncryptKey[] = "Encrypt";
25 
26 }  // namespace
27 
CPDF_CrossRefAvail(CPDF_SyntaxParser * parser,FX_FILESIZE last_crossref_offset)28 CPDF_CrossRefAvail::CPDF_CrossRefAvail(CPDF_SyntaxParser* parser,
29                                        FX_FILESIZE last_crossref_offset)
30     : parser_(parser), last_crossref_offset_(last_crossref_offset) {
31   DCHECK(parser_);
32   AddCrossRefForCheck(last_crossref_offset);
33 }
34 
35 CPDF_CrossRefAvail::~CPDF_CrossRefAvail() = default;
36 
CheckAvail()37 CPDF_DataAvail::DocAvailStatus CPDF_CrossRefAvail::CheckAvail() {
38   if (status_ == CPDF_DataAvail::kDataAvailable)
39     return CPDF_DataAvail::kDataAvailable;
40 
41   CPDF_ReadValidator::ScopedSession read_session(GetValidator());
42   while (true) {
43     bool check_result = false;
44     switch (state_) {
45       case State::kCrossRefCheck:
46         check_result = CheckCrossRef();
47         break;
48       case State::kCrossRefTableItemCheck:
49         check_result = CheckCrossRefTableItem();
50         break;
51       case State::kCrossRefTableTrailerCheck:
52         check_result = CheckCrossRefTableTrailer();
53         break;
54       case State::kDone:
55         break;
56     }
57     if (!check_result)
58       break;
59 
60     DCHECK(!GetValidator()->has_read_problems());
61   }
62   return status_;
63 }
64 
CheckReadProblems()65 bool CPDF_CrossRefAvail::CheckReadProblems() {
66   if (GetValidator()->read_error()) {
67     status_ = CPDF_DataAvail::kDataError;
68     return true;
69   }
70   return GetValidator()->has_unavailable_data();
71 }
72 
CheckCrossRef()73 bool CPDF_CrossRefAvail::CheckCrossRef() {
74   if (cross_refs_for_check_.empty()) {
75     // All cross refs were checked.
76     state_ = State::kDone;
77     status_ = CPDF_DataAvail::kDataAvailable;
78     return true;
79   }
80   parser_->SetPos(cross_refs_for_check_.front());
81 
82   const ByteString first_word = parser_->PeekNextWord();
83   if (CheckReadProblems())
84     return false;
85 
86   const bool result = (first_word == kCrossRefKeyword) ? CheckCrossRefTable()
87                                                        : CheckCrossRefStream();
88 
89   if (result)
90     cross_refs_for_check_.pop();
91 
92   return result;
93 }
94 
CheckCrossRefTable()95 bool CPDF_CrossRefAvail::CheckCrossRefTable() {
96   const ByteString keyword = parser_->GetKeyword();
97   if (CheckReadProblems())
98     return false;
99 
100   if (keyword != kCrossRefKeyword) {
101     status_ = CPDF_DataAvail::kDataError;
102     return false;
103   }
104 
105   state_ = State::kCrossRefTableItemCheck;
106   offset_ = parser_->GetPos();
107   return true;
108 }
109 
CheckCrossRefTableItem()110 bool CPDF_CrossRefAvail::CheckCrossRefTableItem() {
111   parser_->SetPos(offset_);
112   const ByteString keyword = parser_->GetKeyword();
113   if (CheckReadProblems())
114     return false;
115 
116   if (keyword.IsEmpty()) {
117     status_ = CPDF_DataAvail::kDataError;
118     return false;
119   }
120 
121   if (keyword == kTrailerKeyword)
122     state_ = State::kCrossRefTableTrailerCheck;
123 
124   // Go to next item.
125   offset_ = parser_->GetPos();
126   return true;
127 }
128 
CheckCrossRefTableTrailer()129 bool CPDF_CrossRefAvail::CheckCrossRefTableTrailer() {
130   parser_->SetPos(offset_);
131 
132   RetainPtr<CPDF_Dictionary> trailer =
133       ToDictionary(parser_->GetObjectBody(nullptr));
134   if (CheckReadProblems())
135     return false;
136 
137   if (!trailer) {
138     status_ = CPDF_DataAvail::kDataError;
139     return false;
140   }
141 
142   if (ToReference(trailer->GetObjectFor(kEncryptKey))) {
143     status_ = CPDF_DataAvail::kDataError;
144     return false;
145   }
146 
147   const int32_t xrefpos = trailer->GetDirectIntegerFor(kPrevCrossRefFieldKey);
148   if (xrefpos > 0 &&
149       pdfium::IsValueInRangeForNumericType<FX_FILESIZE>(xrefpos)) {
150     AddCrossRefForCheck(static_cast<FX_FILESIZE>(xrefpos));
151   }
152 
153   const int32_t stream_xref_offset =
154       trailer->GetDirectIntegerFor(kPrevCrossRefStreamOffsetFieldKey);
155   if (stream_xref_offset > 0 &&
156       pdfium::IsValueInRangeForNumericType<FX_FILESIZE>(stream_xref_offset)) {
157     AddCrossRefForCheck(static_cast<FX_FILESIZE>(stream_xref_offset));
158   }
159 
160   // Goto check next crossref
161   state_ = State::kCrossRefCheck;
162   return true;
163 }
164 
CheckCrossRefStream()165 bool CPDF_CrossRefAvail::CheckCrossRefStream() {
166   auto cross_ref =
167       parser_->GetIndirectObject(nullptr, CPDF_SyntaxParser::ParseType::kLoose);
168   if (CheckReadProblems())
169     return false;
170 
171   RetainPtr<const CPDF_Dictionary> trailer =
172       cross_ref && cross_ref->IsStream() ? cross_ref->GetDict() : nullptr;
173   if (!trailer) {
174     status_ = CPDF_DataAvail::kDataError;
175     return false;
176   }
177 
178   if (ToReference(trailer->GetObjectFor(kEncryptKey))) {
179     status_ = CPDF_DataAvail::kDataError;
180     return false;
181   }
182 
183   if (trailer->GetNameFor(kTypeFieldKey) == kXRefKeyword) {
184     const int32_t xrefpos = trailer->GetIntegerFor(kPrevCrossRefFieldKey);
185     if (xrefpos > 0 &&
186         pdfium::IsValueInRangeForNumericType<FX_FILESIZE>(xrefpos)) {
187       AddCrossRefForCheck(static_cast<FX_FILESIZE>(xrefpos));
188     }
189   }
190   // Goto check next crossref
191   state_ = State::kCrossRefCheck;
192   return true;
193 }
194 
AddCrossRefForCheck(FX_FILESIZE crossref_offset)195 void CPDF_CrossRefAvail::AddCrossRefForCheck(FX_FILESIZE crossref_offset) {
196   if (pdfium::Contains(registered_crossrefs_, crossref_offset))
197     return;
198 
199   cross_refs_for_check_.push(crossref_offset);
200   registered_crossrefs_.insert(crossref_offset);
201 }
202 
GetValidator()203 RetainPtr<CPDF_ReadValidator> CPDF_CrossRefAvail::GetValidator() {
204   return parser_->GetValidator();
205 }
206