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