// Copyright 2018 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 "fpdfsdk/cpdfsdk_helpers.h"

#include <utility>

#include "build/build_config.h"
#include "constants/catalog.h"
#include "constants/form_fields.h"
#include "constants/stream_dict_common.h"
#include "core/fpdfapi/page/cpdf_page.h"
#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_document.h"
#include "core/fpdfapi/parser/cpdf_stream_acc.h"
#include "core/fpdfapi/render/cpdf_renderoptions.h"
#include "core/fpdfdoc/cpdf_annot.h"
#include "core/fpdfdoc/cpdf_interactiveform.h"
#include "core/fpdfdoc/cpdf_metadata.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "core/fxcrt/span_util.h"
#include "core/fxcrt/stl_util.h"
#include "core/fxcrt/unowned_ptr.h"
#include "core/fxge/dib/cfx_dibitmap.h"
#include "fpdfsdk/cpdfsdk_formfillenvironment.h"

#if defined(PDF_USE_SKIA)
#include "core/fxge/cfx_gemodule.h"
#endif

namespace {

constexpr char kQuadPoints[] = "QuadPoints";

// 0 bit: FPDF_POLICY_MACHINETIME_ACCESS
uint32_t g_sandbox_policy = 0xFFFFFFFF;

UNSUPPORT_INFO* g_unsupport_info = nullptr;

bool RaiseUnsupportedError(int nError) {
  if (!g_unsupport_info) {
    return false;
  }

  if (g_unsupport_info->FSDK_UnSupport_Handler) {
    g_unsupport_info->FSDK_UnSupport_Handler(g_unsupport_info, nError);
  }
  return true;
}

// Use the existence of the XFA array as a signal for XFA forms.
bool DocHasXFA(const CPDF_Document* doc) {
  const CPDF_Dictionary* root = doc->GetRoot();
  if (!root) {
    return false;
  }

  RetainPtr<const CPDF_Dictionary> form =
      root->GetDictFor(pdfium::catalog::kAcroForm);
  return form && form->GetArrayFor("XFA");
}

unsigned long GetStreamMaybeCopyAndReturnLengthImpl(
    RetainPtr<const CPDF_Stream> stream,
    pdfium::span<uint8_t> buffer,
    bool decode) {
  DCHECK(stream);
  auto stream_acc = pdfium::MakeRetain<CPDF_StreamAcc>(std::move(stream));
  if (decode) {
    stream_acc->LoadAllDataFiltered();
  } else {
    stream_acc->LoadAllDataRaw();
  }

  pdfium::span<const uint8_t> stream_data_span = stream_acc->GetSpan();
  if (!buffer.empty() && buffer.size() <= stream_data_span.size()) {
    fxcrt::Copy(stream_data_span, buffer);
  }
  return pdfium::checked_cast<unsigned long>(stream_data_span.size());
}

// TODO(tsepez): should be UNSAFE_BUFFER_USAGE.
size_t FPDFWideStringLength(const unsigned short* str) {
  if (!str) {
    return 0;
  }
  size_t len = 0;
  // SAFETY: NUL-termination required from caller.
  UNSAFE_BUFFERS({
    while (str[len]) {
      len++;
    }
  });
  return len;
}

#ifdef PDF_ENABLE_XFA
class FPDF_FileHandlerContext final : public IFX_SeekableStream {
 public:
  CONSTRUCT_VIA_MAKE_RETAIN;

  // IFX_SeekableStream:
  FX_FILESIZE GetSize() override;
  FX_FILESIZE GetPosition() override;
  bool IsEOF() override;
  bool ReadBlockAtOffset(pdfium::span<uint8_t> buffer,
                         FX_FILESIZE offset) override;
  bool WriteBlock(pdfium::span<const uint8_t> buffer) override;
  bool Flush() override;

 private:
  explicit FPDF_FileHandlerContext(FPDF_FILEHANDLER* pFS);
  ~FPDF_FileHandlerContext() override;

  UnownedPtr<FPDF_FILEHANDLER> const fs_;
  FX_FILESIZE cur_pos_ = 0;
};

FPDF_FileHandlerContext::FPDF_FileHandlerContext(FPDF_FILEHANDLER* pFS)
    : fs_(pFS) {
  CHECK(fs_);
}

FPDF_FileHandlerContext::~FPDF_FileHandlerContext() {
  if (fs_->Release) {
    fs_->Release(fs_->clientData);
  }
}

FX_FILESIZE FPDF_FileHandlerContext::GetSize() {
  if (fs_->GetSize) {
    return static_cast<FX_FILESIZE>(fs_->GetSize(fs_->clientData));
  }
  return 0;
}

bool FPDF_FileHandlerContext::IsEOF() {
  return cur_pos_ >= GetSize();
}

FX_FILESIZE FPDF_FileHandlerContext::GetPosition() {
  return cur_pos_;
}

bool FPDF_FileHandlerContext::ReadBlockAtOffset(pdfium::span<uint8_t> buffer,
                                                FX_FILESIZE offset) {
  if (buffer.empty() || !fs_->ReadBlock) {
    return false;
  }

  FX_SAFE_FILESIZE new_position = offset;
  new_position += buffer.size();
  if (!new_position.IsValid()) {
    return false;
  }

  if (fs_->ReadBlock(fs_->clientData, static_cast<FPDF_DWORD>(offset),
                     buffer.data(),
                     static_cast<FPDF_DWORD>(buffer.size())) != 0) {
    return false;
  }

  cur_pos_ = new_position.ValueOrDie();
  return true;
}

bool FPDF_FileHandlerContext::WriteBlock(pdfium::span<const uint8_t> buffer) {
  if (!fs_->WriteBlock) {
    return false;
  }

  const FX_FILESIZE size = GetSize();
  FX_SAFE_FILESIZE new_position = size;
  new_position += buffer.size();
  if (!new_position.IsValid()) {
    return false;
  }

  if (fs_->WriteBlock(fs_->clientData, static_cast<FPDF_DWORD>(size),
                      buffer.data(),
                      static_cast<FPDF_DWORD>(buffer.size())) != 0) {
    return false;
  }

  cur_pos_ = new_position.ValueOrDie();
  return true;
}

bool FPDF_FileHandlerContext::Flush() {
  if (!fs_->Flush) {
    return true;
  }

  return fs_->Flush(fs_->clientData) == 0;
}
#endif  // PDF_ENABLE_XFA

}  // namespace

IPDF_Page* IPDFPageFromFPDFPage(FPDF_PAGE page) {
  return reinterpret_cast<IPDF_Page*>(page);
}

FPDF_PAGE FPDFPageFromIPDFPage(IPDF_Page* page) {
  return reinterpret_cast<FPDF_PAGE>(page);
}

CPDF_Document* CPDFDocumentFromFPDFDocument(FPDF_DOCUMENT doc) {
  return reinterpret_cast<CPDF_Document*>(doc);
}

FPDF_DOCUMENT FPDFDocumentFromCPDFDocument(CPDF_Document* doc) {
  return reinterpret_cast<FPDF_DOCUMENT>(doc);
}

CPDF_Page* CPDFPageFromFPDFPage(FPDF_PAGE page) {
  return page ? IPDFPageFromFPDFPage(page)->AsPDFPage() : nullptr;
}

FXDIB_Format FXDIBFormatFromFPDFFormat(int format) {
  switch (format) {
    case FPDFBitmap_Gray:
      return FXDIB_Format::k8bppRgb;
    case FPDFBitmap_BGR:
      return FXDIB_Format::kBgr;
    case FPDFBitmap_BGRx:
      return FXDIB_Format::kBgrx;
    case FPDFBitmap_BGRA:
      return FXDIB_Format::kBgra;
#if defined(PDF_USE_SKIA)
    case FPDFBitmap_BGRA_Premul:
      return CFX_GEModule::Get()->UseSkiaRenderer() ? FXDIB_Format::kBgraPremul
                                                    : FXDIB_Format::kInvalid;
#endif
    default:
      return FXDIB_Format::kInvalid;
  }
}

void ValidateBitmapPremultiplyState(CFX_DIBitmap* bitmap) {
#if defined(PDF_USE_SKIA)
  const bool should_be_premultiplied =
      CFX_GEModule::Get()->UseSkiaRenderer() &&
      bitmap->GetFormat() == FXDIB_Format::kBgraPremul;
  CHECK_EQ(should_be_premultiplied, bitmap->IsPremultiplied());
#else
  CHECK(!bitmap->IsPremultiplied());
#endif
}

CPDFSDK_InteractiveForm* FormHandleToInteractiveForm(FPDF_FORMHANDLE hHandle) {
  CPDFSDK_FormFillEnvironment* pFormFillEnv =
      CPDFSDKFormFillEnvironmentFromFPDFFormHandle(hHandle);
  return pFormFillEnv ? pFormFillEnv->GetInteractiveForm() : nullptr;
}

WideString WideStringFromFPDFWideString(FPDF_WIDESTRING wide_string) {
  // SAFETY: caller ensures `wide_string` is NUL-terminated and enforced
  // by UNSAFE_BUFFER_USAGE in header file.
  return WideString::FromUTF16LE(
      UNSAFE_BUFFERS(pdfium::span(reinterpret_cast<const uint8_t*>(wide_string),
                                  FPDFWideStringLength(wide_string) * 2)));
}

UNSAFE_BUFFER_USAGE pdfium::span<char> SpanFromFPDFApiArgs(
    void* buffer,
    pdfium::StrictNumeric<size_t> buflen) {
  if (!buffer) {
    // API convention is to ignore `buflen` arg when `buffer` is NULL.
    return pdfium::span<char>();
  }
  // SAFETY: required from caller, enforced by UNSAFE_BUFFER_USAGE in header.
  return UNSAFE_BUFFERS(pdfium::span(static_cast<char*>(buffer), buflen));
}

#ifdef PDF_ENABLE_XFA
RetainPtr<IFX_SeekableStream> MakeSeekableStream(
    FPDF_FILEHANDLER* pFilehandler) {
  return pdfium::MakeRetain<FPDF_FileHandlerContext>(pFilehandler);
}
#endif  // PDF_ENABLE_XFA

RetainPtr<const CPDF_Array> GetQuadPointsArrayFromDictionary(
    const CPDF_Dictionary* dict) {
  return dict->GetArrayFor("QuadPoints");
}

RetainPtr<CPDF_Array> GetMutableQuadPointsArrayFromDictionary(
    CPDF_Dictionary* dict) {
  return pdfium::WrapRetain(
      const_cast<CPDF_Array*>(GetQuadPointsArrayFromDictionary(dict).Get()));
}

RetainPtr<CPDF_Array> AddQuadPointsArrayToDictionary(CPDF_Dictionary* dict) {
  return dict->SetNewFor<CPDF_Array>(kQuadPoints);
}

bool IsValidQuadPointsIndex(const CPDF_Array* array, size_t index) {
  return array && index < array->size() / 8;
}

bool GetQuadPointsAtIndex(RetainPtr<const CPDF_Array> array,
                          size_t quad_index,
                          FS_QUADPOINTSF* quad_points) {
  DCHECK(quad_points);
  DCHECK(array);

  if (!IsValidQuadPointsIndex(array, quad_index)) {
    return false;
  }

  quad_index *= 8;
  quad_points->x1 = array->GetFloatAt(quad_index);
  quad_points->y1 = array->GetFloatAt(quad_index + 1);
  quad_points->x2 = array->GetFloatAt(quad_index + 2);
  quad_points->y2 = array->GetFloatAt(quad_index + 3);
  quad_points->x3 = array->GetFloatAt(quad_index + 4);
  quad_points->y3 = array->GetFloatAt(quad_index + 5);
  quad_points->x4 = array->GetFloatAt(quad_index + 6);
  quad_points->y4 = array->GetFloatAt(quad_index + 7);
  return true;
}

CFX_PointF CFXPointFFromFSPointF(const FS_POINTF& point) {
  return CFX_PointF(point.x, point.y);
}

CFX_FloatRect CFXFloatRectFromFSRectF(const FS_RECTF& rect) {
  return CFX_FloatRect(rect.left, rect.bottom, rect.right, rect.top);
}

FS_RECTF FSRectFFromCFXFloatRect(const CFX_FloatRect& rect) {
  return {rect.left, rect.top, rect.right, rect.bottom};
}

CFX_Matrix CFXMatrixFromFSMatrix(const FS_MATRIX& matrix) {
  return CFX_Matrix(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f);
}

FS_MATRIX FSMatrixFromCFXMatrix(const CFX_Matrix& matrix) {
  return {matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f};
}

unsigned long NulTerminateMaybeCopyAndReturnLength(
    const ByteString& text,
    pdfium::span<char> result_span) {
  pdfium::span<const char> text_span = text.span_with_terminator();
  fxcrt::try_spancpy(result_span, text_span);
  return pdfium::checked_cast<unsigned long>(text_span.size());
}

unsigned long Utf16EncodeMaybeCopyAndReturnLength(
    const WideString& text,
    pdfium::span<char> result_span) {
  ByteString encoded_text = text.ToUTF16LE();
  pdfium::span<const char> encoded_text_span = encoded_text.span();
  fxcrt::try_spancpy(result_span, encoded_text_span);
  return pdfium::checked_cast<unsigned long>(encoded_text_span.size());
}

unsigned long GetRawStreamMaybeCopyAndReturnLength(
    RetainPtr<const CPDF_Stream> stream,
    pdfium::span<uint8_t> buffer) {
  return GetStreamMaybeCopyAndReturnLengthImpl(std::move(stream), buffer,
                                               /*decode=*/false);
}

unsigned long DecodeStreamMaybeCopyAndReturnLength(
    RetainPtr<const CPDF_Stream> stream,
    pdfium::span<uint8_t> buffer) {
  return GetStreamMaybeCopyAndReturnLengthImpl(std::move(stream), buffer,
                                               /*decode=*/true);
}

void SetPDFSandboxPolicy(FPDF_DWORD policy, FPDF_BOOL enable) {
  switch (policy) {
    case FPDF_POLICY_MACHINETIME_ACCESS: {
      uint32_t mask = 1 << policy;
      if (enable) {
        g_sandbox_policy |= mask;
      } else {
        g_sandbox_policy &= ~mask;
      }
    } break;
    default:
      break;
  }
}

FPDF_BOOL IsPDFSandboxPolicyEnabled(FPDF_DWORD policy) {
  switch (policy) {
    case FPDF_POLICY_MACHINETIME_ACCESS: {
      uint32_t mask = 1 << policy;
      return !!(g_sandbox_policy & mask);
    }
    default:
      return false;
  }
}

void SetPDFUnsupportInfo(UNSUPPORT_INFO* unsp_info) {
  g_unsupport_info = unsp_info;
}

void ReportUnsupportedFeatures(const CPDF_Document* doc) {
  const CPDF_Dictionary* pRootDict = doc->GetRoot();
  if (!pRootDict) {
    return;
  }

  // Portfolios and Packages
  if (pRootDict->KeyExist("Collection")) {
    RaiseUnsupportedError(FPDF_UNSP_DOC_PORTABLECOLLECTION);
  }

  RetainPtr<const CPDF_Dictionary> pNameDict =
      pRootDict->GetDictFor(pdfium::catalog::kNames);
  if (pNameDict) {
    if (pNameDict->KeyExist("EmbeddedFiles")) {
      RaiseUnsupportedError(FPDF_UNSP_DOC_ATTACHMENT);
    }

    RetainPtr<const CPDF_Dictionary> pJSDict =
        pNameDict->GetDictFor("JavaScript");
    if (pJSDict) {
      RetainPtr<const CPDF_Array> pArray = pJSDict->GetArrayFor("Names");
      if (pArray) {
        for (size_t i = 0; i < pArray->size(); i++) {
          ByteString cbStr = pArray->GetByteStringAt(i);
          if (cbStr == "com.adobe.acrobat.SharedReview.Register") {
            RaiseUnsupportedError(FPDF_UNSP_DOC_SHAREDREVIEW);
            break;
          }
        }
      }
    }
  }

  // SharedForm
  RetainPtr<const CPDF_Stream> pStream = pRootDict->GetStreamFor("Metadata");
  if (pStream) {
    CPDF_Metadata metadata(std::move(pStream));
    for (const UnsupportedFeature& feature : metadata.CheckForSharedForm()) {
      RaiseUnsupportedError(static_cast<int>(feature));
    }
  }
}

void ReportUnsupportedXFA(const CPDF_Document* doc) {
  if (!doc->GetExtension() && DocHasXFA(doc)) {
    RaiseUnsupportedError(FPDF_UNSP_DOC_XFAFORM);
  }
}

void CheckForUnsupportedAnnot(const CPDF_Annot* pAnnot) {
  switch (pAnnot->GetSubtype()) {
    case CPDF_Annot::Subtype::FILEATTACHMENT:
      RaiseUnsupportedError(FPDF_UNSP_ANNOT_ATTACHMENT);
      break;
    case CPDF_Annot::Subtype::MOVIE:
      RaiseUnsupportedError(FPDF_UNSP_ANNOT_MOVIE);
      break;
    case CPDF_Annot::Subtype::RICHMEDIA:
      RaiseUnsupportedError(FPDF_UNSP_ANNOT_SCREEN_RICHMEDIA);
      break;
    case CPDF_Annot::Subtype::SCREEN: {
      const CPDF_Dictionary* pAnnotDict = pAnnot->GetAnnotDict();
      ByteString cbString = pAnnotDict->GetByteStringFor("IT");
      if (cbString != "Img") {
        RaiseUnsupportedError(FPDF_UNSP_ANNOT_SCREEN_MEDIA);
      }
      break;
    }
    case CPDF_Annot::Subtype::SOUND:
      RaiseUnsupportedError(FPDF_UNSP_ANNOT_SOUND);
      break;
    case CPDF_Annot::Subtype::THREED:
      RaiseUnsupportedError(FPDF_UNSP_ANNOT_3DANNOT);
      break;
    case CPDF_Annot::Subtype::WIDGET: {
      const CPDF_Dictionary* pAnnotDict = pAnnot->GetAnnotDict();
      ByteString cbString =
          pAnnotDict->GetByteStringFor(pdfium::form_fields::kFT);
      if (cbString == pdfium::form_fields::kSig) {
        RaiseUnsupportedError(FPDF_UNSP_ANNOT_SIG);
      }
      break;
    }
    default:
      break;
  }
}

void ProcessParseError(CPDF_Parser::Error err) {
  uint32_t err_code = FPDF_ERR_SUCCESS;
  // Translate FPDFAPI error code to FPDFVIEW error code
  switch (err) {
    case CPDF_Parser::SUCCESS:
      err_code = FPDF_ERR_SUCCESS;
      break;
    case CPDF_Parser::FILE_ERROR:
      err_code = FPDF_ERR_FILE;
      break;
    case CPDF_Parser::FORMAT_ERROR:
      err_code = FPDF_ERR_FORMAT;
      break;
    case CPDF_Parser::PASSWORD_ERROR:
      err_code = FPDF_ERR_PASSWORD;
      break;
    case CPDF_Parser::HANDLER_ERROR:
      err_code = FPDF_ERR_SECURITY;
      break;
  }
  FXSYS_SetLastError(err_code);
}

void SetColorFromScheme(const FPDF_COLORSCHEME* pColorScheme,
                        CPDF_RenderOptions* pRenderOptions) {
  CPDF_RenderOptions::ColorScheme color_scheme;
  color_scheme.path_fill_color =
      static_cast<FX_ARGB>(pColorScheme->path_fill_color);
  color_scheme.path_stroke_color =
      static_cast<FX_ARGB>(pColorScheme->path_stroke_color);
  color_scheme.text_fill_color =
      static_cast<FX_ARGB>(pColorScheme->text_fill_color);
  color_scheme.text_stroke_color =
      static_cast<FX_ARGB>(pColorScheme->text_stroke_color);
  pRenderOptions->SetColorScheme(color_scheme);
}

std::vector<uint32_t> ParsePageRangeString(const ByteString& bsPageRange,
                                           uint32_t nCount) {
  ByteStringView alphabet(" 0123456789-,");
  for (const auto& ch : bsPageRange) {
    if (!alphabet.Contains(ch)) {
      return std::vector<uint32_t>();
    }
  }

  ByteString bsStrippedPageRange = bsPageRange;
  bsStrippedPageRange.Remove(' ');

  std::vector<uint32_t> results;
  for (const auto& entry : fxcrt::Split(bsStrippedPageRange, ',')) {
    std::vector<ByteString> args = fxcrt::Split(entry, '-');
    if (args.size() == 1) {
      uint32_t page_num = pdfium::checked_cast<uint32_t>(atoi(args[0].c_str()));
      if (page_num == 0 || page_num > nCount) {
        return std::vector<uint32_t>();
      }
      results.push_back(page_num - 1);
    } else if (args.size() == 2) {
      uint32_t first_num =
          pdfium::checked_cast<uint32_t>(atoi(args[0].c_str()));
      if (first_num == 0) {
        return std::vector<uint32_t>();
      }
      uint32_t last_num = pdfium::checked_cast<uint32_t>(atoi(args[1].c_str()));
      if (last_num == 0 || first_num > last_num || last_num > nCount) {
        return std::vector<uint32_t>();
      }
      for (uint32_t i = first_num; i <= last_num; ++i) {
        results.push_back(i - 1);
      }
    } else {
      return std::vector<uint32_t>();
    }
  }
  return results;
}
