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_object_avail.h"
6
7 #include <map>
8 #include <utility>
9
10 #include "core/fpdfapi/parser/cpdf_array.h"
11 #include "core/fpdfapi/parser/cpdf_dictionary.h"
12 #include "core/fpdfapi/parser/cpdf_indirect_object_holder.h"
13 #include "core/fpdfapi/parser/cpdf_read_validator.h"
14 #include "core/fpdfapi/parser/cpdf_reference.h"
15 #include "core/fpdfapi/parser/cpdf_string.h"
16 #include "core/fxcrt/fx_stream.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "testing/invalid_seekable_read_stream.h"
19 #include "third_party/base/check.h"
20 #include "third_party/base/notreached.h"
21
22 namespace {
23
24 class TestReadValidator final : public CPDF_ReadValidator {
25 public:
26 CONSTRUCT_VIA_MAKE_RETAIN;
27
SimulateReadError()28 void SimulateReadError() { ReadBlockAtOffset({}, 0); }
29
30 private:
TestReadValidator()31 TestReadValidator()
32 : CPDF_ReadValidator(pdfium::MakeRetain<InvalidSeekableReadStream>(100),
33 nullptr) {}
34 ~TestReadValidator() override = default;
35 };
36
37 class TestHolder final : public CPDF_IndirectObjectHolder {
38 public:
39 enum class ObjectState {
40 Unavailable,
41 Available,
42 };
TestHolder()43 TestHolder() : validator_(pdfium::MakeRetain<TestReadValidator>()) {}
44 ~TestHolder() override = default;
45
46 // CPDF_IndirectObjectHolder overrides:
ParseIndirectObject(uint32_t objnum)47 RetainPtr<CPDF_Object> ParseIndirectObject(uint32_t objnum) override {
48 auto it = objects_data_.find(objnum);
49 if (it == objects_data_.end())
50 return nullptr;
51
52 ObjectData& obj_data = it->second;
53 if (obj_data.state == ObjectState::Unavailable) {
54 validator_->SimulateReadError();
55 return nullptr;
56 }
57 return obj_data.object;
58 }
59
GetValidator()60 RetainPtr<CPDF_ReadValidator> GetValidator() { return validator_; }
61
AddObject(uint32_t objnum,RetainPtr<CPDF_Object> object,ObjectState state)62 void AddObject(uint32_t objnum,
63 RetainPtr<CPDF_Object> object,
64 ObjectState state) {
65 ObjectData object_data;
66 object_data.object = std::move(object);
67 object_data.state = state;
68 DCHECK(objects_data_.find(objnum) == objects_data_.end());
69 objects_data_[objnum] = std::move(object_data);
70 }
71
SetObjectState(uint32_t objnum,ObjectState state)72 void SetObjectState(uint32_t objnum, ObjectState state) {
73 auto it = objects_data_.find(objnum);
74 DCHECK(it != objects_data_.end());
75 ObjectData& obj_data = it->second;
76 obj_data.state = state;
77 }
78
GetTestObject(uint32_t objnum)79 CPDF_Object* GetTestObject(uint32_t objnum) {
80 auto it = objects_data_.find(objnum);
81 if (it == objects_data_.end())
82 return nullptr;
83 return it->second.object.Get();
84 }
85
86 private:
87 struct ObjectData {
88 RetainPtr<CPDF_Object> object;
89 ObjectState state = ObjectState::Unavailable;
90 };
91 std::map<uint32_t, ObjectData> objects_data_;
92 RetainPtr<TestReadValidator> validator_;
93 };
94
95 class CPDF_ObjectAvailFailOnExclude final : public CPDF_ObjectAvail {
96 public:
97 using CPDF_ObjectAvail::CPDF_ObjectAvail;
98 ~CPDF_ObjectAvailFailOnExclude() override = default;
ExcludeObject(const CPDF_Object * object) const99 bool ExcludeObject(const CPDF_Object* object) const override {
100 NOTREACHED();
101 return false;
102 }
103 };
104
105 class CPDF_ObjectAvailExcludeArray final : public CPDF_ObjectAvail {
106 public:
107 using CPDF_ObjectAvail::CPDF_ObjectAvail;
108 ~CPDF_ObjectAvailExcludeArray() override = default;
ExcludeObject(const CPDF_Object * object) const109 bool ExcludeObject(const CPDF_Object* object) const override {
110 return object->IsArray();
111 }
112 };
113
114 class CPDF_ObjectAvailExcludeTypeKey final : public CPDF_ObjectAvail {
115 public:
116 using CPDF_ObjectAvail::CPDF_ObjectAvail;
117 ~CPDF_ObjectAvailExcludeTypeKey() override = default;
ExcludeObject(const CPDF_Object * object) const118 bool ExcludeObject(const CPDF_Object* object) const override {
119 // The value of "Type" may be reference, and if it is not available, we can
120 // incorrect filter objects.
121 // In this case CPDF_ObjectAvail should wait availability of this item and
122 // call ExcludeObject again.
123 return object->IsDictionary() &&
124 object->GetDict()->GetByteStringFor("Type") == "Exclude me";
125 }
126 };
127
128 } // namespace
129
TEST(ObjectAvailTest,OneObject)130 TEST(ObjectAvailTest, OneObject) {
131 TestHolder holder;
132 holder.AddObject(1, pdfium::MakeRetain<CPDF_String>(nullptr, "string", false),
133 TestHolder::ObjectState::Unavailable);
134 CPDF_ObjectAvail avail(holder.GetValidator(), &holder, 1);
135 EXPECT_EQ(CPDF_DataAvail::kDataNotAvailable, avail.CheckAvail());
136 holder.SetObjectState(1, TestHolder::ObjectState::Available);
137 EXPECT_EQ(CPDF_DataAvail::kDataAvailable, avail.CheckAvail());
138 }
139
TEST(ObjectAvailTest,OneReferencedObject)140 TEST(ObjectAvailTest, OneReferencedObject) {
141 TestHolder holder;
142 holder.AddObject(1, pdfium::MakeRetain<CPDF_Reference>(&holder, 2),
143 TestHolder::ObjectState::Unavailable);
144 holder.AddObject(2, pdfium::MakeRetain<CPDF_String>(nullptr, "string", false),
145 TestHolder::ObjectState::Unavailable);
146 CPDF_ObjectAvail avail(holder.GetValidator(), &holder, 1);
147 EXPECT_EQ(CPDF_DataAvail::kDataNotAvailable, avail.CheckAvail());
148
149 holder.SetObjectState(1, TestHolder::ObjectState::Available);
150 EXPECT_EQ(CPDF_DataAvail::kDataNotAvailable, avail.CheckAvail());
151
152 holder.SetObjectState(2, TestHolder::ObjectState::Available);
153 EXPECT_EQ(CPDF_DataAvail::kDataAvailable, avail.CheckAvail());
154 }
155
TEST(ObjectAvailTest,CycledReferences)156 TEST(ObjectAvailTest, CycledReferences) {
157 TestHolder holder;
158 holder.AddObject(1, pdfium::MakeRetain<CPDF_Reference>(&holder, 2),
159 TestHolder::ObjectState::Unavailable);
160 holder.AddObject(2, pdfium::MakeRetain<CPDF_Reference>(&holder, 3),
161 TestHolder::ObjectState::Unavailable);
162 holder.AddObject(3, pdfium::MakeRetain<CPDF_Reference>(&holder, 1),
163 TestHolder::ObjectState::Unavailable);
164
165 CPDF_ObjectAvail avail(holder.GetValidator(), &holder, 1);
166 EXPECT_EQ(CPDF_DataAvail::kDataNotAvailable, avail.CheckAvail());
167
168 holder.SetObjectState(1, TestHolder::ObjectState::Available);
169 EXPECT_EQ(CPDF_DataAvail::kDataNotAvailable, avail.CheckAvail());
170
171 holder.SetObjectState(2, TestHolder::ObjectState::Available);
172 EXPECT_EQ(CPDF_DataAvail::kDataNotAvailable, avail.CheckAvail());
173
174 holder.SetObjectState(3, TestHolder::ObjectState::Available);
175 EXPECT_EQ(CPDF_DataAvail::kDataAvailable, avail.CheckAvail());
176 }
177
TEST(ObjectAvailTest,DoNotCheckParent)178 TEST(ObjectAvailTest, DoNotCheckParent) {
179 TestHolder holder;
180 holder.AddObject(1, pdfium::MakeRetain<CPDF_Dictionary>(),
181 TestHolder::ObjectState::Unavailable);
182 holder.AddObject(2, pdfium::MakeRetain<CPDF_Dictionary>(),
183 TestHolder::ObjectState::Unavailable);
184
185 holder.GetTestObject(2)->GetMutableDict()->SetNewFor<CPDF_Reference>(
186 "Parent", &holder, 1);
187
188 CPDF_ObjectAvail avail(holder.GetValidator(), &holder, 2);
189 EXPECT_EQ(CPDF_DataAvail::kDataNotAvailable, avail.CheckAvail());
190
191 holder.SetObjectState(2, TestHolder::ObjectState::Available);
192 // Object should be available in case when "Parent" object is unavailable.
193 EXPECT_EQ(CPDF_DataAvail::kDataAvailable, avail.CheckAvail());
194 }
195
TEST(ObjectAvailTest,Generic)196 TEST(ObjectAvailTest, Generic) {
197 TestHolder holder;
198 const uint32_t kDepth = 100;
199 for (uint32_t i = 1; i < kDepth; ++i) {
200 holder.AddObject(i, pdfium::MakeRetain<CPDF_Dictionary>(),
201 TestHolder::ObjectState::Unavailable);
202 // Add ref to next dictionary.
203 holder.GetTestObject(i)->GetMutableDict()->SetNewFor<CPDF_Reference>(
204 "Child", &holder, i + 1);
205 }
206 // Add final object
207 holder.AddObject(kDepth, pdfium::MakeRetain<CPDF_Dictionary>(),
208 TestHolder::ObjectState::Unavailable);
209
210 CPDF_ObjectAvail avail(holder.GetValidator(), &holder, 1);
211 for (uint32_t i = 1; i <= kDepth; ++i) {
212 EXPECT_EQ(CPDF_DataAvail::kDataNotAvailable, avail.CheckAvail());
213 holder.SetObjectState(i, TestHolder::ObjectState::Available);
214 }
215 EXPECT_EQ(CPDF_DataAvail::kDataAvailable, avail.CheckAvail());
216 }
217
TEST(ObjectAvailTest,NotExcludeRoot)218 TEST(ObjectAvailTest, NotExcludeRoot) {
219 TestHolder holder;
220 holder.AddObject(1, pdfium::MakeRetain<CPDF_Dictionary>(),
221 TestHolder::ObjectState::Available);
222 CPDF_ObjectAvailFailOnExclude avail(holder.GetValidator(), &holder, 1);
223 EXPECT_EQ(CPDF_DataAvail::kDataAvailable, avail.CheckAvail());
224 }
225
TEST(ObjectAvailTest,NotExcludeReferedRoot)226 TEST(ObjectAvailTest, NotExcludeReferedRoot) {
227 TestHolder holder;
228 holder.AddObject(1, pdfium::MakeRetain<CPDF_Reference>(&holder, 2),
229 TestHolder::ObjectState::Available);
230 holder.AddObject(2, pdfium::MakeRetain<CPDF_Dictionary>(),
231 TestHolder::ObjectState::Available);
232 CPDF_ObjectAvailFailOnExclude avail(holder.GetValidator(), &holder, 1);
233 EXPECT_EQ(CPDF_DataAvail::kDataAvailable, avail.CheckAvail());
234 }
235
TEST(ObjectAvailTest,Exclude)236 TEST(ObjectAvailTest, Exclude) {
237 TestHolder holder;
238 holder.AddObject(1, pdfium::MakeRetain<CPDF_Dictionary>(),
239 TestHolder::ObjectState::Available);
240 holder.GetTestObject(1)->GetMutableDict()->SetNewFor<CPDF_Reference>(
241 "ArrayRef", &holder, 2);
242 holder.AddObject(2, pdfium::MakeRetain<CPDF_Array>(),
243 TestHolder::ObjectState::Available);
244 holder.GetTestObject(2)->AsMutableArray()->AppendNew<CPDF_Reference>(&holder,
245 2);
246
247 // Add string, which is refered by array item. It is should not be checked.
248 holder.AddObject(
249 3,
250 pdfium::MakeRetain<CPDF_String>(nullptr, "Not available string", false),
251 TestHolder::ObjectState::Unavailable);
252 CPDF_ObjectAvailExcludeArray avail(holder.GetValidator(), &holder, 1);
253 EXPECT_EQ(CPDF_DataAvail::kDataAvailable, avail.CheckAvail());
254 }
255
TEST(ObjectAvailTest,ReadErrorOnExclude)256 TEST(ObjectAvailTest, ReadErrorOnExclude) {
257 TestHolder holder;
258 holder.AddObject(1, pdfium::MakeRetain<CPDF_Dictionary>(),
259 TestHolder::ObjectState::Available);
260 holder.GetTestObject(1)->GetMutableDict()->SetNewFor<CPDF_Reference>(
261 "DictRef", &holder, 2);
262 holder.AddObject(2, pdfium::MakeRetain<CPDF_Dictionary>(),
263 TestHolder::ObjectState::Available);
264
265 holder.GetTestObject(2)->GetMutableDict()->SetNewFor<CPDF_Reference>(
266 "Type", &holder, 3);
267 // The value of "Type" key is not available at start
268 holder.AddObject(
269 3, pdfium::MakeRetain<CPDF_String>(nullptr, "Exclude me", false),
270 TestHolder::ObjectState::Unavailable);
271
272 holder.GetTestObject(2)->GetMutableDict()->SetNewFor<CPDF_Reference>(
273 "OtherData", &holder, 4);
274 // Add string, which is referred by dictionary item. It is should not be
275 // checked, because the dictionary with it, should be skipped.
276 holder.AddObject(
277 4,
278 pdfium::MakeRetain<CPDF_String>(nullptr, "Not available string", false),
279 TestHolder::ObjectState::Unavailable);
280
281 CPDF_ObjectAvailExcludeTypeKey avail(holder.GetValidator(), &holder, 1);
282
283 EXPECT_EQ(CPDF_DataAvail::kDataNotAvailable, avail.CheckAvail());
284
285 // Make "Type" value object available.
286 holder.SetObjectState(3, TestHolder::ObjectState::Available);
287
288 // Now object should be available, although the object '4' is not available,
289 // because it is in skipped dictionary.
290 EXPECT_EQ(CPDF_DataAvail::kDataAvailable, avail.CheckAvail());
291 }
292
TEST(ObjectAvailTest,IgnoreNotExistsObject)293 TEST(ObjectAvailTest, IgnoreNotExistsObject) {
294 TestHolder holder;
295 holder.AddObject(1, pdfium::MakeRetain<CPDF_Dictionary>(),
296 TestHolder::ObjectState::Available);
297 holder.GetTestObject(1)->GetMutableDict()->SetNewFor<CPDF_Reference>(
298 "NotExistsObjRef", &holder, 2);
299 CPDF_ObjectAvail avail(holder.GetValidator(), &holder, 1);
300 // Now object should be available, although the object '2' is not exists. But
301 // all exists in file related data are checked.
302 EXPECT_EQ(CPDF_DataAvail::kDataAvailable, avail.CheckAvail());
303 }
304
TEST(ObjectAvailTest,CheckTwice)305 TEST(ObjectAvailTest, CheckTwice) {
306 TestHolder holder;
307 holder.AddObject(1, pdfium::MakeRetain<CPDF_String>(nullptr, "string", false),
308 TestHolder::ObjectState::Unavailable);
309
310 CPDF_ObjectAvail avail(holder.GetValidator(), &holder, 1);
311 EXPECT_EQ(avail.CheckAvail(), avail.CheckAvail());
312
313 holder.SetObjectState(1, TestHolder::ObjectState::Available);
314 EXPECT_EQ(avail.CheckAvail(), avail.CheckAvail());
315 }
316
TEST(ObjectAvailTest,SelfReferedInlinedObject)317 TEST(ObjectAvailTest, SelfReferedInlinedObject) {
318 TestHolder holder;
319 holder.AddObject(1, pdfium::MakeRetain<CPDF_Dictionary>(),
320 TestHolder::ObjectState::Available);
321
322 holder.GetTestObject(1)->GetMutableDict()->SetNewFor<CPDF_Reference>(
323 "Data", &holder, 2);
324 auto root =
325 holder.GetTestObject(1)->GetMutableDict()->SetNewFor<CPDF_Dictionary>(
326 "Dict");
327
328 root->SetNewFor<CPDF_Reference>("Self", &holder, 1);
329 holder.AddObject(2, pdfium::MakeRetain<CPDF_String>(nullptr, "Data", false),
330 TestHolder::ObjectState::Unavailable);
331
332 CPDF_ObjectAvail avail(holder.GetValidator(), &holder, root);
333 EXPECT_EQ(CPDF_DataAvail::kDataNotAvailable, avail.CheckAvail());
334
335 holder.SetObjectState(2, TestHolder::ObjectState::Available);
336 EXPECT_EQ(CPDF_DataAvail::kDataAvailable, avail.CheckAvail());
337 }
338