// Copyright (C) 2003-2026, Foxit Software Inc..
// All Rights Reserved.
//
// http://www.foxitsoftware.com
//
// The following code is copyrighted and contains proprietary information and trade secrets of Foxit Software Inc..
// You cannot distribute any part of Foxit PDF SDK to any third party or general public,
// unless there is a separate license agreement with Foxit Software Inc. which explicitly grants you such rights.
//
// This file contains an example to demonstrate how to use Foxit PDF SDK to add, sign, verify and get PAdES level of
// a PAdES signature in PDF document.



const FSDK = require("@foxitsoftware/foxit-pdf-sdk-node");
const fs = require('fs');
const os = require('os');
const path = require('path');
const util = require('util');

var sn = "bPxThFlBAYifO3yiW0C51bKbjPap1zHaf5aG5sX378TjFvB7Q0802A==";
var key = "8f3g1sGNtR8NAgfSNv+BvaJWy2fnrw36NZvzh8GUxYXvs+/YczAk4e34yqlOnmL4asgxGMO7+rlOcSHUSSKagx5g4G7VEXfkYLXnDDx77FOOedZPrFFcOCu1vT4+wOKzy1zkORq9cKtiFf44byH5sfRqf+OvELjep2teT9eF9yPCsSw2vRKljXlPy40DMP9XX1O5OTe+mSG3RvwKUMb0+RpG3YobM87j+/ynTDlJMMXw4vgwuTb1ZuAtlvkTdFz7HDLV81C6TYnn73ddHeGD3qYWBVsD4uJkDwyyH/SWr7QX0HSgVg8HSZqnab1VGdAUYxKY2vGWeOTJdu6qDGb5ZEb9O3kZZg+9LYAmUKzDfCYwfIsYZ8Y9LPC4wMx70kIq7JpNnzPvigrdshV8/ew1F0+qP/P5Z1EB53vDVrp2LVSuL873Wv4g3PzjZlNH+1qgT87hC+Buto4qsSUfP5Y2z0uApu+c/TfttCXbeMLSsw9TY1HQD3mkrZExMaQqRrAuXAcZWdvBnVuG3DCZmm8q0DLxJiCfYfViAU/AkbeKMIGaTJdrvIXBcssfde4nmU6qr0M4x4+Hs2ESddtY2QRtLAUBq/7h7GJlJHkEZr8ueaUy/6D/2vvAWz6wMLgmjlPbDTLqzfnCJylHCPtcJmu1SPPT9v1VreBX3+zNWcIT8HueyHEiUniqzwWkRKaotrnNb/zhnHLPpgaBSwKjyi8rOzzVwu/nY84G9ye4jsVjmkIRItKDrW7z22iS+QQuTwfqLVuMB0OD4jMkPCC2slzPfEeZnuh4+Rewln2qLRJVgI72zJ20OBWAelGSpO1Ii2Y3gOvojdmtUwx6R3Z1qFNWTTB25o9ytcux8BLOtTCkA8T8xaT4N339OTCsu/TmfahIDixKvr8cDa6F2WkBT8SOfv64W9PgiNv5B/BMOD2VWYaAl0N7yD0NkYYg/cDWRC4dfpdeip1jrr0k3mND0oSpIkxC+FicZbaYYwRdtJ9OSVWJ5OE0voLv8HgLze8mw6s4TrN3z+a2NQCveMa2VRN7YcEF7agPrvb/qU+otMwHae/wM7+f+HFAaOW+WTfzfxIVFB8/A1DN0NiEX+em4G7nO5F5ZcvfYPpaaioe8Nn33BbVLtMjXqsOBeM8AXiVfpD6YO75j7BqnBUPnivvD8BxJaaIQZvVSuQmvGm0ynt03IYwE8Rq+jYsunvsZ86jh5BiUR+Nm7sK0Qe9BdZn/7jdqohMv9czfMGUAscX+xBMunFbDpcRbWVpUADcm209L8Ndek2XY7YlCMxPgPVNvfWRF2jxehib9Tpd7zB5ZIDfAEH9zrpLfp29DLvCh2Yx8mH7xxdKG1O9Q0GdHphYHhej6smAcPm68R0GUQaTOLfXqNpueGJC1GAOI3Y4swjyt7jMTWcibnKLzwukWE5rnEDhn5n64JnyUM0ZFkRmtOiV";

const input_path = "../input_files/";
const output_path = "../output_files/";

const TIMESTAMP_SIGNATURE_FILTER = "Adobe.PPKLite"
const TIMESTAMP_SIGNATURE_SUBFILTER = "ETSI.RFC3161"
const FREE_ETSIRFC316TSA_SERVER_NAME = "FreeTSAServer"
const FREE_ETSIRFC316TSA_SERVER_URL = "http://ca.signfiles.com/TSAServer.aspx"

const ETSICADES_SIGNATURE_FILTER = "Adobe.PPKLite"
const ETSICADES_SIGNATURE_SUBFILTER = "ETSI.CAdES.detached"

function TransformSignatureStateToString(sig_state) {
  let state_str = "";
  if (sig_state & FSDK.Signature.e_TypeUnknown)
    state_str += "Unknown";
  if (sig_state & FSDK.Signature.e_StateNoSignData) {
    if (state_str != "") state_str += "|";
    state_str += "NoSignData";
  }
  if (sig_state & FSDK.Signature.e_StateUnsigned) {
    if (state_str != "") state_str += "|";
    state_str += "Unsigned";
  }
  if (sig_state & FSDK.Signature.e_StateSigned) {
    if (state_str != "") state_str += "|";
    state_str += "Signed";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyValid) {
    if (state_str != "") state_str += "|";
    state_str += "VerfiyValid";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyInvalid) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyInvalid";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyErrorData) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyErrorData";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyNoSupportWay) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyNoSupportWay";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyErrorByteRange) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyErrorByteRange";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyChange) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyChange";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyIncredible) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyIncredible";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyNoChange) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyNoChange";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyIssueValid) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyIssueValid";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyIssueUnknown) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyIssueUnknown";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyIssueRevoke) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyIssueRevoke";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyIssueExpire) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyIssueExpire";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyIssueUncheck) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyIssueUncheck";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyIssueCurrent) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyIssueCurrent";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyTimestampNone) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyTimestampNone";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyTimestampDoc) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyTimestampDoc";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyTimestampValid) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyTimestampValid";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyTimestampInvalid) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyTimestampInvalid";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyTimestampExpire) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyTimestampExpire";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyTimestampIssueUnknown) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyTimestampIssueUnknown";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyTimestampIssueValid) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyTimestampIssueValid";
  }
  if (sig_state & FSDK.Signature.e_StateVerifyTimestampTimeBefore) {
    if (state_str != "") state_str += "|";
    state_str += "VerifyTimestampTimeBefore";
  }
  if (sig_state & FSDK.Signature.e_StateCertCannotGetVRI) {
    if (state_str != "") state_str += "|";
    state_str += "CertCannotGetVRI";
  }
  return state_str;
}

function GenerateNewPDFFileName(org_file_name, suffix_name) {
  const org_length = org_file_name.length;
  const first_part = org_file_name.slice(0, org_length - 4);
  const new_file_path = `${first_part}${suffix_name}.pdf`;
  return new_file_path;
}

// get current date time
function GetLocalDateTime() {
  const date = new Date();
  let timezone_hour = date.getTimezoneOffset() / 60 * -1;
  let timezone_minute = Math.abs(date.getTimezoneOffset() % 60);
  let datetime = new FSDK.DateTime(date.getFullYear(), date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds(), timezone_hour, timezone_minute);
  return datetime;
}

function TransformLevel2String(level) {
  switch (level) {
    case FSDK.Signature.e_PAdESLevelNotPAdES:
      return "NotPades";
    case FSDK.Signature.e_PAdESLevelNone:
      return "NoneLevel";
    case FSDK.Signature.e_PAdESLevelBB:
      return "LevelB";
    case FSDK.Signature.e_PAdESLevelBT:
      return "LevelT";
    case FSDK.Signature.e_PAdESLevelBLT:
      return "LevelLT";
    case FSDK.Signature.e_PAdESLevelBLTA:
      return "LevelLTA";
    default:
      return "Unknown level value";
  }
}

function PAdESSign(input_pdf_path, signed_pdf_path, pades_level) {
  console.log("To add a PAdES signature in %s", TransformLevel2String(pades_level));
  let cached_signed_pdf_path = signed_pdf_path;
  let real_signed_pdf_path = signed_pdf_path;
  if (pades_level > FSDK.Signature.e_PAdESLevelBT) {
    cached_signed_pdf_path = `${signed_pdf_path.slice(0, signed_pdf_path.length - 4)}_cache.pdf`;
  }

  let pdf_doc = new FSDK.PDFDoc(input_pdf_path);
  pdf_doc.StartLoad("", true, null);
  let pdf_page = pdf_doc.GetPage(0);
  let page_width = pdf_page.GetWidth();
  let page_height = pdf_page.GetHeight();
  let new_sig_rect = new FSDK.RectF((page_width / 2 - 50.0), (page_height / 2 - 50.0), (page_width / 2 + 50.0), (page_height / 2 + 50.0));
  let new_signature = pdf_page.AddSignature(new_sig_rect);
  new_signature.SetFilter(ETSICADES_SIGNATURE_FILTER);
  new_signature.SetSubFilter(ETSICADES_SIGNATURE_SUBFILTER);

  new_signature.SetKeyValue(FSDK.Signature.e_KeyNameSigner, "Foxit PDF SDK");
  new_signature.SetKeyValue(FSDK.Signature.e_KeyNameContactInfo, "support@foxitsoftware.com");
  new_signature.SetKeyValue(FSDK.Signature.e_KeyNameDN, "CN=CN,MAIL=MAIL@MAIL.COM");
  new_signature.SetKeyValue(FSDK.Signature.e_KeyNameLocation, "Fuzhou, China");
  let new_value = util.format('As a sample for subfilter "%s", in %s ', ETSICADES_SIGNATURE_SUBFILTER, TransformLevel2String(pades_level));
  new_signature.SetKeyValue(FSDK.Signature.e_KeyNameReason, new_value);
  new_signature.SetKeyValue(FSDK.Signature.e_KeyNameText, new_value);
  let sign_time = GetLocalDateTime();
  new_signature.SetSignTime(sign_time);
  // Set appearance flags to decide which content would be used in appearance.
  let ap_flags = FSDK.Signature.e_APFlagLabel | FSDK.Signature.e_APFlagSigner |
    FSDK.Signature.e_APFlagReason | FSDK.Signature.e_APFlagDN |
    FSDK.Signature.e_APFlagLocation | FSDK.Signature.e_APFlagText |
    FSDK.Signature.e_APFlagSigningTime ;
  new_signature.SetAppearanceFlags(ap_flags);

  let cert_path = input_path + "foxit_all.pfx";
  let cert_password = "123456";
  let sign_progressive = new_signature.StartSign(cert_path, cert_password, FSDK.Signature.e_DigestSHA256, cached_signed_pdf_path, null, null);
  if (sign_progressive.GetRateOfProgress() != 100) {
    if (FSDK.Progressive.e_Finished != sign_progressive.Continue()) {
      console.log("[Failed] Fail to sign the new CAdES signature.");
      return;
    }
  }

  if (pades_level > FSDK.Signature.e_PAdESLevelBT) {
    let cache_pdf_doc = new FSDK.PDFDoc(cached_signed_pdf_path);
    cache_pdf_doc.StartLoad("", true, null);
    // Here, we only simply create an empty DSS object in PDF document, just as a simple exmaple.
    // In fact, user should use LTVVerifier to add DSS.
    cache_pdf_doc.CreateDSS();

    if (pades_level > FSDK.Signature.e_PAdESLevelBLT) {
      let cache_pdf_page = cache_pdf_doc.GetPage(0);
      let time_stamp_signature = cache_pdf_page.AddSignature(new FSDK.RectF(), "", FSDK.Signature.e_SignatureTypeTimeStamp, true);
      let sign_ts_progressive = time_stamp_signature.StartSign("", "", FSDK.Signature.e_DigestSHA256, real_signed_pdf_path, null, null);
      if (sign_ts_progressive.GetRateOfProgress() != 100) {
        if (FSDK.Progressive.e_Finished != sign_ts_progressive.Continue()) {
          console.log("[Failed] Fail to sign the new time stamp signature.");
          return;
        }
      }
    } else {
      cache_pdf_doc.SaveAs(real_signed_pdf_path, FSDK.PDFDoc.e_SaveFlagIncremental);
    }
  }
}

function PAdESVerify(check_pdf_path, expect_pades_level) {
  let check_pdf_doc = new FSDK.PDFDoc(check_pdf_path);
  check_pdf_doc.StartLoad("", true, null);
  console.log("To verify level of PAdES signature in file %s", check_pdf_path);

  let sig_count = check_pdf_doc.GetSignatureCount();
  if (0 == sig_count)
    console.log("No signature in current PDF file.");
  let has_cades_signature = false;
  for (let i = 0; i < check_pdf_doc.GetSignatureCount(); i++) {
    let temp_sig = check_pdf_doc.GetSignature(i);
    if (temp_sig.IsEmpty()) continue;
    let sig_org_state = temp_sig.GetState();
    let is_true = sig_org_state & FSDK.Signature.e_StateSigned;
    if (!is_true) continue;
    if (temp_sig.GetSubFilter() == ETSICADES_SIGNATURE_SUBFILTER) {
      has_cades_signature = true;
      // Verify PAdES signature.
      let verify_progressive = temp_sig.StartVerify(null, null);
      if (100 != verify_progressive.GetRateOfProgress()) {
        if (FSDK.Progressive.e_Finished != verify_progressive.Continue()) {
          console.log("[Failed] Fail to verify a PAdES signature. Signature index:%d", i);
          continue;
        }
      }
      let sig_state = temp_sig.GetState();
      console.log("Signature index: %d, a PAdES signature. State after verifying: %s", i, TransformSignatureStateToString(sig_state));

      // Get PAdES level.
      let actual_level = temp_sig.GetPAdESLevel();
      console.log("Signature index:%d, PAdES level:%s, %s", 
             i, TransformLevel2String(actual_level), 
             actual_level == expect_pades_level?"matching expected level." : "NOT match expected level.");
    }
  }
  if (false == has_cades_signature)
    console.log("No PAdES signature in current PDF file.");
}

let error_code = FSDK.Library.Initialize(sn, key);
if (FSDK.e_ErrSuccess != error_code) {
  if (FSDK.e_ErrInvalidLicense == error_code)
    console.log("[Failed] Current used Foxit PDF SDK key information is invalid.");
  else
    console.log("Library Initialize Error: %d", FSDK.e_ErrSuccess);
  return;
}

let output_directory = output_path + "pades/";
if (!fs.existsSync(output_directory))  {
  fs.mkdirSync(output_directory);
}


let input_file_name = "AboutFoxit.pdf";
let input_file_path = input_path + input_file_name;
console.log("Input file path: %s", input_file_path);
try {
  FSDK.TimeStampServerMgr.Initialize();

  let level = FSDK.Signature.e_PAdESLevelBB;
  let signed_pdf_path = output_directory + GenerateNewPDFFileName(input_file_name, TransformLevel2String(level));
  PAdESSign(input_file_path, signed_pdf_path, level);
  PAdESVerify(signed_pdf_path, level);

  let timestamp_server = FSDK.TimeStampServerMgr.AddServer(FREE_ETSIRFC316TSA_SERVER_NAME, FREE_ETSIRFC316TSA_SERVER_URL, "", "");
  FSDK.TimeStampServerMgr.SetDefaultServer(timestamp_server);
  for (level = FSDK.Signature.e_PAdESLevelBT; level <= FSDK.Signature.e_PAdESLevelBLTA; level = level+1) {
    let signed_pdf_path = output_directory + GenerateNewPDFFileName(input_file_name, TransformLevel2String(level));
    PAdESSign(input_file_path, signed_pdf_path, level);
    PAdESVerify(signed_pdf_path, level);
  }

  FSDK.TimeStampServerMgr.Release();
} catch (e) {
  console.log(e);
}
FSDK.Library.Release();
