Add FPDFAnnot_GetFormAdditionalActionJavaScript() API

This is similar to FPDFAnnot_GetFormFieldType() and allows getting the
JavaScript of a given event. Such JavaScripts are used e.g. on a text
form which wants to accept dates.

Bug: pdfium:1885
Change-Id: Ieceb3042a309b9578e8a6751a60918c7e8d8f91d
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/97950
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_annot.cpp b/fpdfsdk/fpdf_annot.cpp
index 3493485..75bdf6c 100644
--- a/fpdfsdk/fpdf_annot.cpp
+++ b/fpdfsdk/fpdf_annot.cpp
@@ -164,6 +164,21 @@
                   FPDF_OBJECT_REFERENCE,
               "CPDF_Object::kReference value mismatch");
 
+// These checks ensure the consistency of annotation additional action event
+// values across core/ and public.
+static_assert(static_cast<int>(CPDF_AAction::kKeyStroke) ==
+                  FPDF_ANNOT_AACTION_KEY_STROKE,
+              "CPDF_AAction::kKeyStroke value mismatch");
+static_assert(static_cast<int>(CPDF_AAction::kFormat) ==
+                  FPDF_ANNOT_AACTION_FORMAT,
+              "CPDF_AAction::kFormat value mismatch");
+static_assert(static_cast<int>(CPDF_AAction::kValidate) ==
+                  FPDF_ANNOT_AACTION_VALIDATE,
+              "CPDF_AAction::kValidate value mismatch");
+static_assert(static_cast<int>(CPDF_AAction::kCalculate) ==
+                  FPDF_ANNOT_AACTION_CALCULATE,
+              "CPDF_AAction::kCalculate value mismatch");
+
 bool HasAPStream(CPDF_Dictionary* pAnnotDict) {
   return !!GetAnnotAP(pAnnotDict, CPDF_Annot::AppearanceMode::kNormal);
 }
@@ -1224,6 +1239,28 @@
 }
 
 FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFAnnot_GetFormAdditionalActionJavaScript(FPDF_FORMHANDLE hHandle,
+                                            FPDF_ANNOTATION annot,
+                                            int event,
+                                            FPDF_WCHAR* buffer,
+                                            unsigned long buflen) {
+  const CPDF_FormField* pFormField = GetFormField(hHandle, annot);
+  if (!pFormField)
+    return 0;
+
+  if (event < FPDF_ANNOT_AACTION_KEY_STROKE ||
+      event > FPDF_ANNOT_AACTION_CALCULATE) {
+    return 0;
+  }
+
+  auto type = static_cast<CPDF_AAction::AActionType>(event);
+  CPDF_AAction additional_action = pFormField->GetAdditionalAction();
+  CPDF_Action action = additional_action.GetAction(type);
+  return Utf16EncodeMaybeCopyAndReturnLength(action.GetJavaScript(), buffer,
+                                             buflen);
+}
+
+FPDF_EXPORT unsigned long FPDF_CALLCONV
 FPDFAnnot_GetFormFieldValue(FPDF_FORMHANDLE hHandle,
                             FPDF_ANNOTATION annot,
                             FPDF_WCHAR* buffer,
diff --git a/fpdfsdk/fpdf_annot_embeddertest.cpp b/fpdfsdk/fpdf_annot_embeddertest.cpp
index 39c3d5e..3d07911 100644
--- a/fpdfsdk/fpdf_annot_embeddertest.cpp
+++ b/fpdfsdk/fpdf_annot_embeddertest.cpp
@@ -3612,6 +3612,42 @@
   UnloadPage(page);
 }
 
+TEST_F(FPDFAnnotEmbedderTest, AnnotationJavaScript) {
+  ASSERT_TRUE(OpenDocument("annot_javascript.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+  EXPECT_EQ(1, FPDFPage_GetAnnotCount(page));
+
+  {
+    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 0));
+    ASSERT_TRUE(annot);
+
+    // FPDFAnnot_GetFormAdditionalActionJavaScript() positive testing.
+    unsigned long length_bytes = FPDFAnnot_GetFormAdditionalActionJavaScript(
+        form_handle(), annot.get(), FPDF_ANNOT_AACTION_FORMAT, nullptr, 0);
+    ASSERT_EQ(62u, length_bytes);
+    std::vector<FPDF_WCHAR> buf = GetFPDFWideStringBuffer(length_bytes);
+    EXPECT_EQ(62u, FPDFAnnot_GetFormAdditionalActionJavaScript(
+                       form_handle(), annot.get(), FPDF_ANNOT_AACTION_FORMAT,
+                       buf.data(), length_bytes));
+    EXPECT_EQ(L"AFDate_FormatEx(\"yyyy-mm-dd\");",
+              GetPlatformWString(buf.data()));
+
+    // FPDFAnnot_GetFormAdditionalActionJavaScript() negative testing.
+    EXPECT_EQ(0u, FPDFAnnot_GetFormAdditionalActionJavaScript(
+                      form_handle(), nullptr, 0, nullptr, 0));
+    EXPECT_EQ(0u, FPDFAnnot_GetFormAdditionalActionJavaScript(
+                      nullptr, annot.get(), 0, nullptr, 0));
+    EXPECT_EQ(0u, FPDFAnnot_GetFormAdditionalActionJavaScript(
+                      form_handle(), annot.get(), 0, nullptr, 0));
+    EXPECT_EQ(2u, FPDFAnnot_GetFormAdditionalActionJavaScript(
+                      form_handle(), annot.get(), FPDF_ANNOT_AACTION_KEY_STROKE,
+                      nullptr, 0));
+  }
+
+  UnloadPage(page);
+}
+
 // Due to https://crbug.com/pdfium/570, the AnnotationBorder test above cannot
 // actually render the line annotations inside line_annot.pdf. For now, use a
 // square annotation in annots.pdf for testing.
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 6e47876..7951525 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -52,6 +52,7 @@
     CHK(FPDFAnnot_GetFocusableSubtypes);
     CHK(FPDFAnnot_GetFocusableSubtypesCount);
     CHK(FPDFAnnot_GetFontSize);
+    CHK(FPDFAnnot_GetFormAdditionalActionJavaScript);
     CHK(FPDFAnnot_GetFormControlCount);
     CHK(FPDFAnnot_GetFormControlIndex);
     CHK(FPDFAnnot_GetFormFieldAtPoint);
diff --git a/public/fpdf_annot.h b/public/fpdf_annot.h
index ccfbb0f..0c0302c 100644
--- a/public/fpdf_annot.h
+++ b/public/fpdf_annot.h
@@ -82,6 +82,16 @@
 #define FPDF_FORMFLAG_CHOICE_EDIT (1 << 18)
 #define FPDF_FORMFLAG_CHOICE_MULTI_SELECT (1 << 21)
 
+// Additional actions type of form field:
+//   K, on key stroke, JavaScript action.
+//   F, on format, JavaScript action.
+//   V, on validate, JavaScript action.
+//   C, on calculate, JavaScript action.
+#define FPDF_ANNOT_AACTION_KEY_STROKE 12
+#define FPDF_ANNOT_AACTION_FORMAT 13
+#define FPDF_ANNOT_AACTION_VALIDATE 14
+#define FPDF_ANNOT_AACTION_CALCULATE 15
+
 typedef enum FPDFANNOT_COLORTYPE {
   FPDFANNOT_COLORTYPE_Color = 0,
   FPDFANNOT_COLORTYPE_InteriorColor
@@ -495,6 +505,31 @@
                     float* border_width);
 
 // Experimental API.
+// Get the JavaScript of an event of the annotation's additional actions.
+// |buffer| is only modified if |buflen| is large enough to hold the whole
+// JavaScript string. If |buflen| is smaller, the total size of the JavaScript
+// is still returned, but nothing is copied.  If there is no JavaScript for
+// |event| in |annot|, an empty string is written to |buf| and 2 is returned,
+// denoting the size of the null terminator in the buffer.  On other errors,
+// nothing is written to |buffer| and 0 is returned.
+//
+//    hHandle     -   handle to the form fill module, returned by
+//                    FPDFDOC_InitFormFillEnvironment().
+//    annot       -   handle to an interactive form annotation.
+//    event       -   event type, one of the FPDF_ANNOT_AACTION_* values.
+//    buffer      -   buffer for holding the value string, encoded in UTF-16LE.
+//    buflen      -   length of the buffer in bytes.
+//
+// Returns the length of the string value in bytes, including the 2-byte
+// null terminator.
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFAnnot_GetFormAdditionalActionJavaScript(FPDF_FORMHANDLE hHandle,
+                                            FPDF_ANNOTATION annot,
+                                            int event,
+                                            FPDF_WCHAR* buffer,
+                                            unsigned long buflen);
+
+// Experimental API.
 // Check if |annot|'s dictionary has |key| as a key.
 //
 //   annot  - handle to an annotation.
diff --git a/testing/resources/annot_javascript.in b/testing/resources/annot_javascript.in
new file mode 100644
index 0000000..fe9ac46
--- /dev/null
+++ b/testing/resources/annot_javascript.in
@@ -0,0 +1,42 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [4 0 R]
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 612 792]
+  /Kids [3 0 R]
+  /Count 1
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /Rect [85 721 153 735]
+  /FT /Tx
+  /P 3 0 R
+  /T (Widget)
+  /AA <<
+    /F <<
+      /JS (AFDate_FormatEx\("yyyy-mm-dd"\);)
+      /S /JavaScript
+    >>
+  >>
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/annot_javascript.pdf b/testing/resources/annot_javascript.pdf
new file mode 100644
index 0000000..fbeb5df
--- /dev/null
+++ b/testing/resources/annot_javascript.pdf
@@ -0,0 +1,53 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [4 0 R]
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 612 792]
+  /Kids [3 0 R]
+  /Count 1
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [4 0 R]
+>>
+endobj
+4 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /Rect [85 721 153 735]
+  /FT /Tx
+  /P 3 0 R
+  /T (Widget)
+  /AA <<
+    /F <<
+      /JS (AFDate_FormatEx\("yyyy-mm-dd"\);)
+      /S /JavaScript
+    >>
+  >>
+>>
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000108 00000 n 
+0000000197 00000 n 
+0000000292 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+504
+%%EOF