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

#include "testing/fuzzers/pdfium_fuzzer_helper.h"

#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sstream>
#include <string>
#include <tuple>
#include <utility>

#include "public/cpp/fpdf_scopers.h"
#include "public/fpdf_dataavail.h"
#include "public/fpdf_ext.h"
#include "public/fpdf_text.h"
#include "third_party/base/check_op.h"
#include "third_party/base/containers/span.h"
#include "third_party/base/numerics/checked_math.h"

namespace {

class FuzzerTestLoader {
 public:
  explicit FuzzerTestLoader(pdfium::span<const char> span) : m_Span(span) {}

  static int GetBlock(void* param,
                      unsigned long pos,
                      unsigned char* pBuf,
                      unsigned long size) {
    FuzzerTestLoader* pLoader = static_cast<FuzzerTestLoader*>(param);
    pdfium::base::CheckedNumeric<size_t> end = pos;
    end += size;
    CHECK_LE(end.ValueOrDie(), pLoader->m_Span.size());

    memcpy(pBuf, &pLoader->m_Span[pos], size);
    return 1;
  }

 private:
  const pdfium::span<const char> m_Span;
};

int ExampleAppAlert(IPDF_JSPLATFORM*,
                    FPDF_WIDESTRING,
                    FPDF_WIDESTRING,
                    int,
                    int) {
  return 0;
}

int ExampleAppResponse(IPDF_JSPLATFORM*,
                       FPDF_WIDESTRING question,
                       FPDF_WIDESTRING title,
                       FPDF_WIDESTRING default_value,
                       FPDF_WIDESTRING label,
                       FPDF_BOOL is_password,
                       void* response,
                       int length) {
  // UTF-16, always LE regardless of platform.
  uint8_t* ptr = static_cast<uint8_t*>(response);
  ptr[0] = 'N';
  ptr[1] = 0;
  ptr[2] = 'o';
  ptr[3] = 0;
  return 4;
}

void ExampleDocGotoPage(IPDF_JSPLATFORM*, int pageNumber) {}

void ExampleDocMail(IPDF_JSPLATFORM*,
                    void* mailData,
                    int length,
                    FPDF_BOOL UI,
                    FPDF_WIDESTRING To,
                    FPDF_WIDESTRING Subject,
                    FPDF_WIDESTRING CC,
                    FPDF_WIDESTRING BCC,
                    FPDF_WIDESTRING Msg) {}

FPDF_BOOL Is_Data_Avail(FX_FILEAVAIL* pThis, size_t offset, size_t size) {
  return true;
}

void Add_Segment(FX_DOWNLOADHINTS* pThis, size_t offset, size_t size) {}

std::pair<int, int> GetRenderingAndFormFlagFromData(const char* data,
                                                    size_t len) {
  std::string data_str = std::string(data, len);
  size_t data_hash = std::hash<std::string>()(data_str);

  // The largest flag value is 0x4FFF, so just take 16 bits from |data_hash| at
  // a time.
  int render_flags = data_hash & 0xffff;
  int form_flags = (data_hash >> 16) & 0xffff;
  return std::make_pair(render_flags, form_flags);
}

}  // namespace

PDFiumFuzzerHelper::PDFiumFuzzerHelper() = default;

PDFiumFuzzerHelper::~PDFiumFuzzerHelper() = default;

bool PDFiumFuzzerHelper::OnFormFillEnvLoaded(FPDF_DOCUMENT doc) {
  return true;
}

void PDFiumFuzzerHelper::RenderPdf(const char* data, size_t len) {
  int render_flags;
  int form_flags;
  std::tie(render_flags, form_flags) =
      GetRenderingAndFormFlagFromData(data, len);

  IPDF_JSPLATFORM platform_callbacks;
  memset(&platform_callbacks, '\0', sizeof(platform_callbacks));
  platform_callbacks.version = 3;
  platform_callbacks.app_alert = ExampleAppAlert;
  platform_callbacks.app_response = ExampleAppResponse;
  platform_callbacks.Doc_gotoPage = ExampleDocGotoPage;
  platform_callbacks.Doc_mail = ExampleDocMail;

  FPDF_FORMFILLINFO form_callbacks;
  memset(&form_callbacks, '\0', sizeof(form_callbacks));
  form_callbacks.version = GetFormCallbackVersion();
  form_callbacks.m_pJsPlatform = &platform_callbacks;

  FuzzerTestLoader loader({data, len});
  FPDF_FILEACCESS file_access;
  memset(&file_access, '\0', sizeof(file_access));
  file_access.m_FileLen = static_cast<unsigned long>(len);
  file_access.m_GetBlock = FuzzerTestLoader::GetBlock;
  file_access.m_Param = &loader;

  FX_FILEAVAIL file_avail;
  memset(&file_avail, '\0', sizeof(file_avail));
  file_avail.version = 1;
  file_avail.IsDataAvail = Is_Data_Avail;

  FX_DOWNLOADHINTS hints;
  memset(&hints, '\0', sizeof(hints));
  hints.version = 1;
  hints.AddSegment = Add_Segment;

  ScopedFPDFAvail pdf_avail(FPDFAvail_Create(&file_avail, &file_access));

  int nRet = PDF_DATA_NOTAVAIL;
  bool bIsLinearized = false;
  ScopedFPDFDocument doc;
  if (FPDFAvail_IsLinearized(pdf_avail.get()) == PDF_LINEARIZED) {
    doc.reset(FPDFAvail_GetDocument(pdf_avail.get(), nullptr));
    if (doc) {
      while (nRet == PDF_DATA_NOTAVAIL)
        nRet = FPDFAvail_IsDocAvail(pdf_avail.get(), &hints);

      if (nRet == PDF_DATA_ERROR)
        return;

      nRet = FPDFAvail_IsFormAvail(pdf_avail.get(), &hints);
      if (nRet == PDF_FORM_ERROR || nRet == PDF_FORM_NOTAVAIL)
        return;

      bIsLinearized = true;
    }
  } else {
    doc.reset(FPDF_LoadCustomDocument(&file_access, nullptr));
  }

  if (!doc)
    return;

  (void)FPDF_GetDocPermissions(doc.get());

  ScopedFPDFFormHandle form(
      FPDFDOC_InitFormFillEnvironment(doc.get(), &form_callbacks));
  if (!OnFormFillEnvLoaded(doc.get()))
    return;

  FPDF_SetFormFieldHighlightColor(form.get(), FPDF_FORMFIELD_UNKNOWN, 0xFFE4DD);
  FPDF_SetFormFieldHighlightAlpha(form.get(), 100);
  FORM_DoDocumentJSAction(form.get());
  FORM_DoDocumentOpenAction(form.get());

  int page_count = FPDF_GetPageCount(doc.get());
  for (int i = 0; i < page_count; ++i) {
    if (bIsLinearized) {
      nRet = PDF_DATA_NOTAVAIL;
      while (nRet == PDF_DATA_NOTAVAIL)
        nRet = FPDFAvail_IsPageAvail(pdf_avail.get(), i, &hints);

      if (nRet == PDF_DATA_ERROR)
        return;
    }
    RenderPage(doc.get(), form.get(), i, render_flags, form_flags);
  }
  OnRenderFinished(doc.get());
  FORM_DoDocumentAAction(form.get(), FPDFDOC_AACTION_WC);
}

bool PDFiumFuzzerHelper::RenderPage(FPDF_DOCUMENT doc,
                                    FPDF_FORMHANDLE form,
                                    int page_index,
                                    int render_flags,
                                    int form_flags) {
  ScopedFPDFPage page(FPDF_LoadPage(doc, page_index));
  if (!page)
    return false;

  ScopedFPDFTextPage text_page(FPDFText_LoadPage(page.get()));
  FORM_OnAfterLoadPage(page.get(), form);
  FORM_DoPageAAction(page.get(), form, FPDFPAGE_AACTION_OPEN);

  FormActionHandler(form, doc, page.get());

  const double scale = 1.0;
  int width = static_cast<int>(FPDF_GetPageWidthF(page.get()) * scale);
  int height = static_cast<int>(FPDF_GetPageHeightF(page.get()) * scale);
  ScopedFPDFBitmap bitmap(FPDFBitmap_Create(width, height, 0));
  if (bitmap) {
    FPDFBitmap_FillRect(bitmap.get(), 0, 0, width, height, 0xFFFFFFFF);
    FPDF_RenderPageBitmap(bitmap.get(), page.get(), 0, 0, width, height, 0,
                          render_flags);
    FPDF_FFLDraw(form, bitmap.get(), page.get(), 0, 0, width, height, 0,
                 form_flags);
  }
  FORM_DoPageAAction(page.get(), form, FPDFPAGE_AACTION_CLOSE);
  FORM_OnBeforeClosePage(page.get(), form);
  return !!bitmap;
}
