// Copyright 2016 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.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "core/fpdfapi/parser/cpdf_parser.h"

#include <algorithm>
#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/fx_extension.h"
#include "core/fxcrt/fx_memory_wrappers.h"
#include "core/fxcrt/fx_safe_types.h"
#include "base/stl_util.h"

namespace {

// A limit on the size of the xref table. Theoretical limits are higher, but
// this may be large enough in practice.
const int32_t kMaxXRefSize = 1048576;

// "%PDF-1.7\n"
constexpr FX_FILESIZE kPDFHeaderSize = 9;

uint32_t GetVarInt(const uint8_t* p, int32_t n) {
  uint32_t result = 0;
  for (int32_t i = 0; i < n; ++i)
    result = result * 256 + p[i];
  return result;
}

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)
    : m_pObjectsHolder(holder),
      m_CrossRefTable(std::make_unique<CPDF_CrossRefTable>()) {
  if (!holder) {
    m_pOwnedObjectsHolder = std::make_unique<ObjectsHolderStub>();
    m_pObjectsHolder = m_pOwnedObjectsHolder.get();
  }
}

CPDF_Parser::CPDF_Parser() : CPDF_Parser(nullptr) {}

CPDF_Parser::~CPDF_Parser() {
  ReleaseEncryptHandler();
}

uint32_t CPDF_Parser::GetLastObjNum() const {
  return m_CrossRefTable->objects_info().empty()
             ? 0
             : m_CrossRefTable->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 = m_CrossRefTable->GetObjectInfo(objnum);
  return (info && info->type == ObjectType::kNormal) ? info->pos : 0;
}

CPDF_Parser::ObjectType CPDF_Parser::GetObjectType(uint32_t objnum) const {
  ASSERT(IsValidObjectNumber(objnum));
  const auto* info = m_CrossRefTable->GetObjectInfo(objnum);
  return info ? info->type : ObjectType::kFree;
}

bool CPDF_Parser::IsObjectFreeOrNull(uint32_t objnum) const {
  switch (GetObjectType(objnum)) {
    case ObjectType::kFree:
    case ObjectType::kNull:
      return true;
    case ObjectType::kNotCompressed:
    case ObjectType::kCompressed:
      return false;
  }
  NOTREACHED();
  return false;
}

bool CPDF_Parser::IsObjectFree(uint32_t objnum) const {
  return GetObjectType(objnum) == ObjectType::kFree;
}

void CPDF_Parser::ShrinkObjectMap(uint32_t size) {
  m_CrossRefTable->ShrinkObjectMap(size);
}

bool CPDF_Parser::InitSyntaxParser(
    const RetainPtr<CPDF_ReadValidator>& validator) {
  const Optional<FX_FILESIZE> header_offset = GetHeaderOffset(validator);
  if (!header_offset)
    return false;
  if (validator->GetSize() < *header_offset + kPDFHeaderSize)
    return false;

  m_pSyntax = std::make_unique<CPDF_SyntaxParser>(validator, *header_offset);
  return ParseFileVersion();
}

bool CPDF_Parser::ParseFileVersion() {
  m_FileVersion = 0;
  uint8_t ch;
  if (!m_pSyntax->GetCharAt(5, ch))
    return false;

  if (std::isdigit(ch))
    m_FileVersion = FXSYS_DecimalCharToInt(static_cast<wchar_t>(ch)) * 10;

  if (!m_pSyntax->GetCharAt(7, ch))
    return false;

  if (std::isdigit(ch))
    m_FileVersion += FXSYS_DecimalCharToInt(static_cast<wchar_t>(ch));
  return true;
}

CPDF_Parser::Error CPDF_Parser::StartParse(
    const RetainPtr<IFX_SeekableReadStream>& pFileAccess,
    const char* password) {
  if (!InitSyntaxParser(
          pdfium::MakeRetain<CPDF_ReadValidator>(pFileAccess, nullptr)))
    return FORMAT_ERROR;
  SetPassword(password);
  return StartParseInternal();
}

CPDF_Parser::Error CPDF_Parser::StartParseInternal() {
  ASSERT(!m_bHasParsed);
  ASSERT(!m_bXRefTableRebuilt);
  m_bHasParsed = true;
  m_bXRefStream = false;

  m_LastXRefOffset = ParseStartXRef();
  if (m_LastXRefOffset >= kPDFHeaderSize) {
    if (!LoadAllCrossRefV4(m_LastXRefOffset) &&
        !LoadAllCrossRefV5(m_LastXRefOffset)) {
      if (!RebuildCrossRef())
        return FORMAT_ERROR;

      m_bXRefTableRebuilt = true;
      m_LastXRefOffset = 0;
    }
  } else {
    if (!RebuildCrossRef())
      return FORMAT_ERROR;

    m_bXRefTableRebuilt = true;
  }
  Error eRet = SetEncryptHandler();
  if (eRet != SUCCESS)
    return eRet;

  if (!GetRoot() || !m_pObjectsHolder->TryInit()) {
    if (m_bXRefTableRebuilt)
      return FORMAT_ERROR;

    ReleaseEncryptHandler();
    if (!RebuildCrossRef())
      return FORMAT_ERROR;

    eRet = SetEncryptHandler();
    if (eRet != SUCCESS)
      return eRet;

    m_pObjectsHolder->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 (m_pSecurityHandler && !m_pSecurityHandler->IsMetadataEncrypted()) {
    CPDF_Reference* pMetadata =
        ToReference(GetRoot()->GetObjectFor("Metadata"));
    if (pMetadata)
      m_MetadataObjnum = pMetadata->GetRefObjNum();
  }
  return SUCCESS;
}

FX_FILESIZE CPDF_Parser::ParseStartXRef() {
  static constexpr char kStartXRefKeyword[] = "startxref";
  m_pSyntax->SetPos(m_pSyntax->GetDocumentSize() - strlen(kStartXRefKeyword));
  if (!m_pSyntax->BackwardsSearchToWord(kStartXRefKeyword, 4096))
    return 0;

  // Skip "startxref" keyword.
  m_pSyntax->GetKeyword();

  // Read XRef offset.
  bool bNumber;
  const ByteString xref_offset_str = m_pSyntax->GetNextWord(&bNumber);
  if (!bNumber || xref_offset_str.IsEmpty())
    return 0;

  const FX_SAFE_FILESIZE result = FXSYS_atoi64(xref_offset_str.c_str());
  if (!result.IsValid() || result.ValueOrDie() >= m_pSyntax->GetDocumentSize())
    return 0;

  return result.ValueOrDie();
}

CPDF_Parser::Error CPDF_Parser::SetEncryptHandler() {
  ReleaseEncryptHandler();
  if (!GetTrailer())
    return FORMAT_ERROR;

  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;

  m_pSecurityHandler = std::move(pSecurityHandler);
  return SUCCESS;
}

void CPDF_Parser::ReleaseEncryptHandler() {
  m_pSecurityHandler.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::VerifyCrossRefV4() {
  for (const auto& it : m_CrossRefTable->objects_info()) {
    if (it.second.pos == 0)
      continue;
    // Find the first non-zero position.
    FX_FILESIZE SavedPos = m_pSyntax->GetPos();
    m_pSyntax->SetPos(it.second.pos);
    bool is_num = false;
    ByteString num_str = m_pSyntax->GetNextWord(&is_num);
    m_pSyntax->SetPos(SavedPos);
    if (!is_num || num_str.IsEmpty() ||
        FXSYS_atoui(num_str.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::LoadAllCrossRefV4(FX_FILESIZE xref_offset) {
  if (!LoadCrossRefV4(xref_offset, true))
    return false;

  RetainPtr<CPDF_Dictionary> trailer = LoadTrailerV4();
  if (!trailer)
    return false;

  m_CrossRefTable->SetTrailer(std::move(trailer));
  int32_t xrefsize = GetDirectInteger(GetTrailer(), "Size");
  if (xrefsize > 0 && xrefsize <= kMaxXRefSize)
    ShrinkObjectMap(xrefsize);

  std::vector<FX_FILESIZE> xref_stream_list{
      GetDirectInteger(GetTrailer(), "XRefStm")};
  std::vector<FX_FILESIZE> xref_list{xref_offset};
  std::set<FX_FILESIZE> seen_xref_offset{xref_offset};

  // When the trailer doesn't have Prev entry or Prev entry value is not
  // numerical, GetDirectInteger() returns 0. Loading will end.
  xref_offset = GetDirectInteger(GetTrailer(), "Prev");
  while (xref_offset) {
    // Check for circular references.
    if (pdfium::Contains(seen_xref_offset, xref_offset))
      return false;

    seen_xref_offset.insert(xref_offset);

    // SLOW ...
    xref_list.insert(xref_list.begin(), xref_offset);
    LoadCrossRefV4(xref_offset, true);

    RetainPtr<CPDF_Dictionary> pDict(LoadTrailerV4());
    if (!pDict)
      return false;

    xref_offset = GetDirectInteger(pDict.Get(), "Prev");

    // SLOW ...
    xref_stream_list.insert(xref_stream_list.begin(),
                            pDict->GetIntegerFor("XRefStm"));

    m_CrossRefTable = CPDF_CrossRefTable::MergeUp(
        std::make_unique<CPDF_CrossRefTable>(std::move(pDict)),
        std::move(m_CrossRefTable));
  }

  for (size_t i = 0; i < xref_list.size(); ++i) {
    if (!LoadCrossRefV4(xref_list[i], false))
      return false;

    if (xref_stream_list[i] && !LoadCrossRefV5(&xref_stream_list[i], false))
      return false;

    if (i == 0 && !VerifyCrossRefV4())
      return false;
  }
  return true;
}

bool CPDF_Parser::LoadLinearizedAllCrossRefV4(FX_FILESIZE main_xref_offset) {
  if (!LoadCrossRefV4(main_xref_offset, false))
    return false;

  RetainPtr<CPDF_Dictionary> main_trailer = LoadTrailerV4();
  if (!main_trailer)
    return false;

  // GetTrailer() currently returns the first-page trailer.
  if (GetDirectInteger(GetTrailer(), "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|.
  std::vector<FX_FILESIZE> xref_stream_list{
      GetDirectInteger(GetTrailer(), "XRefStm")};
  std::vector<FX_FILESIZE> xref_list{main_xref_offset};
  std::set<FX_FILESIZE> seen_xref_offset{main_xref_offset};

  // Merge the trailers.
  m_CrossRefTable = CPDF_CrossRefTable::MergeUp(
      std::make_unique<CPDF_CrossRefTable>(std::move(main_trailer)),
      std::move(m_CrossRefTable));

  // Now GetTrailer() returns the merged trailer, where /Prev is from the
  // main-trailer.
  FX_FILESIZE xref_offset = GetDirectInteger(GetTrailer(), "Prev");
  while (xref_offset) {
    // Check for circular references.
    if (pdfium::Contains(seen_xref_offset, xref_offset))
      return false;

    seen_xref_offset.insert(xref_offset);

    // SLOW ...
    xref_list.insert(xref_list.begin(), xref_offset);
    LoadCrossRefV4(xref_offset, true);

    RetainPtr<CPDF_Dictionary> pDict(LoadTrailerV4());
    if (!pDict)
      return false;

    xref_offset = GetDirectInteger(pDict.Get(), "Prev");

    // SLOW ...
    xref_stream_list.insert(xref_stream_list.begin(),
                            pDict->GetIntegerFor("XRefStm"));

    m_CrossRefTable = CPDF_CrossRefTable::MergeUp(
        std::make_unique<CPDF_CrossRefTable>(std::move(pDict)),
        std::move(m_CrossRefTable));
  }

  if (xref_stream_list[0] && !LoadCrossRefV5(&xref_stream_list[0], false))
    return false;

  for (size_t i = 1; i < xref_list.size(); ++i) {
    if (!LoadCrossRefV4(xref_list[i], false))
      return false;

    if (xref_stream_list[i] && !LoadCrossRefV5(&xref_stream_list[i], 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 kEntryConstSize = 20;

  if (!out_objects) {
    FX_SAFE_FILESIZE pos = count;
    pos *= kEntryConstSize;
    pos += m_pSyntax->GetPos();
    if (!pos.IsValid())
      return false;
    m_pSyntax->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 =
      m_pSyntax->GetDocumentSize() / kEntryConstSize;
  if (new_size.ValueOrDie() > max_entries_in_file)
    return false;

  out_objects->resize(new_size.ValueOrDie());

  std::vector<char, FxAllocAllocator<char>> buf(1024 * kEntryConstSize + 1);
  buf.back() = '\0';

  uint32_t nBytesToRead = count;
  while (nBytesToRead > 0) {
    const uint32_t block_size = std::min(nBytesToRead, 1024u);
    if (!m_pSyntax->ReadBlock(reinterpret_cast<uint8_t*>(buf.data()),
                              block_size * kEntryConstSize)) {
      return false;
    }

    for (uint32_t i = 0; i < block_size; i++) {
      uint32_t iObjectIndex = count - nBytesToRead + 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;

      char* pEntry = &buf[i * kEntryConstSize];
      if (pEntry[17] == 'f') {
        info.pos = 0;
        info.type = ObjectType::kFree;
      } else {
        const FX_SAFE_FILESIZE offset = FXSYS_atoi64(pEntry);
        if (!offset.IsValid())
          return false;

        if (offset.ValueOrDie() == 0) {
          for (int32_t c = 0; c < 10; c++) {
            if (!std::isdigit(pEntry[c]))
              return false;
          }
        }

        info.pos = offset.ValueOrDie();

        // TODO(art-snake): The info.gennum is uint16_t, but version may be
        // greated than max<uint16_t>. Needs solve this issue.
        const int32_t version = FXSYS_atoi(pEntry + 11);
        info.gennum = version;
        info.type = ObjectType::kNotCompressed;
      }
    }
    nBytesToRead -= block_size;
  }
  return true;
}

bool CPDF_Parser::ParseCrossRefV4(std::vector<CrossRefObjData>* out_objects) {
  if (out_objects)
    out_objects->clear();

  if (m_pSyntax->GetKeyword() != "xref")
    return false;
  std::vector<CrossRefObjData> result_objects;
  while (1) {
    FX_FILESIZE saved_pos = m_pSyntax->GetPos();
    bool bIsNumber;
    ByteString word = m_pSyntax->GetNextWord(&bIsNumber);
    if (word.IsEmpty())
      return false;

    if (!bIsNumber) {
      m_pSyntax->SetPos(saved_pos);
      break;
    }

    uint32_t start_objnum = FXSYS_atoui(word.c_str());
    if (start_objnum >= kMaxObjectNumber)
      return false;

    uint32_t count = m_pSyntax->GetDirectNum();
    m_pSyntax->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::LoadCrossRefV4(FX_FILESIZE pos, bool bSkip) {
  m_pSyntax->SetPos(pos);
  std::vector<CrossRefObjData> objects;
  if (!ParseCrossRefV4(bSkip ? 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)
          m_CrossRefTable->SetFree(obj.obj_num);
        break;
      case ObjectType::kNormal:
      case ObjectType::kObjStream:
        m_CrossRefTable->AddNormal(obj.obj_num, obj.info.gennum, obj.info.pos);
        break;
      case ObjectType::kCompressed:
        m_CrossRefTable->AddCompressed(obj.obj_num, obj.info.archive_obj_num);
        break;
      default:
        NOTREACHED();
    }
  }
}

bool CPDF_Parser::LoadAllCrossRefV5(FX_FILESIZE xref_offset) {
  if (!LoadCrossRefV5(&xref_offset, true))
    return false;

  std::set<FX_FILESIZE> seen_xref_offset;
  while (xref_offset) {
    seen_xref_offset.insert(xref_offset);
    if (!LoadCrossRefV5(&xref_offset, false))
      return false;

    // Check for circular references.
    if (pdfium::Contains(seen_xref_offset, xref_offset))
      return false;
  }
  m_ObjectStreamMap.clear();
  m_bXRefStream = true;
  return true;
}

bool CPDF_Parser::RebuildCrossRef() {
  auto cross_ref_table = std::make_unique<CPDF_CrossRefTable>();

  const uint32_t kBufferSize = 4096;
  m_pSyntax->SetReadBufferSize(kBufferSize);
  m_pSyntax->SetPos(0);

  bool bIsNumber;
  std::vector<std::pair<uint32_t, FX_FILESIZE>> numbers;
  for (ByteString word = m_pSyntax->GetNextWord(&bIsNumber); !word.IsEmpty();
       word = m_pSyntax->GetNextWord(&bIsNumber)) {
    if (bIsNumber) {
      numbers.emplace_back(FXSYS_atoui(word.c_str()),
                           m_pSyntax->GetPos() - word.GetLength());
      if (numbers.size() > 2u)
        numbers.erase(numbers.begin());
      continue;
    }

    if (word == "(") {
      m_pSyntax->ReadString();
    } else if (word == "<") {
      m_pSyntax->ReadHexString();
    } else if (word == "trailer") {
      RetainPtr<CPDF_Object> pTrailer = m_pSyntax->GetObjectBody(nullptr);
      if (pTrailer) {
        cross_ref_table = CPDF_CrossRefTable::MergeUp(
            std::move(cross_ref_table),
            std::make_unique<CPDF_CrossRefTable>(ToDictionary(
                pTrailer->IsStream() ? pTrailer->AsStream()->GetDict()->Clone()
                                     : std::move(pTrailer))));
      }
    } 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;

      m_pSyntax->SetPos(obj_pos);
      const RetainPtr<CPDF_Stream> pStream =
          ToStream(m_pSyntax->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())));
      }

      if (obj_num < kMaxObjectNumber) {
        cross_ref_table->AddNormal(obj_num, gen_num, obj_pos);
        if (const auto object_stream =
                CPDF_ObjectStream::Create(pStream.Get())) {
          for (const auto& it : object_stream->objects_offsets()) {
            if (it.first < kMaxObjectNumber)
              cross_ref_table->AddCompressed(it.first, obj_num);
          }
        }
      }
    }
    numbers.clear();
  }

  m_CrossRefTable = CPDF_CrossRefTable::MergeUp(std::move(m_CrossRefTable),
                                                std::move(cross_ref_table));
  // Resore default buffer size.
  m_pSyntax->SetReadBufferSize(CPDF_Stream::kFileBufSize);

  return GetTrailer() && !m_CrossRefTable->objects_info().empty();
}

bool CPDF_Parser::LoadCrossRefV5(FX_FILESIZE* pos, bool bMainXRef) {
  RetainPtr<CPDF_Object> pObject(ParseIndirectObjectAt(*pos, 0));
  if (!pObject || !pObject->GetObjNum())
    return false;

  CPDF_Stream* pStream = pObject->AsStream();
  if (!pStream)
    return false;

  CPDF_Dictionary* pDict = pStream->GetDict();
  *pos = pDict->GetIntegerFor("Prev");
  int32_t size = pDict->GetIntegerFor("Size");
  if (size < 0)
    return false;

  RetainPtr<CPDF_Dictionary> pNewTrailer = ToDictionary(pDict->Clone());
  if (bMainXRef) {
    m_CrossRefTable =
        std::make_unique<CPDF_CrossRefTable>(std::move(pNewTrailer));
    m_CrossRefTable->ShrinkObjectMap(size);
  } else {
    m_CrossRefTable = CPDF_CrossRefTable::MergeUp(
        std::make_unique<CPDF_CrossRefTable>(std::move(pNewTrailer)),
        std::move(m_CrossRefTable));
  }

  std::vector<std::pair<int32_t, int32_t>> arrIndex;
  CPDF_Array* pArray = pDict->GetArrayFor("Index");
  if (pArray) {
    for (size_t i = 0; i < pArray->size() / 2; i++) {
      CPDF_Object* pStartNumObj = pArray->GetObjectAt(i * 2);
      CPDF_Object* pCountObj = pArray->GetObjectAt(i * 2 + 1);

      if (ToNumber(pStartNumObj) && ToNumber(pCountObj)) {
        int nStartNum = pStartNumObj->GetInteger();
        int nCount = pCountObj->GetInteger();
        if (nStartNum >= 0 && nCount > 0)
          arrIndex.push_back(std::make_pair(nStartNum, nCount));
      }
    }
  }

  if (arrIndex.empty())
    arrIndex.push_back(std::make_pair(0, size));

  pArray = pDict->GetArrayFor("W");
  if (!pArray)
    return false;

  std::vector<uint32_t> WidthArray;
  FX_SAFE_UINT32 dwAccWidth = 0;
  for (size_t i = 0; i < pArray->size(); ++i) {
    WidthArray.push_back(pArray->GetIntegerAt(i));
    dwAccWidth += WidthArray[i];
  }

  if (!dwAccWidth.IsValid() || WidthArray.size() < 3)
    return false;

  uint32_t totalWidth = dwAccWidth.ValueOrDie();
  auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(pStream);
  pAcc->LoadAllDataFiltered();

  const uint8_t* pData = pAcc->GetData();
  uint32_t dwTotalSize = pAcc->GetSize();
  uint32_t segindex = 0;
  for (const auto& index : arrIndex) {
    const int32_t startnum = index.first;
    if (startnum < 0)
      continue;

    uint32_t count = pdfium::base::checked_cast<uint32_t>(index.second);
    FX_SAFE_UINT32 dwCaculatedSize = segindex;
    dwCaculatedSize += count;
    dwCaculatedSize *= totalWidth;
    if (!dwCaculatedSize.IsValid() ||
        dwCaculatedSize.ValueOrDie() > dwTotalSize) {
      continue;
    }

    const uint8_t* segstart = pData + segindex * totalWidth;
    FX_SAFE_UINT32 dwMaxObjNum = startnum;
    dwMaxObjNum += count;
    uint32_t dwV5Size =
        m_CrossRefTable->objects_info().empty() ? 0 : GetLastObjNum() + 1;
    if (!dwMaxObjNum.IsValid() || dwMaxObjNum.ValueOrDie() > dwV5Size)
      continue;

    for (uint32_t i = 0; i < count; i++) {
      ObjectType type = ObjectType::kNotCompressed;
      const uint8_t* entrystart = segstart + i * totalWidth;
      if (WidthArray[0]) {
        const uint32_t cross_ref_stream_obj_type =
            GetVarInt(entrystart, WidthArray[0]);
        type = GetObjectTypeFromCrossRefStreamType(cross_ref_stream_obj_type);
        if (type == ObjectType::kNull)
          continue;
      }

      const uint32_t objnum = startnum + i;
      if (objnum >= CPDF_Parser::kMaxObjectNumber)
        continue;

      const ObjectType existing_type = GetObjectType(objnum);
      if (existing_type == ObjectType::kNull) {
        uint32_t offset = GetVarInt(entrystart + WidthArray[0], WidthArray[1]);
        if (pdfium::base::IsValueInRangeForNumericType<FX_FILESIZE>(offset))
          m_CrossRefTable->AddNormal(objnum, 0, offset);
        continue;
      }

      if (existing_type != ObjectType::kFree)
        continue;

      if (type == ObjectType::kFree) {
        m_CrossRefTable->SetFree(objnum);
        continue;
      }

      const uint32_t entry_value =
          GetVarInt(entrystart + WidthArray[0], WidthArray[1]);
      if (type == ObjectType::kNotCompressed) {
        const uint32_t offset = entry_value;
        if (pdfium::base::IsValueInRangeForNumericType<FX_FILESIZE>(offset))
          m_CrossRefTable->AddNormal(objnum, 0, offset);
        continue;
      }

      ASSERT(type == ObjectType::kCompressed);
      const uint32_t archive_obj_num = entry_value;
      if (!IsValidObjectNumber(archive_obj_num))
        return false;

      m_CrossRefTable->AddCompressed(objnum, archive_obj_num);
    }
    segindex += count;
  }
  return true;
}

const CPDF_Array* CPDF_Parser::GetIDArray() const {
  return GetTrailer() ? GetTrailer()->GetArrayFor("ID") : nullptr;
}

CPDF_Dictionary* CPDF_Parser::GetRoot() const {
  CPDF_Object* obj =
      m_pObjectsHolder->GetOrParseIndirectObject(GetRootObjNum());
  return obj ? obj->GetDict() : nullptr;
}

const CPDF_Dictionary* CPDF_Parser::GetEncryptDict() const {
  if (!GetTrailer())
    return nullptr;

  const CPDF_Object* pEncryptObj = GetTrailer()->GetObjectFor("Encrypt");
  if (!pEncryptObj)
    return nullptr;

  if (pEncryptObj->IsDictionary())
    return ToDictionary(pEncryptObj);

  if (pEncryptObj->IsReference()) {
    return ToDictionary(m_pObjectsHolder->GetOrParseIndirectObject(
        pEncryptObj->AsReference()->GetRefObjNum()));
  }
  return nullptr;
}

ByteString CPDF_Parser::GetEncodedPassword() const {
  return GetSecurityHandler()->GetEncodedPassword(GetPassword().AsStringView());
}

const CPDF_Dictionary* CPDF_Parser::GetTrailer() const {
  return m_CrossRefTable->trailer();
}

CPDF_Dictionary* CPDF_Parser::GetMutableTrailerForTesting() {
  return m_CrossRefTable->GetMutableTrailerForTesting();
}

RetainPtr<CPDF_Dictionary> CPDF_Parser::GetCombinedTrailer() const {
  return m_CrossRefTable->trailer()
             ? ToDictionary(m_CrossRefTable->trailer()->Clone())
             : RetainPtr<CPDF_Dictionary>();
}

uint32_t CPDF_Parser::GetInfoObjNum() const {
  const CPDF_Reference* pRef =
      ToReference(m_CrossRefTable->trailer()
                      ? m_CrossRefTable->trailer()->GetObjectFor("Info")
                      : nullptr);
  return pRef ? pRef->GetRefObjNum() : CPDF_Object::kInvalidObjNum;
}

uint32_t CPDF_Parser::GetRootObjNum() const {
  const CPDF_Reference* pRef =
      ToReference(m_CrossRefTable->trailer()
                      ? m_CrossRefTable->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(m_ParsingObjNums, objnum))
    return nullptr;

  pdfium::ScopedSetInsertion<uint32_t> local_insert(&m_ParsingObjNums, objnum);
  if (GetObjectType(objnum) == ObjectType::kNotCompressed) {
    FX_FILESIZE pos = GetObjectPositionOrZero(objnum);
    if (pos <= 0)
      return nullptr;
    return ParseIndirectObjectAt(pos, objnum);
  }
  if (GetObjectType(objnum) != ObjectType::kCompressed)
    return nullptr;

  const CPDF_ObjectStream* pObjStream =
      GetObjectStream(m_CrossRefTable->GetObjectInfo(objnum)->archive_obj_num);
  if (!pObjStream)
    return nullptr;

  return pObjStream->ParseObject(m_pObjectsHolder.Get(), objnum);
}

const CPDF_ObjectStream* CPDF_Parser::GetObjectStream(uint32_t object_number) {
  // Prevent circular parsing the same object.
  if (pdfium::Contains(m_ParsingObjNums, object_number))
    return nullptr;

  pdfium::ScopedSetInsertion<uint32_t> local_insert(&m_ParsingObjNums,
                                                    object_number);

  auto it = m_ObjectStreamMap.find(object_number);
  if (it != m_ObjectStreamMap.end())
    return it->second.get();

  const auto* info = m_CrossRefTable->GetObjectInfo(object_number);
  if (!info || info->type != ObjectType::kObjStream)
    return nullptr;

  const FX_FILESIZE object_pos = info->pos;
  if (object_pos <= 0)
    return nullptr;

  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.Get()));
  const CPDF_ObjectStream* result = objs_stream.get();
  m_ObjectStreamMap[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 = m_pSyntax->GetPos();
  m_pSyntax->SetPos(pos);

  auto result = m_pSyntax->GetIndirectObject(
      m_pObjectsHolder.Get(), CPDF_SyntaxParser::ParseType::kLoose);
  m_pSyntax->SetPos(saved_pos);
  if (result && objnum && result->GetObjNum() != objnum)
    return nullptr;

  const bool should_decrypt = m_pSecurityHandler &&
                              m_pSecurityHandler->GetCryptoHandler() &&
                              objnum != m_MetadataObjnum;
  if (should_decrypt &&
      !m_pSecurityHandler->GetCryptoHandler()->DecryptObjectTree(result)) {
    return nullptr;
  }
  return result;
}

uint32_t CPDF_Parser::GetFirstPageNo() const {
  return m_pLinearized ? m_pLinearized->GetFirstPageNo() : 0;
}

void CPDF_Parser::SetLinearizedHeaderForTesting(
    std::unique_ptr<CPDF_LinearizedHeader> pLinearized) {
  m_pLinearized = std::move(pLinearized);
}

RetainPtr<CPDF_Dictionary> CPDF_Parser::LoadTrailerV4() {
  if (m_pSyntax->GetKeyword() != "trailer")
    return nullptr;

  return ToDictionary(m_pSyntax->GetObjectBody(m_pObjectsHolder.Get()));
}

uint32_t CPDF_Parser::GetPermissions() const {
  return m_pSecurityHandler ? m_pSecurityHandler->GetPermissions() : 0xFFFFFFFF;
}

std::unique_ptr<CPDF_LinearizedHeader> CPDF_Parser::ParseLinearizedHeader() {
  return CPDF_LinearizedHeader::Parse(m_pSyntax.get());
}

CPDF_Parser::Error CPDF_Parser::StartLinearizedParse(
    const RetainPtr<CPDF_ReadValidator>& validator,
    const char* password) {
  ASSERT(!m_bHasParsed);
  ASSERT(!m_bXRefTableRebuilt);
  SetPassword(password);
  m_bXRefStream = false;
  m_LastXRefOffset = 0;

  if (!InitSyntaxParser(validator))
    return FORMAT_ERROR;

  m_pLinearized = ParseLinearizedHeader();
  if (!m_pLinearized)
    return StartParseInternal();

  m_bHasParsed = true;

  m_LastXRefOffset = m_pLinearized->GetLastXRefOffset();
  FX_FILESIZE dwFirstXRefOffset = m_LastXRefOffset;
  bool bLoadV4 = LoadCrossRefV4(dwFirstXRefOffset, false);
  if (!bLoadV4 && !LoadCrossRefV5(&dwFirstXRefOffset, true)) {
    if (!RebuildCrossRef())
      return FORMAT_ERROR;

    m_bXRefTableRebuilt = true;
    m_LastXRefOffset = 0;
  }
  if (bLoadV4) {
    RetainPtr<CPDF_Dictionary> trailer = LoadTrailerV4();
    if (!trailer)
      return SUCCESS;

    m_CrossRefTable->SetTrailer(std::move(trailer));
    int32_t xrefsize = GetDirectInteger(GetTrailer(), "Size");
    if (xrefsize > 0)
      ShrinkObjectMap(xrefsize);
  }

  Error eRet = SetEncryptHandler();
  if (eRet != SUCCESS)
    return eRet;

  if (!GetRoot() || !m_pObjectsHolder->TryInit()) {
    if (m_bXRefTableRebuilt)
      return FORMAT_ERROR;

    ReleaseEncryptHandler();
    if (!RebuildCrossRef())
      return FORMAT_ERROR;

    eRet = SetEncryptHandler();
    if (eRet != SUCCESS)
      return eRet;

    m_pObjectsHolder->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 (m_pSecurityHandler && m_pSecurityHandler->IsMetadataEncrypted()) {
    if (CPDF_Reference* pMetadata =
            ToReference(GetRoot()->GetObjectFor("Metadata")))
      m_MetadataObjnum = pMetadata->GetRefObjNum();
  }
  return SUCCESS;
}

bool CPDF_Parser::LoadLinearizedAllCrossRefV5(FX_FILESIZE main_xref_offset) {
  FX_FILESIZE xref_offset = main_xref_offset;
  if (!LoadCrossRefV5(&xref_offset, false))
    return false;

  std::set<FX_FILESIZE> seen_xref_offset;
  while (xref_offset) {
    seen_xref_offset.insert(xref_offset);
    if (!LoadCrossRefV5(&xref_offset, false))
      return false;

    // Check for circular references.
    if (pdfium::Contains(seen_xref_offset, xref_offset))
      return false;
  }
  m_ObjectStreamMap.clear();
  m_bXRefStream = 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(&m_MetadataObjnum);
  m_MetadataObjnum = 0;
  m_ObjectStreamMap.clear();

  if (!LoadLinearizedAllCrossRefV4(main_xref_offset) &&
      !LoadLinearizedAllCrossRefV5(main_xref_offset)) {
    m_LastXRefOffset = 0;
    return FORMAT_ERROR;
  }

  return SUCCESS;
}

CPDF_Parser::ObjectType CPDF_Parser::GetObjectTypeFromCrossRefStreamType(
    uint32_t cross_ref_stream_type) const {
  switch (cross_ref_stream_type) {
    case 0:
      return CPDF_Parser::ObjectType::kFree;
    case 1:
      return CPDF_Parser::ObjectType::kNotCompressed;
    case 2:
      return CPDF_Parser::ObjectType::kCompressed;
    default:
      return CPDF_Parser::ObjectType::kNull;
  }
}
