// Copyright 2020 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "public/fpdf_signature.h"

#include <utility>
#include <vector>

#include "constants/catalog.h"
#include "constants/form_fields.h"
#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_document.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "core/fxcrt/span.h"
#include "core/fxcrt/span_util.h"
#include "core/fxcrt/stl_util.h"
#include "fpdfsdk/cpdfsdk_helpers.h"

namespace {

std::vector<RetainPtr<const CPDF_Dictionary>> CollectSignatures(
    CPDF_Document* doc) {
  std::vector<RetainPtr<const CPDF_Dictionary>> signatures;
  const CPDF_Dictionary* root = doc->GetRoot();
  if (!root) {
    return signatures;
  }

  RetainPtr<const CPDF_Dictionary> acro_form =
      root->GetDictFor(pdfium::catalog::kAcroForm);
  if (!acro_form) {
    return signatures;
  }

  RetainPtr<const CPDF_Array> fields = acro_form->GetArrayFor("Fields");
  if (!fields) {
    return signatures;
  }

  CPDF_ArrayLocker locker(std::move(fields));
  for (auto& field : locker) {
    RetainPtr<const CPDF_Dictionary> field_dict = field->GetDict();
    if (field_dict && field_dict->GetNameFor(pdfium::form_fields::kFT) ==
                          pdfium::form_fields::kSig) {
      signatures.push_back(std::move(field_dict));
    }
  }
  return signatures;
}

}  // namespace

FPDF_EXPORT int FPDF_CALLCONV FPDF_GetSignatureCount(FPDF_DOCUMENT document) {
  auto* doc = CPDFDocumentFromFPDFDocument(document);
  if (!doc) {
    return -1;
  }

  return fxcrt::CollectionSize<int>(CollectSignatures(doc));
}

FPDF_EXPORT FPDF_SIGNATURE FPDF_CALLCONV
FPDF_GetSignatureObject(FPDF_DOCUMENT document, int index) {
  auto* doc = CPDFDocumentFromFPDFDocument(document);
  if (!doc) {
    return nullptr;
  }

  std::vector<RetainPtr<const CPDF_Dictionary>> signatures =
      CollectSignatures(doc);
  if (!fxcrt::IndexInBounds(signatures, index)) {
    return nullptr;
  }

  return FPDFSignatureFromCPDFDictionary(signatures[index].Get());
}

FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFSignatureObj_GetContents(FPDF_SIGNATURE signature,
                             void* buffer,
                             unsigned long length) {
  const CPDF_Dictionary* signature_dict =
      CPDFDictionaryFromFPDFSignature(signature);
  if (!signature_dict) {
    return 0;
  }
  RetainPtr<const CPDF_Dictionary> value_dict =
      signature_dict->GetDictFor(pdfium::form_fields::kV);
  if (!value_dict) {
    return 0;
  }
  // SAFETY: required from caller.
  auto result_span = UNSAFE_BUFFERS(SpanFromFPDFApiArgs(buffer, length));
  ByteString contents = value_dict->GetByteStringFor("Contents");
  fxcrt::try_spancpy(result_span, contents.span());
  return pdfium::checked_cast<unsigned long>(contents.span().size());
}

FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFSignatureObj_GetByteRange(FPDF_SIGNATURE signature,
                              int* buffer,
                              unsigned long length) {
  const CPDF_Dictionary* signature_dict =
      CPDFDictionaryFromFPDFSignature(signature);
  if (!signature_dict) {
    return 0;
  }

  RetainPtr<const CPDF_Dictionary> value_dict =
      signature_dict->GetDictFor(pdfium::form_fields::kV);
  if (!value_dict) {
    return 0;
  }

  RetainPtr<const CPDF_Array> byte_range = value_dict->GetArrayFor("ByteRange");
  if (!byte_range) {
    return 0;
  }

  const unsigned long byte_range_len =
      fxcrt::CollectionSize<unsigned long>(*byte_range);
  if (buffer && length >= byte_range_len) {
    // SAFETY: required from caller.
    auto buffer_span = UNSAFE_BUFFERS(pdfium::span(buffer, length));
    for (size_t i = 0; i < byte_range_len; ++i) {
      buffer_span[i] = byte_range->GetIntegerAt(i);
    }
  }
  return byte_range_len;
}

FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFSignatureObj_GetSubFilter(FPDF_SIGNATURE signature,
                              char* buffer,
                              unsigned long length) {
  const CPDF_Dictionary* signature_dict =
      CPDFDictionaryFromFPDFSignature(signature);
  if (!signature_dict) {
    return 0;
  }

  RetainPtr<const CPDF_Dictionary> value_dict =
      signature_dict->GetDictFor(pdfium::form_fields::kV);
  if (!value_dict || !value_dict->KeyExist("SubFilter")) {
    return 0;
  }

  ByteString sub_filter = value_dict->GetNameFor("SubFilter");

  // SAFETY: required from caller.
  return NulTerminateMaybeCopyAndReturnLength(
      sub_filter, UNSAFE_BUFFERS(SpanFromFPDFApiArgs(buffer, length)));
}

FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFSignatureObj_GetReason(FPDF_SIGNATURE signature,
                           void* buffer,
                           unsigned long length) {
  const CPDF_Dictionary* signature_dict =
      CPDFDictionaryFromFPDFSignature(signature);
  if (!signature_dict) {
    return 0;
  }

  RetainPtr<const CPDF_Dictionary> value_dict =
      signature_dict->GetDictFor(pdfium::form_fields::kV);
  if (!value_dict) {
    return 0;
  }

  RetainPtr<const CPDF_Object> obj = value_dict->GetObjectFor("Reason");
  if (!obj || !obj->IsString()) {
    return 0;
  }

  // SAFETY: required from caller.
  return Utf16EncodeMaybeCopyAndReturnLength(
      obj->GetUnicodeText(),
      UNSAFE_BUFFERS(SpanFromFPDFApiArgs(buffer, length)));
}

FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFSignatureObj_GetTime(FPDF_SIGNATURE signature,
                         char* buffer,
                         unsigned long length) {
  const CPDF_Dictionary* signature_dict =
      CPDFDictionaryFromFPDFSignature(signature);
  if (!signature_dict) {
    return 0;
  }

  RetainPtr<const CPDF_Dictionary> value_dict =
      signature_dict->GetDictFor(pdfium::form_fields::kV);
  if (!value_dict) {
    return 0;
  }

  RetainPtr<const CPDF_Object> obj = value_dict->GetObjectFor("M");
  if (!obj || !obj->IsString()) {
    return 0;
  }

  // SAFETY: required from caller.
  return NulTerminateMaybeCopyAndReturnLength(
      obj->GetString(), UNSAFE_BUFFERS(SpanFromFPDFApiArgs(buffer, length)));
}

FPDF_EXPORT unsigned int FPDF_CALLCONV
FPDFSignatureObj_GetDocMDPPermission(FPDF_SIGNATURE signature) {
  int permission = 0;
  const CPDF_Dictionary* signature_dict =
      CPDFDictionaryFromFPDFSignature(signature);
  if (!signature_dict) {
    return permission;
  }

  RetainPtr<const CPDF_Dictionary> value_dict =
      signature_dict->GetDictFor(pdfium::form_fields::kV);
  if (!value_dict) {
    return permission;
  }

  RetainPtr<const CPDF_Array> references = value_dict->GetArrayFor("Reference");
  if (!references) {
    return permission;
  }

  CPDF_ArrayLocker locker(std::move(references));
  for (auto& reference : locker) {
    RetainPtr<const CPDF_Dictionary> reference_dict = reference->GetDict();
    if (!reference_dict) {
      continue;
    }

    ByteString transform_method = reference_dict->GetNameFor("TransformMethod");
    if (transform_method != "DocMDP") {
      continue;
    }

    RetainPtr<const CPDF_Dictionary> transform_params =
        reference_dict->GetDictFor("TransformParams");
    if (!transform_params) {
      continue;
    }

    // Valid values are 1, 2 and 3; 2 is the default.
    permission = transform_params->GetIntegerFor("P", 2);
    if (permission < 1 || permission > 3) {
      permission = 0;
    }

    return permission;
  }

  return permission;
}
