| // Copyright 2016 The PDFium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com | |
| #include "core/fpdfapi/parser/cpdf_parser.h" | |
| #include <stdint.h> | |
| #include <algorithm> | |
| #include <optional> | |
| #include <utility> | |
| #include <vector> | |
| #include "core/fpdfapi/parser/cpdf_array.h" | |
| #include "core/fpdfapi/parser/cpdf_crypto_handler.h" | |
| #include "core/fpdfapi/parser/cpdf_dictionary.h" | |
| #include "core/fpdfapi/parser/cpdf_document.h" | |
| #include "core/fpdfapi/parser/cpdf_linearized_header.h" | |
| #include "core/fpdfapi/parser/cpdf_number.h" | |
| #include "core/fpdfapi/parser/cpdf_object_stream.h" | |
| #include "core/fpdfapi/parser/cpdf_read_validator.h" | |
| #include "core/fpdfapi/parser/cpdf_reference.h" | |
| #include "core/fpdfapi/parser/cpdf_security_handler.h" | |
| #include "core/fpdfapi/parser/cpdf_stream.h" | |
| #include "core/fpdfapi/parser/cpdf_stream_acc.h" | |
| #include "core/fpdfapi/parser/cpdf_syntax_parser.h" | |
| #include "core/fpdfapi/parser/fpdf_parser_utility.h" | |
| #include "core/fxcrt/autorestorer.h" | |
| #include "core/fxcrt/check.h" | |
| #include "core/fxcrt/check_op.h" | |
| #include "core/fxcrt/containers/contains.h" | |
| #include "core/fxcrt/data_vector.h" | |
| #include "core/fxcrt/fx_extension.h" | |
| #include "core/fxcrt/fx_safe_types.h" | |
| #include "core/fxcrt/fx_string.h" | |
| #include "core/fxcrt/scoped_set_insertion.h" | |
| #include "core/fxcrt/span.h" | |
| using ObjectType = CPDF_CrossRefTable::ObjectType; | |
| using ObjectInfo = CPDF_CrossRefTable::ObjectInfo; | |
| namespace { | |
| // A limit on the size of the xref table. Theoretical limits are higher, but | |
| // this may be large enough in practice. The max size should always be 1 more | |
| // than the max object number. Thus the valid range is [1, kMaxXRefSize]. | |
| constexpr int32_t kMaxXRefSize = CPDF_Parser::kMaxObjectNumber + 1; | |
| // "%PDF-1.7\n" | |
| constexpr FX_FILESIZE kPDFHeaderSize = 9; | |
| // The required number of fields in a /W array in a cross-reference stream | |
| // dictionary. | |
| constexpr size_t kMinFieldCount = 3; | |
| // Trailers are inline. | |
| constexpr uint32_t kNoTrailerObjectNumber = 0; | |
| struct CrossRefStreamIndexEntry { | |
| uint32_t start_obj_num; | |
| uint32_t obj_count; | |
| }; | |
| std::optional<ObjectType> GetObjectTypeFromCrossRefStreamType( | |
| uint32_t cross_ref_stream_type) { | |
| switch (cross_ref_stream_type) { | |
| case 0: | |
| return ObjectType::kFree; | |
| case 1: | |
| return ObjectType::kNormal; | |
| case 2: | |
| return ObjectType::kCompressed; | |
| default: | |
| return std::nullopt; | |
| } | |
| } | |
| // Use the Get*XRefStreamEntry() functions below, instead of calling this | |
| // directly. | |
| uint32_t GetVarInt(pdfium::span<const uint8_t> input) { | |
| uint32_t result = 0; | |
| for (uint8_t c : input) { | |
| result = result * 256 + c; | |
| } | |
| return result; | |
| } | |
| // The following 3 functions retrieve variable length entries from | |
| // cross-reference streams, as described in ISO 32000-1:2008 table 18. There are | |
| // only 3 fields for any given entry. | |
| uint32_t GetFirstXRefStreamEntry(pdfium::span<const uint8_t> entry_span, | |
| pdfium::span<const uint32_t> field_widths) { | |
| return GetVarInt(entry_span.first(field_widths[0])); | |
| } | |
| uint32_t GetSecondXRefStreamEntry(pdfium::span<const uint8_t> entry_span, | |
| pdfium::span<const uint32_t> field_widths) { | |
| return GetVarInt(entry_span.subspan(field_widths[0], field_widths[1])); | |
| } | |
| uint32_t GetThirdXRefStreamEntry(pdfium::span<const uint8_t> entry_span, | |
| pdfium::span<const uint32_t> field_widths) { | |
| return GetVarInt( | |
| entry_span.subspan(field_widths[0] + field_widths[1], field_widths[2])); | |
| } | |
| std::vector<CrossRefStreamIndexEntry> GetCrossRefStreamIndices( | |
| const CPDF_Array* array, | |
| uint32_t size) { | |
| std::vector<CrossRefStreamIndexEntry> indices; | |
| if (array) { | |
| for (size_t i = 0; i < array->size() / 2; i++) { | |
| RetainPtr<const CPDF_Number> pStartNumObj = array->GetNumberAt(i * 2); | |
| if (!pStartNumObj) { | |
| continue; | |
| } | |
| RetainPtr<const CPDF_Number> pCountObj = array->GetNumberAt(i * 2 + 1); | |
| if (!pCountObj) { | |
| continue; | |
| } | |
| int nStartNum = pStartNumObj->GetInteger(); | |
| int nCount = pCountObj->GetInteger(); | |
| if (nStartNum < 0 || nCount <= 0) { | |
| continue; | |
| } | |
| indices.push_back( | |
| {static_cast<uint32_t>(nStartNum), static_cast<uint32_t>(nCount)}); | |
| } | |
| } | |
| if (indices.empty()) { | |
| indices.push_back({0, size}); | |
| } | |
| return indices; | |
| } | |
| std::vector<uint32_t> GetFieldWidths(const CPDF_Array* array) { | |
| std::vector<uint32_t> results; | |
| if (!array) { | |
| return results; | |
| } | |
| CPDF_ArrayLocker locker(array); | |
| for (const auto& obj : locker) { | |
| results.push_back(obj->GetInteger()); | |
| } | |
| return results; | |
| } | |
| class ObjectsHolderStub final : public CPDF_Parser::ParsedObjectsHolder { | |
| public: | |
| ObjectsHolderStub() = default; | |
| ~ObjectsHolderStub() override = default; | |
| bool TryInit() override { return true; } | |
| }; | |
| } // namespace | |
| CPDF_Parser::CPDF_Parser(ParsedObjectsHolder* holder) | |
| : objects_holder_(holder), | |
| cross_ref_table_(std::make_unique<CPDF_CrossRefTable>()) { | |
| if (!holder) { | |
| owned_objects_holder_ = std::make_unique<ObjectsHolderStub>(); | |
| objects_holder_ = owned_objects_holder_.get(); | |
| } | |
| } | |
| CPDF_Parser::CPDF_Parser() : CPDF_Parser(nullptr) {} | |
| CPDF_Parser::~CPDF_Parser() = default; | |
| uint32_t CPDF_Parser::GetLastObjNum() const { | |
| return cross_ref_table_->objects_info().empty() | |
| ? 0 | |
| : cross_ref_table_->objects_info().rbegin()->first; | |
| } | |
| bool CPDF_Parser::IsValidObjectNumber(uint32_t objnum) const { | |
| return objnum <= GetLastObjNum(); | |
| } | |
| FX_FILESIZE CPDF_Parser::GetObjectPositionOrZero(uint32_t objnum) const { | |
| const auto* info = cross_ref_table_->GetObjectInfo(objnum); | |
| return (info && info->type == ObjectType::kNormal) ? info->pos : 0; | |
| } | |
| bool CPDF_Parser::IsObjectFree(uint32_t objnum) const { | |
| DCHECK(IsValidObjectNumber(objnum)); | |
| const auto* info = cross_ref_table_->GetObjectInfo(objnum); | |
| return !info || info->type == ObjectType::kFree; | |
| } | |
| bool CPDF_Parser::InitSyntaxParser(RetainPtr<CPDF_ReadValidator> validator) { | |
| const std::optional<FX_FILESIZE> header_offset = GetHeaderOffset(validator); | |
| if (!header_offset.has_value()) { | |
| return false; | |
| } | |
| if (validator->GetSize() < header_offset.value() + kPDFHeaderSize) { | |
| return false; | |
| } | |
| syntax_ = std::make_unique<CPDF_SyntaxParser>(std::move(validator), | |
| header_offset.value()); | |
| return ParseFileVersion(); | |
| } | |
| bool CPDF_Parser::ParseFileVersion() { | |
| file_version_ = 0; | |
| uint8_t ch; | |
| if (!syntax_->GetCharAt(5, ch)) { | |
| return false; | |
| } | |
| wchar_t wch = static_cast<wchar_t>(ch); | |
| if (FXSYS_IsDecimalDigit(wch)) { | |
| file_version_ = FXSYS_DecimalCharToInt(wch) * 10; | |
| } | |
| if (!syntax_->GetCharAt(7, ch)) { | |
| return false; | |
| } | |
| wch = static_cast<wchar_t>(ch); | |
| if (FXSYS_IsDecimalDigit(wch)) { | |
| file_version_ += FXSYS_DecimalCharToInt(wch); | |
| } | |
| return true; | |
| } | |
| CPDF_Parser::Error CPDF_Parser::StartParse( | |
| RetainPtr<IFX_SeekableReadStream> pFileAccess, | |
| const ByteString& password) { | |
| if (!InitSyntaxParser(pdfium::MakeRetain<CPDF_ReadValidator>( | |
| std::move(pFileAccess), nullptr))) { | |
| return FORMAT_ERROR; | |
| } | |
| SetPassword(password); | |
| return StartParseInternal(); | |
| } | |
| CPDF_Parser::Error CPDF_Parser::StartParseInternal() { | |
| DCHECK(!has_parsed_); | |
| DCHECK(!xref_table_rebuilt_); | |
| has_parsed_ = true; | |
| xref_stream_ = false; | |
| last_xref_offset_ = ParseStartXRef(); | |
| if (last_xref_offset_ >= kPDFHeaderSize) { | |
| if (!LoadAllCrossRefTablesAndStreams(last_xref_offset_)) { | |
| if (!RebuildCrossRef()) { | |
| return FORMAT_ERROR; | |
| } | |
| xref_table_rebuilt_ = true; | |
| last_xref_offset_ = 0; | |
| } | |
| } else { | |
| if (!RebuildCrossRef()) { | |
| return FORMAT_ERROR; | |
| } | |
| xref_table_rebuilt_ = true; | |
| } | |
| Error eRet = SetEncryptHandler(); | |
| if (eRet != SUCCESS) { | |
| return eRet; | |
| } | |
| if (!GetRoot() || !objects_holder_->TryInit()) { | |
| if (xref_table_rebuilt_) { | |
| return FORMAT_ERROR; | |
| } | |
| ReleaseEncryptHandler(); | |
| if (!RebuildCrossRef()) { | |
| return FORMAT_ERROR; | |
| } | |
| eRet = SetEncryptHandler(); | |
| if (eRet != SUCCESS) { | |
| return eRet; | |
| } | |
| objects_holder_->TryInit(); | |
| if (!GetRoot()) { | |
| return FORMAT_ERROR; | |
| } | |
| } | |
| if (GetRootObjNum() == CPDF_Object::kInvalidObjNum) { | |
| ReleaseEncryptHandler(); | |
| if (!RebuildCrossRef() || GetRootObjNum() == CPDF_Object::kInvalidObjNum) { | |
| return FORMAT_ERROR; | |
| } | |
| eRet = SetEncryptHandler(); | |
| if (eRet != SUCCESS) { | |
| return eRet; | |
| } | |
| } | |
| if (security_handler_ && !security_handler_->IsMetadataEncrypted()) { | |
| RetainPtr<const CPDF_Reference> pMetadata = | |
| ToReference(GetRoot()->GetObjectFor("Metadata")); | |
| if (pMetadata) { | |
| metadata_objnum_ = pMetadata->GetRefObjNum(); | |
| } | |
| } | |
| return SUCCESS; | |
| } | |
| FX_FILESIZE CPDF_Parser::ParseStartXRef() { | |
| static constexpr auto kStartXRefKeyword = | |
| pdfium::span_from_cstring("startxref"); | |
| syntax_->SetPos(syntax_->GetDocumentSize() - kStartXRefKeyword.size()); | |
| if (!syntax_->BackwardsSearchToWord(ByteStringView(kStartXRefKeyword), | |
| 4096)) { | |
| return 0; | |
| } | |
| // Skip "startxref" keyword. | |
| syntax_->GetKeyword(); | |
| // Read XRef offset. | |
| const CPDF_SyntaxParser::WordResult xref_offset_result = | |
| syntax_->GetNextWord(); | |
| if (!xref_offset_result.is_number || xref_offset_result.word.IsEmpty()) { | |
| return 0; | |
| } | |
| const FX_SAFE_FILESIZE result = FXSYS_atoi64(xref_offset_result.word.c_str()); | |
| if (!result.IsValid() || result.ValueOrDie() >= syntax_->GetDocumentSize()) { | |
| return 0; | |
| } | |
| return result.ValueOrDie(); | |
| } | |
| CPDF_Parser::Error CPDF_Parser::SetEncryptHandler() { | |
| ReleaseEncryptHandler(); | |
| if (!GetTrailer()) { | |
| return FORMAT_ERROR; | |
| } | |
| RetainPtr<const CPDF_Dictionary> pEncryptDict = GetEncryptDict(); | |
| if (!pEncryptDict) { | |
| return SUCCESS; | |
| } | |
| if (pEncryptDict->GetNameFor("Filter") != "Standard") { | |
| return HANDLER_ERROR; | |
| } | |
| auto pSecurityHandler = pdfium::MakeRetain<CPDF_SecurityHandler>(); | |
| if (!pSecurityHandler->OnInit(pEncryptDict, GetIDArray(), GetPassword())) { | |
| return PASSWORD_ERROR; | |
| } | |
| security_handler_ = std::move(pSecurityHandler); | |
| return SUCCESS; | |
| } | |
| void CPDF_Parser::ReleaseEncryptHandler() { | |
| security_handler_.Reset(); | |
| } | |
| // Ideally, all the cross reference entries should be verified. | |
| // In reality, we rarely see well-formed cross references don't match | |
| // with the objects. crbug/602650 showed a case where object numbers | |
| // in the cross reference table are all off by one. | |
| bool CPDF_Parser::VerifyCrossRefTable() { | |
| for (const auto& it : cross_ref_table_->objects_info()) { | |
| if (it.second.pos <= 0) { | |
| continue; | |
| } | |
| // Find the first non-zero position. | |
| FX_FILESIZE SavedPos = syntax_->GetPos(); | |
| syntax_->SetPos(it.second.pos); | |
| CPDF_SyntaxParser::WordResult word_result = syntax_->GetNextWord(); | |
| syntax_->SetPos(SavedPos); | |
| if (!word_result.is_number || word_result.word.IsEmpty() || | |
| FXSYS_atoui(word_result.word.c_str()) != it.first) { | |
| // If the object number read doesn't match the one stored, | |
| // something is wrong with the cross reference table. | |
| return false; | |
| } | |
| break; | |
| } | |
| return true; | |
| } | |
| bool CPDF_Parser::LoadAllCrossRefTablesAndStreams(FX_FILESIZE xref_offset) { | |
| const bool is_xref_stream = !LoadCrossRefTable(xref_offset, /*skip=*/true); | |
| if (is_xref_stream) { | |
| // Use a copy of `xref_offset`, as LoadCrossRefStream() may change it. | |
| FX_FILESIZE xref_offset_copy = xref_offset; | |
| if (!LoadCrossRefStream(&xref_offset_copy, /*is_main_xref=*/true)) { | |
| return false; | |
| } | |
| // LoadCrossRefStream() sets the trailer when `is_main_xref` is true. | |
| // Thus no SetTrailer() call like the else-block below. Similarly, | |
| // LoadCrossRefStream() also calls SetObjectMapSize() itself, so no need to | |
| // call it again here. | |
| } else { | |
| RetainPtr<CPDF_Dictionary> trailer = LoadTrailer(); | |
| if (!trailer) { | |
| return false; | |
| } | |
| cross_ref_table_->SetTrailer(std::move(trailer), kNoTrailerObjectNumber); | |
| const int32_t xrefsize = GetTrailer()->GetDirectIntegerFor("Size"); | |
| if (xrefsize > 0 && xrefsize <= kMaxXRefSize) { | |
| cross_ref_table_->SetObjectMapSize(xrefsize); | |
| } | |
| } | |
| std::vector<FX_FILESIZE> xref_list; | |
| std::vector<FX_FILESIZE> xref_stream_list; | |
| if (is_xref_stream) { | |
| xref_list.push_back(0); | |
| xref_stream_list.push_back(xref_offset); | |
| } else { | |
| xref_list.push_back(xref_offset); | |
| xref_stream_list.push_back(GetTrailer()->GetDirectIntegerFor("XRefStm")); | |
| } | |
| if (!FindAllCrossReferenceTablesAndStream(xref_offset, xref_list, | |
| xref_stream_list)) { | |
| return false; | |
| } | |
| if (xref_list.front() > 0) { | |
| if (!LoadCrossRefTable(xref_list.front(), /*skip=*/false)) { | |
| return false; | |
| } | |
| if (!VerifyCrossRefTable()) { | |
| return false; | |
| } | |
| } | |
| // Cross reference table entries take precedence over cross reference stream | |
| // entries. So process the stream entries first and then give the cross | |
| // reference tables a chance to overwrite them. | |
| // | |
| // XRefStm entries should only be used in update sections, so skip | |
| // `xref_stream_list.front()`. | |
| // | |
| // See details in ISO 32000-1:2008, section 7.5.8.4. | |
| for (size_t i = 1; i < xref_list.size(); ++i) { | |
| if (xref_stream_list[i] > 0 && | |
| !LoadCrossRefStream(&xref_stream_list[i], /*is_main_xref=*/false)) { | |
| return false; | |
| } | |
| if (xref_list[i] > 0 && !LoadCrossRefTable(xref_list[i], /*skip=*/false)) { | |
| return false; | |
| } | |
| } | |
| if (is_xref_stream) { | |
| object_stream_map_.clear(); | |
| xref_stream_ = true; | |
| } | |
| return true; | |
| } | |
| bool CPDF_Parser::LoadLinearizedAllCrossRefTable(FX_FILESIZE main_xref_offset) { | |
| if (!LoadCrossRefTable(main_xref_offset, /*skip=*/false)) { | |
| return false; | |
| } | |
| RetainPtr<CPDF_Dictionary> main_trailer = LoadTrailer(); | |
| if (!main_trailer) { | |
| return false; | |
| } | |
| // GetTrailer() currently returns the first-page trailer. | |
| if (GetTrailer()->GetDirectIntegerFor("Size") == 0) { | |
| return false; | |
| } | |
| // Read /XRefStm from the first-page trailer. No need to read /Prev for the | |
| // first-page trailer, as the caller already did that and passed it in as | |
| // |main_xref_offset|. | |
| FX_FILESIZE xref_stm = GetTrailer()->GetDirectIntegerFor("XRefStm"); | |
| std::vector<FX_FILESIZE> xref_list{main_xref_offset}; | |
| std::vector<FX_FILESIZE> xref_stream_list{xref_stm}; | |
| // Merge the trailers. Now GetTrailer() returns the merged trailer, where | |
| // /Prev is from the main-trailer. | |
| cross_ref_table_ = CPDF_CrossRefTable::MergeUp( | |
| std::make_unique<CPDF_CrossRefTable>(std::move(main_trailer), | |
| kNoTrailerObjectNumber), | |
| std::move(cross_ref_table_)); | |
| if (!FindAllCrossReferenceTablesAndStream(main_xref_offset, xref_list, | |
| xref_stream_list)) { | |
| return false; | |
| } | |
| // Unlike LoadAllCrossRefTablesAndStreams(), the first XRefStm entry in | |
| // `xref_stream_list` should be processed. | |
| if (xref_stream_list[0] > 0 && | |
| !LoadCrossRefStream(&xref_stream_list[0], /*is_main_xref=*/false)) { | |
| return false; | |
| } | |
| // Cross reference table entries take precedence over cross reference stream | |
| // entries. So process the stream entries first and then give the cross | |
| // reference tables a chance to overwrite them. | |
| for (size_t i = 1; i < xref_list.size(); ++i) { | |
| if (xref_stream_list[i] > 0 && | |
| !LoadCrossRefStream(&xref_stream_list[i], /*is_main_xref=*/false)) { | |
| return false; | |
| } | |
| if (xref_list[i] > 0 && !LoadCrossRefTable(xref_list[i], /*skip=*/false)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool CPDF_Parser::ParseAndAppendCrossRefSubsectionData( | |
| uint32_t start_objnum, | |
| uint32_t count, | |
| std::vector<CrossRefObjData>* out_objects) { | |
| if (!count) { | |
| return true; | |
| } | |
| // Each entry shall be exactly 20 byte. | |
| // A sample entry looks like: | |
| // "0000000000 00007 f\r\n" | |
| static constexpr int32_t kEntrySize = 20; | |
| if (!out_objects) { | |
| FX_SAFE_FILESIZE pos = count; | |
| pos *= kEntrySize; | |
| pos += syntax_->GetPos(); | |
| if (!pos.IsValid()) { | |
| return false; | |
| } | |
| syntax_->SetPos(pos.ValueOrDie()); | |
| return true; | |
| } | |
| const size_t start_obj_index = out_objects->size(); | |
| FX_SAFE_SIZE_T new_size = start_obj_index; | |
| new_size += count; | |
| if (!new_size.IsValid()) { | |
| return false; | |
| } | |
| if (new_size.ValueOrDie() > kMaxXRefSize) { | |
| return false; | |
| } | |
| const size_t max_entries_in_file = syntax_->GetDocumentSize() / kEntrySize; | |
| if (new_size.ValueOrDie() > max_entries_in_file) { | |
| return false; | |
| } | |
| out_objects->resize(new_size.ValueOrDie()); | |
| DataVector<char> buf(1024 * kEntrySize + 1); | |
| buf.back() = '\0'; | |
| uint32_t entries_to_read = count; | |
| while (entries_to_read > 0) { | |
| const uint32_t entries_in_block = std::min(entries_to_read, 1024u); | |
| const uint32_t bytes_to_read = entries_in_block * kEntrySize; | |
| auto block_span = pdfium::span(buf).first(bytes_to_read); | |
| if (!syntax_->ReadBlock(pdfium::as_writable_bytes(block_span))) { | |
| return false; | |
| } | |
| for (uint32_t i = 0; i < entries_in_block; i++) { | |
| uint32_t iObjectIndex = count - entries_to_read + i; | |
| CrossRefObjData& obj_data = | |
| (*out_objects)[start_obj_index + iObjectIndex]; | |
| const uint32_t objnum = start_objnum + iObjectIndex; | |
| obj_data.obj_num = objnum; | |
| ObjectInfo& info = obj_data.info; | |
| pdfium::span<const char> pEntry = | |
| pdfium::span(buf).subspan(i * kEntrySize); | |
| if (pEntry[17] == 'f') { | |
| info.pos = 0; | |
| info.type = ObjectType::kFree; | |
| } else { | |
| const FX_SAFE_FILESIZE offset = FXSYS_atoi64(pEntry.data()); | |
| if (!offset.IsValid()) { | |
| return false; | |
| } | |
| if (offset.ValueOrDie() == 0) { | |
| for (int32_t c = 0; c < 10; c++) { | |
| if (!FXSYS_IsDecimalDigit(pEntry[c])) { | |
| return false; | |
| } | |
| } | |
| } | |
| info.pos = offset.ValueOrDie(); | |
| // TODO(art-snake): The info.gennum is uint16_t, but version may be | |
| // greater than max<uint16_t>. Need to solve this issue. | |
| const int32_t version = | |
| StringToInt(ByteStringView(pEntry.subspan<11u>())); | |
| info.gennum = version; | |
| info.type = ObjectType::kNormal; | |
| } | |
| } | |
| entries_to_read -= entries_in_block; | |
| } | |
| return true; | |
| } | |
| bool CPDF_Parser::ParseCrossRefTable( | |
| std::vector<CrossRefObjData>* out_objects) { | |
| if (out_objects) { | |
| out_objects->clear(); | |
| } | |
| if (syntax_->GetKeyword() != "xref") { | |
| return false; | |
| } | |
| std::vector<CrossRefObjData> result_objects; | |
| while (true) { | |
| FX_FILESIZE saved_pos = syntax_->GetPos(); | |
| CPDF_SyntaxParser::WordResult word_result = syntax_->GetNextWord(); | |
| const ByteString& word = word_result.word; | |
| if (word.IsEmpty()) { | |
| return false; | |
| } | |
| if (!word_result.is_number) { | |
| syntax_->SetPos(saved_pos); | |
| break; | |
| } | |
| uint32_t start_objnum = FXSYS_atoui(word.c_str()); | |
| if (start_objnum > kMaxObjectNumber) { | |
| return false; | |
| } | |
| uint32_t count = syntax_->GetDirectNum(); | |
| syntax_->ToNextWord(); | |
| if (!ParseAndAppendCrossRefSubsectionData( | |
| start_objnum, count, out_objects ? &result_objects : nullptr)) { | |
| return false; | |
| } | |
| } | |
| if (out_objects) { | |
| *out_objects = std::move(result_objects); | |
| } | |
| return true; | |
| } | |
| bool CPDF_Parser::LoadCrossRefTable(FX_FILESIZE pos, bool skip) { | |
| syntax_->SetPos(pos); | |
| std::vector<CrossRefObjData> objects; | |
| if (!ParseCrossRefTable(skip ? nullptr : &objects)) { | |
| return false; | |
| } | |
| MergeCrossRefObjectsData(objects); | |
| return true; | |
| } | |
| void CPDF_Parser::MergeCrossRefObjectsData( | |
| const std::vector<CrossRefObjData>& objects) { | |
| for (const auto& obj : objects) { | |
| switch (obj.info.type) { | |
| case ObjectType::kFree: | |
| if (obj.info.gennum > 0) { | |
| cross_ref_table_->SetFree(obj.obj_num, obj.info.gennum); | |
| } | |
| break; | |
| case ObjectType::kNormal: | |
| cross_ref_table_->AddNormal(obj.obj_num, obj.info.gennum, | |
| obj.info.is_object_stream_flag, | |
| obj.info.pos); | |
| break; | |
| case ObjectType::kCompressed: | |
| cross_ref_table_->AddCompressed(obj.obj_num, obj.info.archive.obj_num, | |
| obj.info.archive.obj_index); | |
| break; | |
| } | |
| } | |
| } | |
| bool CPDF_Parser::FindAllCrossReferenceTablesAndStream( | |
| FX_FILESIZE main_xref_offset, | |
| std::vector<FX_FILESIZE>& xref_list, | |
| std::vector<FX_FILESIZE>& xref_stream_list) { | |
| std::set<FX_FILESIZE> seen_xref_offset{main_xref_offset}; | |
| // When the trailer doesn't have Prev entry or Prev entry value is not | |
| // numerical, GetDirectInteger() returns 0. Loading will end. | |
| FX_FILESIZE xref_offset = GetTrailer()->GetDirectIntegerFor("Prev"); | |
| while (xref_offset > 0) { | |
| // Check for circular references. | |
| if (pdfium::Contains(seen_xref_offset, xref_offset)) { | |
| return false; | |
| } | |
| seen_xref_offset.insert(xref_offset); | |
| // Use a copy of `xref_offset`, as LoadCrossRefStream() may change it. | |
| FX_FILESIZE xref_offset_copy = xref_offset; | |
| if (LoadCrossRefStream(&xref_offset_copy, /*is_main_xref=*/false)) { | |
| // Since `xref_offset` points to a cross reference stream, mark it | |
| // accordingly. | |
| xref_list.insert(xref_list.begin(), 0); | |
| xref_stream_list.insert(xref_stream_list.begin(), xref_offset); | |
| xref_offset = xref_offset_copy; | |
| // On success, LoadCrossRefStream() called CPDF_CrossRefTable::MergeUp() | |
| // when `is_main_xref` is false. Thus no explicit call here. | |
| } else { | |
| // SLOW ... | |
| LoadCrossRefTable(xref_offset, /*skip=*/true); | |
| RetainPtr<CPDF_Dictionary> trailer_dict = LoadTrailer(); | |
| if (!trailer_dict) { | |
| return false; | |
| } | |
| // The trailer for cross reference tables may point to a cross reference | |
| // stream as well. | |
| xref_list.insert(xref_list.begin(), xref_offset); | |
| xref_stream_list.insert(xref_stream_list.begin(), | |
| trailer_dict->GetIntegerFor("XRefStm")); | |
| xref_offset = trailer_dict->GetDirectIntegerFor("Prev"); | |
| // SLOW ... | |
| cross_ref_table_ = CPDF_CrossRefTable::MergeUp( | |
| std::make_unique<CPDF_CrossRefTable>(std::move(trailer_dict), | |
| kNoTrailerObjectNumber), | |
| std::move(cross_ref_table_)); | |
| } | |
| } | |
| return true; | |
| } | |
| bool CPDF_Parser::RebuildCrossRef() { | |
| auto cross_ref_table = std::make_unique<CPDF_CrossRefTable>(); | |
| const uint32_t kBufferSize = 4096; | |
| syntax_->SetReadBufferSize(kBufferSize); | |
| syntax_->SetPos(0); | |
| std::vector<std::pair<uint32_t, FX_FILESIZE>> numbers; | |
| for (CPDF_SyntaxParser::WordResult result = syntax_->GetNextWord(); | |
| !result.word.IsEmpty(); result = syntax_->GetNextWord()) { | |
| const ByteString& word = result.word; | |
| if (result.is_number) { | |
| numbers.emplace_back(FXSYS_atoui(word.c_str()), | |
| syntax_->GetPos() - word.GetLength()); | |
| if (numbers.size() > 2u) { | |
| numbers.erase(numbers.begin()); | |
| } | |
| continue; | |
| } | |
| if (word == "(") { | |
| syntax_->ReadString(); | |
| } else if (word == "<") { | |
| syntax_->ReadHexString(); | |
| } else if (word == "trailer") { | |
| RetainPtr<CPDF_Object> pTrailer = syntax_->GetObjectBody(nullptr); | |
| if (pTrailer) { | |
| CPDF_Stream* stream_trailer = pTrailer->AsMutableStream(); | |
| // Grab the object number from `pTrailer` before potentially calling | |
| // std::move(pTrailer) below. | |
| const uint32_t trailer_object_number = pTrailer->GetObjNum(); | |
| RetainPtr<CPDF_Dictionary> trailer_dict = | |
| stream_trailer ? stream_trailer->GetMutableDict() | |
| : ToDictionary(std::move(pTrailer)); | |
| cross_ref_table = CPDF_CrossRefTable::MergeUp( | |
| std::move(cross_ref_table), | |
| std::make_unique<CPDF_CrossRefTable>(std::move(trailer_dict), | |
| trailer_object_number)); | |
| } | |
| } else if (word == "obj" && numbers.size() == 2u) { | |
| const FX_FILESIZE obj_pos = numbers[0].second; | |
| const uint32_t obj_num = numbers[0].first; | |
| const uint32_t gen_num = numbers[1].first; | |
| syntax_->SetPos(obj_pos); | |
| RetainPtr<CPDF_Stream> pStream = ToStream(syntax_->GetIndirectObject( | |
| nullptr, CPDF_SyntaxParser::ParseType::kStrict)); | |
| if (pStream && pStream->GetDict()->GetNameFor("Type") == "XRef") { | |
| cross_ref_table = CPDF_CrossRefTable::MergeUp( | |
| std::move(cross_ref_table), | |
| std::make_unique<CPDF_CrossRefTable>( | |
| ToDictionary(pStream->GetDict()->Clone()), | |
| pStream->GetObjNum())); | |
| } | |
| if (obj_num <= kMaxObjectNumber) { | |
| cross_ref_table->AddNormal(obj_num, gen_num, /*is_object_stream=*/false, | |
| obj_pos); | |
| const auto object_stream = | |
| CPDF_ObjectStream::Create(std::move(pStream)); | |
| if (object_stream) { | |
| const auto& object_info = object_stream->object_info(); | |
| for (size_t i = 0; i < object_info.size(); ++i) { | |
| const auto& info = object_info[i]; | |
| if (info.obj_num <= kMaxObjectNumber) { | |
| cross_ref_table->AddCompressed(info.obj_num, obj_num, i); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| numbers.clear(); | |
| } | |
| cross_ref_table_ = CPDF_CrossRefTable::MergeUp(std::move(cross_ref_table_), | |
| std::move(cross_ref_table)); | |
| // Resore default buffer size. | |
| syntax_->SetReadBufferSize(CPDF_Stream::kFileBufSize); | |
| return GetTrailer() && !cross_ref_table_->objects_info().empty(); | |
| } | |
| bool CPDF_Parser::LoadCrossRefStream(FX_FILESIZE* pos, bool is_main_xref) { | |
| RetainPtr<const CPDF_Stream> pStream = | |
| ToStream(ParseIndirectObjectAt(*pos, 0)); | |
| if (!pStream || !pStream->GetObjNum()) { | |
| return false; | |
| } | |
| RetainPtr<const CPDF_Dictionary> dict = pStream->GetDict(); | |
| const int32_t prev = dict->GetIntegerFor("Prev"); | |
| if (prev < 0) { | |
| return false; | |
| } | |
| // If /Size is negative or way too big, then it is obvious wrong. | |
| // Immediately reject these cases, so `cross_ref_table_` does not get into a | |
| // bad state. | |
| // | |
| // For a /Size of 0 or other issues, just ignore them. See comments in the | |
| // for-loop below. | |
| const int32_t size = dict->GetIntegerFor("Size"); | |
| if (size < 0 || size > kMaxXRefSize) { | |
| return false; | |
| } | |
| *pos = prev; | |
| auto new_cross_ref_table = std::make_unique<CPDF_CrossRefTable>( | |
| /*trailer=*/ToDictionary(dict->Clone()), | |
| /*trailer_object_number=*/pStream->GetObjNum()); | |
| if (is_main_xref) { | |
| cross_ref_table_ = std::move(new_cross_ref_table); | |
| cross_ref_table_->SetObjectMapSize(size); | |
| } else { | |
| cross_ref_table_ = CPDF_CrossRefTable::MergeUp( | |
| std::move(new_cross_ref_table), std::move(cross_ref_table_)); | |
| } | |
| std::vector<CrossRefStreamIndexEntry> indices = | |
| GetCrossRefStreamIndices(dict->GetArrayFor("Index").Get(), size); | |
| std::vector<uint32_t> field_widths = | |
| GetFieldWidths(dict->GetArrayFor("W").Get()); | |
| if (field_widths.size() < kMinFieldCount) { | |
| return false; | |
| } | |
| FX_SAFE_UINT32 dwAccWidth; | |
| for (uint32_t width : field_widths) { | |
| dwAccWidth += width; | |
| } | |
| if (!dwAccWidth.IsValid()) { | |
| return false; | |
| } | |
| uint32_t total_width = dwAccWidth.ValueOrDie(); | |
| auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(pStream); | |
| pAcc->LoadAllDataFiltered(); | |
| pdfium::span<const uint8_t> data_span = pAcc->GetSpan(); | |
| uint32_t segindex = 0; | |
| for (const auto& index : indices) { | |
| FX_SAFE_UINT32 seg_end = segindex; | |
| seg_end += index.obj_count; | |
| seg_end *= total_width; | |
| if (!seg_end.IsValid() || seg_end.ValueOrDie() > data_span.size()) { | |
| continue; | |
| } | |
| pdfium::span<const uint8_t> seg_span = data_span.subspan( | |
| segindex * total_width, index.obj_count * total_width); | |
| FX_SAFE_UINT32 safe_new_size = index.start_obj_num; | |
| safe_new_size += index.obj_count; | |
| if (!safe_new_size.IsValid()) { | |
| continue; | |
| } | |
| // Until SetObjectMapSize() below has been called by a prior loop iteration, | |
| // `current_size` is based on the /Size value parsed in | |
| // LoadCrossRefStream(). PDFs may not always have the correct /Size. In this | |
| // case, other PDF implementations ignore the incorrect size, and PDFium | |
| // also ignores incorrect size in trailers for cross reference tables. | |
| const uint32_t current_size = | |
| cross_ref_table_->objects_info().empty() ? 0 : GetLastObjNum() + 1; | |
| // So allow `new_size` to be greater than `current_size`, but avoid going | |
| // over `kMaxXRefSize`. This works just fine because the loop below checks | |
| // against `kMaxObjectNumber`, and the two "max" constants are in sync. | |
| const uint32_t new_size = | |
| std::min<uint32_t>(safe_new_size.ValueOrDie(), kMaxXRefSize); | |
| if (new_size > current_size) { | |
| cross_ref_table_->SetObjectMapSize(new_size); | |
| } | |
| for (uint32_t i = 0; i < index.obj_count; ++i) { | |
| const uint32_t obj_num = index.start_obj_num + i; | |
| if (obj_num > kMaxObjectNumber) { | |
| break; | |
| } | |
| ProcessCrossRefStreamEntry(seg_span.subspan(i * total_width, total_width), | |
| field_widths, obj_num); | |
| } | |
| segindex += index.obj_count; | |
| } | |
| return true; | |
| } | |
| void CPDF_Parser::ProcessCrossRefStreamEntry( | |
| pdfium::span<const uint8_t> entry_span, | |
| pdfium::span<const uint32_t> field_widths, | |
| uint32_t obj_num) { | |
| DCHECK_GE(field_widths.size(), kMinFieldCount); | |
| ObjectType type; | |
| if (field_widths[0]) { | |
| const uint32_t cross_ref_stream_obj_type = | |
| GetFirstXRefStreamEntry(entry_span, field_widths); | |
| std::optional<ObjectType> maybe_type = | |
| GetObjectTypeFromCrossRefStreamType(cross_ref_stream_obj_type); | |
| if (!maybe_type.has_value()) { | |
| return; | |
| } | |
| type = maybe_type.value(); | |
| } else { | |
| // Per ISO 32000-1:2008 table 17, use the default value of 1 for the xref | |
| // stream entry when it is not specified. The `type` assignment is the | |
| // equivalent to calling GetObjectTypeFromCrossRefStreamType(1). | |
| type = ObjectType::kNormal; | |
| } | |
| if (type == ObjectType::kFree) { | |
| const uint32_t gen_num = GetThirdXRefStreamEntry(entry_span, field_widths); | |
| if (pdfium::IsValueInRangeForNumericType<uint16_t>(gen_num)) { | |
| cross_ref_table_->SetFree(obj_num, gen_num); | |
| } | |
| return; | |
| } | |
| if (type == ObjectType::kNormal) { | |
| const uint32_t offset = GetSecondXRefStreamEntry(entry_span, field_widths); | |
| const uint32_t gen_num = GetThirdXRefStreamEntry(entry_span, field_widths); | |
| if (pdfium::IsValueInRangeForNumericType<FX_FILESIZE>(offset) && | |
| pdfium::IsValueInRangeForNumericType<uint16_t>(gen_num)) { | |
| cross_ref_table_->AddNormal(obj_num, gen_num, /*is_object_stream=*/false, | |
| offset); | |
| } | |
| return; | |
| } | |
| DCHECK_EQ(type, ObjectType::kCompressed); | |
| const uint32_t archive_obj_num = | |
| GetSecondXRefStreamEntry(entry_span, field_widths); | |
| if (!IsValidObjectNumber(archive_obj_num)) { | |
| return; | |
| } | |
| const uint32_t archive_obj_index = | |
| GetThirdXRefStreamEntry(entry_span, field_widths); | |
| cross_ref_table_->AddCompressed(obj_num, archive_obj_num, archive_obj_index); | |
| } | |
| RetainPtr<const CPDF_Array> CPDF_Parser::GetIDArray() const { | |
| return GetTrailer() ? GetTrailer()->GetArrayFor("ID") : nullptr; | |
| } | |
| RetainPtr<const CPDF_Dictionary> CPDF_Parser::GetRoot() const { | |
| RetainPtr<CPDF_Object> obj = | |
| objects_holder_->GetOrParseIndirectObject(GetRootObjNum()); | |
| return obj ? obj->GetDict() : nullptr; | |
| } | |
| RetainPtr<const CPDF_Dictionary> CPDF_Parser::GetEncryptDict() const { | |
| if (!GetTrailer()) { | |
| return nullptr; | |
| } | |
| RetainPtr<const CPDF_Object> pEncryptObj = | |
| GetTrailer()->GetObjectFor("Encrypt"); | |
| if (!pEncryptObj) { | |
| return nullptr; | |
| } | |
| if (pEncryptObj->IsDictionary()) { | |
| return pdfium::WrapRetain(pEncryptObj->AsDictionary()); | |
| } | |
| if (pEncryptObj->IsReference()) { | |
| return ToDictionary(objects_holder_->GetOrParseIndirectObject( | |
| pEncryptObj->AsReference()->GetRefObjNum())); | |
| } | |
| return nullptr; | |
| } | |
| ByteString CPDF_Parser::GetEncodedPassword() const { | |
| return GetSecurityHandler()->GetEncodedPassword(GetPassword().AsStringView()); | |
| } | |
| const CPDF_Dictionary* CPDF_Parser::GetTrailer() const { | |
| return cross_ref_table_->trailer(); | |
| } | |
| CPDF_Dictionary* CPDF_Parser::GetMutableTrailerForTesting() { | |
| return cross_ref_table_->GetMutableTrailerForTesting(); | |
| } | |
| uint32_t CPDF_Parser::GetTrailerObjectNumber() const { | |
| return cross_ref_table_->trailer_object_number(); | |
| } | |
| RetainPtr<CPDF_Dictionary> CPDF_Parser::GetCombinedTrailer() const { | |
| return cross_ref_table_->trailer() | |
| ? ToDictionary(cross_ref_table_->trailer()->Clone()) | |
| : RetainPtr<CPDF_Dictionary>(); | |
| } | |
| uint32_t CPDF_Parser::GetInfoObjNum() const { | |
| RetainPtr<const CPDF_Reference> pRef = | |
| ToReference(cross_ref_table_->trailer() | |
| ? cross_ref_table_->trailer()->GetObjectFor("Info") | |
| : nullptr); | |
| return pRef ? pRef->GetRefObjNum() : CPDF_Object::kInvalidObjNum; | |
| } | |
| uint32_t CPDF_Parser::GetRootObjNum() const { | |
| RetainPtr<const CPDF_Reference> pRef = | |
| ToReference(cross_ref_table_->trailer() | |
| ? cross_ref_table_->trailer()->GetObjectFor("Root") | |
| : nullptr); | |
| return pRef ? pRef->GetRefObjNum() : CPDF_Object::kInvalidObjNum; | |
| } | |
| RetainPtr<CPDF_Object> CPDF_Parser::ParseIndirectObject(uint32_t objnum) { | |
| if (!IsValidObjectNumber(objnum)) { | |
| return nullptr; | |
| } | |
| // Prevent circular parsing the same object. | |
| if (pdfium::Contains(parsing_obj_nums_, objnum)) { | |
| return nullptr; | |
| } | |
| ScopedSetInsertion local_insert(&parsing_obj_nums_, objnum); | |
| const auto* info = cross_ref_table_->GetObjectInfo(objnum); | |
| if (!info) { | |
| return nullptr; | |
| } | |
| switch (info->type) { | |
| case ObjectType::kFree: { | |
| return nullptr; | |
| } | |
| case ObjectType::kNormal: { | |
| if (info->pos <= 0) { | |
| return nullptr; | |
| } | |
| return ParseIndirectObjectAt(info->pos, objnum); | |
| } | |
| case ObjectType::kCompressed: { | |
| const auto* obj_stream = GetObjectStream(info->archive.obj_num); | |
| if (!obj_stream) { | |
| return nullptr; | |
| } | |
| return obj_stream->ParseObject(objects_holder_, objnum, | |
| info->archive.obj_index); | |
| } | |
| } | |
| } | |
| const CPDF_ObjectStream* CPDF_Parser::GetObjectStream(uint32_t object_number) { | |
| // Prevent circular parsing the same object. | |
| if (pdfium::Contains(parsing_obj_nums_, object_number)) { | |
| return nullptr; | |
| } | |
| auto it = object_stream_map_.find(object_number); | |
| if (it != object_stream_map_.end()) { | |
| return it->second.get(); | |
| } | |
| const auto* info = cross_ref_table_->GetObjectInfo(object_number); | |
| if (!info || !info->is_object_stream_flag) { | |
| return nullptr; | |
| } | |
| const FX_FILESIZE object_pos = info->pos; | |
| if (object_pos <= 0) { | |
| return nullptr; | |
| } | |
| // Keep track of `object_number` before doing more parsing. | |
| ScopedSetInsertion local_insert(&parsing_obj_nums_, object_number); | |
| RetainPtr<CPDF_Object> object = | |
| ParseIndirectObjectAt(object_pos, object_number); | |
| if (!object) { | |
| return nullptr; | |
| } | |
| std::unique_ptr<CPDF_ObjectStream> objs_stream = | |
| CPDF_ObjectStream::Create(ToStream(object)); | |
| const CPDF_ObjectStream* result = objs_stream.get(); | |
| object_stream_map_[object_number] = std::move(objs_stream); | |
| return result; | |
| } | |
| RetainPtr<CPDF_Object> CPDF_Parser::ParseIndirectObjectAt(FX_FILESIZE pos, | |
| uint32_t objnum) { | |
| const FX_FILESIZE saved_pos = syntax_->GetPos(); | |
| syntax_->SetPos(pos); | |
| auto result = syntax_->GetIndirectObject( | |
| objects_holder_, CPDF_SyntaxParser::ParseType::kLoose); | |
| syntax_->SetPos(saved_pos); | |
| if (result && objnum && result->GetObjNum() != objnum) { | |
| return nullptr; | |
| } | |
| const bool should_decrypt = security_handler_ && | |
| security_handler_->GetCryptoHandler() && | |
| objnum != metadata_objnum_; | |
| if (should_decrypt && | |
| !security_handler_->GetCryptoHandler()->DecryptObjectTree(result)) { | |
| return nullptr; | |
| } | |
| return result; | |
| } | |
| FX_FILESIZE CPDF_Parser::GetDocumentSize() const { | |
| return syntax_->GetDocumentSize(); | |
| } | |
| uint32_t CPDF_Parser::GetFirstPageNo() const { | |
| return linearized_ ? linearized_->GetFirstPageNo() : 0; | |
| } | |
| void CPDF_Parser::SetLinearizedHeaderForTesting( | |
| std::unique_ptr<CPDF_LinearizedHeader> pLinearized) { | |
| linearized_ = std::move(pLinearized); | |
| } | |
| RetainPtr<CPDF_Dictionary> CPDF_Parser::LoadTrailer() { | |
| if (syntax_->GetKeyword() != "trailer") { | |
| return nullptr; | |
| } | |
| return ToDictionary(syntax_->GetObjectBody(objects_holder_)); | |
| } | |
| uint32_t CPDF_Parser::GetPermissions(bool get_owner_perms) const { | |
| return security_handler_ ? security_handler_->GetPermissions(get_owner_perms) | |
| : 0xFFFFFFFF; | |
| } | |
| std::unique_ptr<CPDF_LinearizedHeader> CPDF_Parser::ParseLinearizedHeader() { | |
| return CPDF_LinearizedHeader::Parse(syntax_.get()); | |
| } | |
| CPDF_Parser::Error CPDF_Parser::StartLinearizedParse( | |
| RetainPtr<CPDF_ReadValidator> validator, | |
| const ByteString& password) { | |
| DCHECK(!has_parsed_); | |
| DCHECK(!xref_table_rebuilt_); | |
| SetPassword(password); | |
| xref_stream_ = false; | |
| last_xref_offset_ = 0; | |
| if (!InitSyntaxParser(std::move(validator))) { | |
| return FORMAT_ERROR; | |
| } | |
| linearized_ = ParseLinearizedHeader(); | |
| if (!linearized_) { | |
| return StartParseInternal(); | |
| } | |
| has_parsed_ = true; | |
| last_xref_offset_ = linearized_->GetLastXRefOffset(); | |
| FX_FILESIZE dwFirstXRefOffset = last_xref_offset_; | |
| const bool loaded_xref_table = | |
| LoadCrossRefTable(dwFirstXRefOffset, /*skip=*/false); | |
| if (!loaded_xref_table && | |
| !LoadCrossRefStream(&dwFirstXRefOffset, /*is_main_xref=*/true)) { | |
| if (!RebuildCrossRef()) { | |
| return FORMAT_ERROR; | |
| } | |
| xref_table_rebuilt_ = true; | |
| last_xref_offset_ = 0; | |
| } | |
| if (loaded_xref_table) { | |
| RetainPtr<CPDF_Dictionary> trailer = LoadTrailer(); | |
| if (!trailer) { | |
| return SUCCESS; | |
| } | |
| cross_ref_table_->SetTrailer(std::move(trailer), kNoTrailerObjectNumber); | |
| const int32_t xrefsize = GetTrailer()->GetDirectIntegerFor("Size"); | |
| if (xrefsize > 0) { | |
| // Check if `xrefsize` is correct. If it is incorrect, give up and rebuild | |
| // the xref table. | |
| const uint32_t expected_last_obj_num = xrefsize - 1; | |
| if (GetLastObjNum() != expected_last_obj_num && !RebuildCrossRef()) { | |
| return FORMAT_ERROR; | |
| } | |
| } | |
| } | |
| Error eRet = SetEncryptHandler(); | |
| if (eRet != SUCCESS) { | |
| return eRet; | |
| } | |
| if (!GetRoot() || !objects_holder_->TryInit()) { | |
| if (xref_table_rebuilt_) { | |
| return FORMAT_ERROR; | |
| } | |
| ReleaseEncryptHandler(); | |
| if (!RebuildCrossRef()) { | |
| return FORMAT_ERROR; | |
| } | |
| eRet = SetEncryptHandler(); | |
| if (eRet != SUCCESS) { | |
| return eRet; | |
| } | |
| objects_holder_->TryInit(); | |
| if (!GetRoot()) { | |
| return FORMAT_ERROR; | |
| } | |
| } | |
| if (GetRootObjNum() == CPDF_Object::kInvalidObjNum) { | |
| ReleaseEncryptHandler(); | |
| if (!RebuildCrossRef() || GetRootObjNum() == CPDF_Object::kInvalidObjNum) { | |
| return FORMAT_ERROR; | |
| } | |
| eRet = SetEncryptHandler(); | |
| if (eRet != SUCCESS) { | |
| return eRet; | |
| } | |
| } | |
| if (security_handler_ && security_handler_->IsMetadataEncrypted()) { | |
| RetainPtr<const CPDF_Reference> pMetadata = | |
| ToReference(GetRoot()->GetObjectFor("Metadata")); | |
| if (pMetadata) { | |
| metadata_objnum_ = pMetadata->GetRefObjNum(); | |
| } | |
| } | |
| return SUCCESS; | |
| } | |
| bool CPDF_Parser::LoadLinearizedAllCrossRefStream( | |
| FX_FILESIZE main_xref_offset) { | |
| FX_FILESIZE xref_offset = main_xref_offset; | |
| if (!LoadCrossRefStream(&xref_offset, /*is_main_xref=*/false)) { | |
| return false; | |
| } | |
| std::set<FX_FILESIZE> seen_xref_offset; | |
| while (xref_offset) { | |
| seen_xref_offset.insert(xref_offset); | |
| if (!LoadCrossRefStream(&xref_offset, /*is_main_xref=*/false)) { | |
| return false; | |
| } | |
| // Check for circular references. | |
| if (pdfium::Contains(seen_xref_offset, xref_offset)) { | |
| return false; | |
| } | |
| } | |
| object_stream_map_.clear(); | |
| xref_stream_ = true; | |
| return true; | |
| } | |
| CPDF_Parser::Error CPDF_Parser::LoadLinearizedMainXRefTable() { | |
| const FX_SAFE_FILESIZE prev = GetTrailer()->GetIntegerFor("Prev"); | |
| const FX_FILESIZE main_xref_offset = prev.ValueOrDefault(-1); | |
| if (main_xref_offset < 0) { | |
| return FORMAT_ERROR; | |
| } | |
| if (main_xref_offset == 0) { | |
| return SUCCESS; | |
| } | |
| const AutoRestorer<uint32_t> save_metadata_objnum(&metadata_objnum_); | |
| metadata_objnum_ = 0; | |
| object_stream_map_.clear(); | |
| if (!LoadLinearizedAllCrossRefTable(main_xref_offset) && | |
| !LoadLinearizedAllCrossRefStream(main_xref_offset)) { | |
| last_xref_offset_ = 0; | |
| return FORMAT_ERROR; | |
| } | |
| return SUCCESS; | |
| } | |
| void CPDF_Parser::SetSyntaxParserForTesting( | |
| std::unique_ptr<CPDF_SyntaxParser> parser) { | |
| syntax_ = std::move(parser); | |
| } | |
| std::vector<unsigned int> CPDF_Parser::GetTrailerEnds() { | |
| std::vector<unsigned int> trailer_ends; | |
| syntax_->SetTrailerEnds(&trailer_ends); | |
| // Traverse the document. | |
| syntax_->SetPos(0); | |
| while (true) { | |
| CPDF_SyntaxParser::WordResult word_result = syntax_->GetNextWord(); | |
| if (word_result.is_number) { | |
| // The object number was read. Read the generation number. | |
| word_result = syntax_->GetNextWord(); | |
| if (!word_result.is_number) { | |
| break; | |
| } | |
| word_result = syntax_->GetNextWord(); | |
| if (word_result.word != "obj") { | |
| break; | |
| } | |
| syntax_->GetObjectBody(nullptr); | |
| word_result = syntax_->GetNextWord(); | |
| if (word_result.word != "endobj") { | |
| break; | |
| } | |
| } else if (word_result.word == "trailer") { | |
| syntax_->GetObjectBody(nullptr); | |
| } else if (word_result.word == "startxref") { | |
| syntax_->GetNextWord(); | |
| } else if (word_result.word == "xref") { | |
| while (true) { | |
| word_result = syntax_->GetNextWord(); | |
| if (word_result.word.IsEmpty() || word_result.word == "startxref") { | |
| break; | |
| } | |
| } | |
| syntax_->GetNextWord(); | |
| } else { | |
| break; | |
| } | |
| } | |
| // Stop recording trailer ends. | |
| syntax_->SetTrailerEnds(nullptr); | |
| return trailer_ends; | |
| } | |
| bool CPDF_Parser::WriteToArchive(IFX_ArchiveStream* archive, | |
| FX_FILESIZE src_size) { | |
| static constexpr FX_FILESIZE kBufferSize = 4096; | |
| DataVector<uint8_t> buffer(kBufferSize); | |
| syntax_->SetPos(0); | |
| while (src_size) { | |
| const uint32_t block_size = | |
| static_cast<uint32_t>(std::min(kBufferSize, src_size)); | |
| auto block_span = pdfium::span(buffer).first(block_size); | |
| if (!syntax_->ReadBlock(block_span)) { | |
| return false; | |
| } | |
| if (!archive->WriteBlock(pdfium::span(buffer).first(block_size))) { | |
| return false; | |
| } | |
| src_size -= block_size; | |
| } | |
| return true; | |
| } |