Add experimental JavaScript retrieval APIs.
Add the following set of APIs to retrieve the JavaScript code from
actions in a document's name tree:
- FPDFDoc_GetJavaScriptActionCount()
- FPDFDoc_GetJavaScriptAction()
- FPDFDoc_CloseJavaScriptAction()
- FPDFJavaScriptAction_GetName()
- FPDFJavaScriptAction_GetScript()
This also adds the FPDF_JAVASCRIPT_ACTION type and
ScopedFPDFJavaScriptAction to help manage the lifetime of
FPDF_JAVASCRIPT_ACTION.
This is implemented on top of CPDF_Action, and improves validation in
CPDF_Action to better follow the spec.
Bug: pdfium:1253
Change-Id: I2b3ba83bb4f661e6c996c9d057fbf7d3a0c20e70
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/55750
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index bf20f6e..3ce1e08 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -120,6 +120,7 @@
"public/fpdf_flatten.h",
"public/fpdf_formfill.h",
"public/fpdf_fwlevent.h",
+ "public/fpdf_javascript.h",
"public/fpdf_ppo.h",
"public/fpdf_progressive.h",
"public/fpdf_save.h",
diff --git a/core/fpdfdoc/cpdf_action.cpp b/core/fpdfdoc/cpdf_action.cpp
index 18e4927..74a7c6f 100644
--- a/core/fpdfdoc/cpdf_action.cpp
+++ b/core/fpdfdoc/cpdf_action.cpp
@@ -10,6 +10,7 @@
#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_name.h"
#include "core/fpdfdoc/cpdf_filespec.h"
#include "core/fpdfdoc/cpdf_nametree.h"
@@ -33,6 +34,14 @@
if (!m_pDict)
return Unknown;
+ // Validate |m_pDict|. Type is optional, but must be valid if present.
+ const CPDF_Object* pType = m_pDict->GetObjectFor("Type");
+ if (pType) {
+ const CPDF_Name* pName = pType->AsName();
+ if (!pName || pName->GetString() != "Action")
+ return Unknown;
+ }
+
ByteString csType = m_pDict->GetStringFor("S");
if (csType.IsEmpty())
return Unknown;
@@ -115,13 +124,16 @@
return m_pDict->GetIntegerFor("Flags");
}
-WideString CPDF_Action::GetJavaScript() const {
- if (!m_pDict)
- return WideString();
+Optional<WideString> CPDF_Action::MaybeGetJavaScript() const {
+ const CPDF_Object* pObject = GetJavaScriptObject();
+ if (!pObject)
+ return pdfium::nullopt;
+ return pObject->GetUnicodeText();
+}
- const CPDF_Object* pJS = m_pDict->GetDirectObjectFor("JS");
- return (pJS && (pJS->IsString() || pJS->IsStream())) ? pJS->GetUnicodeText()
- : WideString();
+WideString CPDF_Action::GetJavaScript() const {
+ const CPDF_Object* pObject = GetJavaScriptObject();
+ return pObject ? pObject->GetUnicodeText() : WideString();
}
size_t CPDF_Action::GetSubActionsCount() const {
@@ -150,3 +162,11 @@
}
return CPDF_Action(nullptr);
}
+
+const CPDF_Object* CPDF_Action::GetJavaScriptObject() const {
+ if (!m_pDict)
+ return nullptr;
+
+ const CPDF_Object* pJS = m_pDict->GetDirectObjectFor("JS");
+ return (pJS && (pJS->IsString() || pJS->IsStream())) ? pJS : nullptr;
+}
diff --git a/core/fpdfdoc/cpdf_action.h b/core/fpdfdoc/cpdf_action.h
index ccc40aa..391f175 100644
--- a/core/fpdfdoc/cpdf_action.h
+++ b/core/fpdfdoc/cpdf_action.h
@@ -10,9 +10,11 @@
#include "core/fpdfdoc/cpdf_dest.h"
#include "core/fxcrt/fx_string.h"
#include "core/fxcrt/retain_ptr.h"
+#include "third_party/base/optional.h"
class CPDF_Dictionary;
class CPDF_Document;
+class CPDF_Object;
class CPDF_Action {
public:
@@ -51,11 +53,18 @@
bool GetHideStatus() const;
ByteString GetNamedAction() const;
uint32_t GetFlags() const;
+
+ // Differentiates between empty JS entry and no JS entry.
+ Optional<WideString> MaybeGetJavaScript() const;
+ // Returns empty string for empty JS entry and no JS entry.
WideString GetJavaScript() const;
+
size_t GetSubActionsCount() const;
CPDF_Action GetSubAction(size_t iIndex) const;
private:
+ const CPDF_Object* GetJavaScriptObject() const;
+
RetainPtr<const CPDF_Dictionary> const m_pDict;
};
diff --git a/fpdfsdk/BUILD.gn b/fpdfsdk/BUILD.gn
index 62df18b..463d5ca 100644
--- a/fpdfsdk/BUILD.gn
+++ b/fpdfsdk/BUILD.gn
@@ -58,6 +58,7 @@
"fpdf_ext.cpp",
"fpdf_flatten.cpp",
"fpdf_formfill.cpp",
+ "fpdf_javascript.cpp",
"fpdf_ppo.cpp",
"fpdf_progressive.cpp",
"fpdf_save.cpp",
@@ -149,6 +150,7 @@
"fpdf_ext_embeddertest.cpp",
"fpdf_flatten_embeddertest.cpp",
"fpdf_formfill_embeddertest.cpp",
+ "fpdf_javascript_embeddertest.cpp",
"fpdf_ppo_embeddertest.cpp",
"fpdf_save_embeddertest.cpp",
"fpdf_searchex_embeddertest.cpp",
diff --git a/fpdfsdk/cpdfsdk_helpers.h b/fpdfsdk/cpdfsdk_helpers.h
index 8eca854..a06cc39 100644
--- a/fpdfsdk/cpdfsdk_helpers.h
+++ b/fpdfsdk/cpdfsdk_helpers.h
@@ -42,6 +42,7 @@
class CPDFSDK_InteractiveForm;
class IPDFSDK_PauseAdapter;
class FX_PATHPOINT;
+struct CPDF_JavaScript;
#ifdef PDF_ENABLE_XFA
class CPDFXFA_Context;
@@ -117,6 +118,15 @@
return reinterpret_cast<CPDF_Font*>(font);
}
+inline FPDF_JAVASCRIPT_ACTION FPDFJavaScriptActionFromCPDFJavaScriptAction(
+ CPDF_JavaScript* javascript) {
+ return reinterpret_cast<FPDF_JAVASCRIPT_ACTION>(javascript);
+}
+inline CPDF_JavaScript* CPDFJavaScriptActionFromFPDFJavaScriptAction(
+ FPDF_JAVASCRIPT_ACTION javascript) {
+ return reinterpret_cast<CPDF_JavaScript*>(javascript);
+}
+
inline FPDF_LINK FPDFLinkFromCPDFDictionary(CPDF_Dictionary* link) {
return reinterpret_cast<FPDF_LINK>(link);
}
diff --git a/fpdfsdk/fpdf_javascript.cpp b/fpdfsdk/fpdf_javascript.cpp
new file mode 100644
index 0000000..c2d119b
--- /dev/null
+++ b/fpdfsdk/fpdf_javascript.cpp
@@ -0,0 +1,85 @@
+// Copyright 2019 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "public/fpdf_javascript.h"
+
+#include <memory>
+
+#include "core/fpdfapi/parser/cpdf_dictionary.h"
+#include "core/fpdfapi/parser/cpdf_document.h"
+#include "core/fpdfdoc/cpdf_action.h"
+#include "core/fpdfdoc/cpdf_nametree.h"
+#include "fpdfsdk/cpdfsdk_helpers.h"
+#include "third_party/base/ptr_util.h"
+
+struct CPDF_JavaScript {
+ WideString name;
+ WideString script;
+};
+
+FPDF_EXPORT int FPDF_CALLCONV
+FPDFDoc_GetJavaScriptActionCount(FPDF_DOCUMENT document) {
+ CPDF_Document* doc = CPDFDocumentFromFPDFDocument(document);
+ return doc ? CPDF_NameTree(doc, "JavaScript").GetCount() : -1;
+}
+
+FPDF_EXPORT FPDF_JAVASCRIPT_ACTION FPDF_CALLCONV
+FPDFDoc_GetJavaScriptAction(FPDF_DOCUMENT document, int index) {
+ CPDF_Document* doc = CPDFDocumentFromFPDFDocument(document);
+ if (!doc || index < 0)
+ return nullptr;
+
+ CPDF_NameTree name_tree(doc, "JavaScript");
+ if (static_cast<size_t>(index) >= name_tree.GetCount())
+ return nullptr;
+
+ WideString name;
+ CPDF_Dictionary* obj =
+ ToDictionary(name_tree.LookupValueAndName(index, &name));
+ if (!obj)
+ return nullptr;
+
+ // Validate |obj|. Type is optional, but must be valid if present.
+ CPDF_Action action(obj);
+ if (action.GetType() != CPDF_Action::JavaScript)
+ return nullptr;
+
+ Optional<WideString> script = action.MaybeGetJavaScript();
+ if (!script.has_value())
+ return nullptr;
+
+ auto js = pdfium::MakeUnique<CPDF_JavaScript>();
+ js->name = name;
+ js->script = script.value();
+ return FPDFJavaScriptActionFromCPDFJavaScriptAction(js.release());
+}
+
+FPDF_EXPORT void FPDF_CALLCONV
+FPDFDoc_CloseJavaScriptAction(FPDF_JAVASCRIPT_ACTION javascript) {
+ // Take object back across API and destroy it.
+ std::unique_ptr<CPDF_JavaScript>(
+ CPDFJavaScriptActionFromFPDFJavaScriptAction(javascript));
+}
+
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFJavaScriptAction_GetName(FPDF_JAVASCRIPT_ACTION javascript,
+ FPDF_WCHAR* buffer,
+ unsigned long buflen) {
+ CPDF_JavaScript* js =
+ CPDFJavaScriptActionFromFPDFJavaScriptAction(javascript);
+ if (!js)
+ return 0;
+ return Utf16EncodeMaybeCopyAndReturnLength(js->name, buffer, buflen);
+}
+
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFJavaScriptAction_GetScript(FPDF_JAVASCRIPT_ACTION javascript,
+ FPDF_WCHAR* buffer,
+ unsigned long buflen) {
+ CPDF_JavaScript* js =
+ CPDFJavaScriptActionFromFPDFJavaScriptAction(javascript);
+ if (!js)
+ return 0;
+ return Utf16EncodeMaybeCopyAndReturnLength(js->script, buffer, buflen);
+}
diff --git a/fpdfsdk/fpdf_javascript_embeddertest.cpp b/fpdfsdk/fpdf_javascript_embeddertest.cpp
new file mode 100644
index 0000000..35deb69
--- /dev/null
+++ b/fpdfsdk/fpdf_javascript_embeddertest.cpp
@@ -0,0 +1,130 @@
+// Copyright 2019 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "core/fxcrt/fx_memory.h"
+#include "public/fpdf_javascript.h"
+#include "public/fpdfview.h"
+#include "testing/embedder_test.h"
+#include "testing/fx_string_testhelpers.h"
+#include "testing/utils/hash.h"
+
+class FPDFJavaScriptEmbedderTest : public EmbedderTest {};
+
+TEST_F(FPDFJavaScriptEmbedderTest, CountJS) {
+ // Open a file with JS.
+ ASSERT_TRUE(OpenDocument("bug_679649.pdf"));
+ EXPECT_EQ(1, FPDFDoc_GetJavaScriptActionCount(document()));
+}
+
+TEST_F(FPDFJavaScriptEmbedderTest, CountNoJS) {
+ // Open a file without JS.
+ ASSERT_TRUE(OpenDocument("hello_world.pdf"));
+ EXPECT_EQ(0, FPDFDoc_GetJavaScriptActionCount(document()));
+
+ // Provide no document.
+ EXPECT_EQ(-1, FPDFDoc_GetJavaScriptActionCount(nullptr));
+}
+
+TEST_F(FPDFJavaScriptEmbedderTest, GetJS) {
+ ASSERT_TRUE(OpenDocument("js.pdf"));
+ EXPECT_EQ(6, FPDFDoc_GetJavaScriptActionCount(document()));
+
+ ScopedFPDFJavaScriptAction js;
+ js.reset(FPDFDoc_GetJavaScriptAction(document(), -1));
+ EXPECT_FALSE(js);
+ js.reset(FPDFDoc_GetJavaScriptAction(document(), 6));
+ EXPECT_FALSE(js);
+ js.reset(FPDFDoc_GetJavaScriptAction(nullptr, -1));
+ EXPECT_FALSE(js);
+ js.reset(FPDFDoc_GetJavaScriptAction(nullptr, 0));
+ EXPECT_FALSE(js);
+ js.reset(FPDFDoc_GetJavaScriptAction(nullptr, 1));
+ EXPECT_FALSE(js);
+ js.reset(FPDFDoc_GetJavaScriptAction(nullptr, 2));
+ EXPECT_FALSE(js);
+ js.reset(FPDFDoc_GetJavaScriptAction(nullptr, 5));
+ EXPECT_FALSE(js);
+ js.reset(FPDFDoc_GetJavaScriptAction(nullptr, 6));
+ EXPECT_FALSE(js);
+
+ js.reset(FPDFDoc_GetJavaScriptAction(document(), 0));
+ EXPECT_TRUE(js);
+ js.reset(FPDFDoc_GetJavaScriptAction(document(), 1));
+ EXPECT_TRUE(js);
+ js.reset(FPDFDoc_GetJavaScriptAction(document(), 2));
+ EXPECT_TRUE(js);
+ js.reset(FPDFDoc_GetJavaScriptAction(document(), 3));
+ EXPECT_FALSE(js);
+ js.reset(FPDFDoc_GetJavaScriptAction(document(), 4));
+ EXPECT_FALSE(js);
+ js.reset(FPDFDoc_GetJavaScriptAction(document(), 5));
+ EXPECT_FALSE(js);
+}
+
+TEST_F(FPDFJavaScriptEmbedderTest, GetJSName) {
+ ASSERT_TRUE(OpenDocument("bug_679649.pdf"));
+ ScopedFPDFJavaScriptAction js(FPDFDoc_GetJavaScriptAction(document(), 0));
+ ASSERT_TRUE(js);
+
+ {
+ FPDF_WCHAR buf[10];
+ EXPECT_EQ(0u, FPDFJavaScriptAction_GetName(nullptr, nullptr, 0));
+ EXPECT_EQ(0u, FPDFJavaScriptAction_GetName(nullptr, buf, 0));
+ EXPECT_EQ(0u, FPDFJavaScriptAction_GetName(nullptr, buf, sizeof(buf)));
+ }
+
+ constexpr size_t kExpectedLength = 22;
+ ASSERT_EQ(kExpectedLength,
+ FPDFJavaScriptAction_GetName(js.get(), nullptr, 0));
+
+ // Check that the name not returned if the buffer is too small.
+ // The result buffer should be overwritten with an empty string.
+ std::vector<FPDF_WCHAR> buf = GetFPDFWideStringBuffer(kExpectedLength);
+ // Write in the buffer to verify it's not overwritten.
+ memcpy(buf.data(), "abcdefgh", 8);
+ EXPECT_EQ(kExpectedLength, FPDFJavaScriptAction_GetName(js.get(), buf.data(),
+ kExpectedLength - 1));
+ EXPECT_EQ(0, memcmp(buf.data(), "abcdefgh", 8));
+
+ EXPECT_EQ(kExpectedLength, FPDFJavaScriptAction_GetName(js.get(), buf.data(),
+ kExpectedLength));
+ EXPECT_EQ(L"startDelay", GetPlatformWString(buf.data()));
+}
+
+TEST_F(FPDFJavaScriptEmbedderTest, GetJSScript) {
+ ASSERT_TRUE(OpenDocument("bug_679649.pdf"));
+ ScopedFPDFJavaScriptAction js(FPDFDoc_GetJavaScriptAction(document(), 0));
+ ASSERT_TRUE(js);
+
+ {
+ FPDF_WCHAR buf[10];
+ EXPECT_EQ(0u, FPDFJavaScriptAction_GetScript(nullptr, nullptr, 0));
+ EXPECT_EQ(0u, FPDFJavaScriptAction_GetScript(nullptr, buf, 0));
+ EXPECT_EQ(0u, FPDFJavaScriptAction_GetScript(nullptr, buf, sizeof(buf)));
+ }
+
+ constexpr size_t kExpectedLength = 218;
+ ASSERT_EQ(kExpectedLength,
+ FPDFJavaScriptAction_GetScript(js.get(), nullptr, 0));
+
+ // Check that the string value of an AP is not returned if the buffer is too
+ // small. The result buffer should be overwritten with an empty string.
+ std::vector<FPDF_WCHAR> buf = GetFPDFWideStringBuffer(kExpectedLength);
+ // Write in the buffer to verify it's not overwritten.
+ memcpy(buf.data(), "abcdefgh", 8);
+ EXPECT_EQ(kExpectedLength, FPDFJavaScriptAction_GetScript(
+ js.get(), buf.data(), kExpectedLength - 1));
+ EXPECT_EQ(0, memcmp(buf.data(), "abcdefgh", 8));
+
+ static const wchar_t kExpectedScript[] =
+ L"function ping() {\n app.alert(\"ping\");\n}\n"
+ L"var timer = app.setTimeOut(\"ping()\", 100);\napp.clearTimeOut(timer);";
+ EXPECT_EQ(kExpectedLength, FPDFJavaScriptAction_GetScript(
+ js.get(), buf.data(), kExpectedLength));
+ EXPECT_EQ(kExpectedScript, GetPlatformWString(buf.data()));
+}
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index cf1c14f..396b6d9 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -19,6 +19,7 @@
#include "public/fpdf_flatten.h"
#include "public/fpdf_formfill.h"
#include "public/fpdf_fwlevent.h"
+#include "public/fpdf_javascript.h"
#include "public/fpdf_ppo.h"
#include "public/fpdf_progressive.h"
#include "public/fpdf_save.h"
@@ -271,6 +272,13 @@
CHK(FPDF_SetFormFieldHighlightAlpha);
CHK(FPDF_SetFormFieldHighlightColor);
+ // fpdf_javascript.h
+ CHK(FPDFDoc_CloseJavaScriptAction);
+ CHK(FPDFDoc_GetJavaScriptAction);
+ CHK(FPDFDoc_GetJavaScriptActionCount);
+ CHK(FPDFJavaScriptAction_GetName);
+ CHK(FPDFJavaScriptAction_GetScript);
+
// fpdf_ppo.h
CHK(FPDF_CopyViewerPreferences);
CHK(FPDF_ImportNPagesToOne);
diff --git a/public/cpp/fpdf_deleters.h b/public/cpp/fpdf_deleters.h
index 9a700e3..633ddf5 100644
--- a/public/cpp/fpdf_deleters.h
+++ b/public/cpp/fpdf_deleters.h
@@ -9,6 +9,7 @@
#include "public/fpdf_dataavail.h"
#include "public/fpdf_edit.h"
#include "public/fpdf_formfill.h"
+#include "public/fpdf_javascript.h"
#include "public/fpdf_structtree.h"
#include "public/fpdf_text.h"
#include "public/fpdf_transformpage.h"
@@ -48,6 +49,12 @@
}
};
+struct FPDFJavaScriptActionDeleter {
+ inline void operator()(FPDF_JAVASCRIPT_ACTION javascript) {
+ FPDFDoc_CloseJavaScriptAction(javascript);
+ }
+};
+
struct FPDFPageDeleter {
inline void operator()(FPDF_PAGE page) { FPDF_ClosePage(page); }
};
diff --git a/public/cpp/fpdf_scopers.h b/public/cpp/fpdf_scopers.h
index ebae107..ff57c1b 100644
--- a/public/cpp/fpdf_scopers.h
+++ b/public/cpp/fpdf_scopers.h
@@ -9,13 +9,6 @@
#include <type_traits>
#include "public/cpp/fpdf_deleters.h"
-#include "public/fpdf_annot.h"
-#include "public/fpdf_dataavail.h"
-#include "public/fpdf_edit.h"
-#include "public/fpdf_formfill.h"
-#include "public/fpdf_structtree.h"
-#include "public/fpdf_text.h"
-#include "public/fpdfview.h"
// Versions of FPDF types that clean up the object at scope exit.
@@ -44,6 +37,10 @@
std::unique_ptr<std::remove_pointer<FPDF_FORMHANDLE>::type,
FPDFFormHandleDeleter>;
+using ScopedFPDFJavaScriptAction =
+ std::unique_ptr<std::remove_pointer<FPDF_JAVASCRIPT_ACTION>::type,
+ FPDFJavaScriptActionDeleter>;
+
using ScopedFPDFPage =
std::unique_ptr<std::remove_pointer<FPDF_PAGE>::type, FPDFPageDeleter>;
diff --git a/public/fpdf_javascript.h b/public/fpdf_javascript.h
new file mode 100644
index 0000000..19f3810
--- /dev/null
+++ b/public/fpdf_javascript.h
@@ -0,0 +1,77 @@
+// Copyright 2019 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PUBLIC_FPDF_JAVASCRIPT_H_
+#define PUBLIC_FPDF_JAVASCRIPT_H_
+
+// NOLINTNEXTLINE(build/include)
+#include "fpdfview.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+// Experimental API.
+// Get the number of JavaScript actions in |document|.
+//
+// document - handle to a document.
+//
+// Returns the number of JavaScript actions in |document| or -1 on error.
+FPDF_EXPORT int FPDF_CALLCONV
+FPDFDoc_GetJavaScriptActionCount(FPDF_DOCUMENT document);
+
+// Experimental API.
+// Get the JavaScript action at |index| in |document|.
+//
+// document - handle to a document.
+// index - the index of the requested JavaScript action.
+//
+// Returns the handle to the JavaScript action, or NULL on failure.
+// Caller owns the returned handle and must close it with
+// FPDFDoc_CloseJavaScriptAction().
+FPDF_EXPORT FPDF_JAVASCRIPT_ACTION FPDF_CALLCONV
+FPDFDoc_GetJavaScriptAction(FPDF_DOCUMENT document, int index);
+
+// Experimental API.
+// Close a loaded FPDF_JAVASCRIPT_ACTION object.
+
+// javascript - Handle to a JavaScript action.
+FPDF_EXPORT void FPDF_CALLCONV
+FPDFDoc_CloseJavaScriptAction(FPDF_JAVASCRIPT_ACTION javascript);
+
+// Experimental API.
+// Get the name from the |javascript| handle. |buffer| is only modified if
+// |buflen| is longer than the length of the name. On errors, |buffer| is
+// unmodified and the returned length is 0.
+//
+// javascript - handle to an JavaScript action.
+// buffer - buffer for holding the name, encoded in UTF-16LE.
+// buflen - length of the buffer in bytes.
+//
+// Returns the length of the JavaScript action name in bytes.
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFJavaScriptAction_GetName(FPDF_JAVASCRIPT_ACTION javascript,
+ FPDF_WCHAR* buffer,
+ unsigned long buflen);
+
+// Experimental API.
+// Get the script from the |javascript| handle. |buffer| is only modified if
+// |buflen| is longer than the length of the script. On errors, |buffer| is
+// unmodified and the returned length is 0.
+//
+// javascript - handle to an JavaScript action.
+// buffer - buffer for holding the name, encoded in UTF-16LE.
+// buflen - length of the buffer in bytes.
+//
+// Returns the length of the JavaScript action name in bytes.
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFJavaScriptAction_GetScript(FPDF_JAVASCRIPT_ACTION javascript,
+ FPDF_WCHAR* buffer,
+ unsigned long buflen);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif // __cplusplus
+
+#endif // PUBLIC_FPDF_JAVASCRIPT_H_
diff --git a/public/fpdfview.h b/public/fpdfview.h
index 6bbc2bf..55ab33c 100644
--- a/public/fpdfview.h
+++ b/public/fpdfview.h
@@ -45,6 +45,7 @@
typedef struct fpdf_document_t__* FPDF_DOCUMENT;
typedef struct fpdf_font_t__* FPDF_FONT;
typedef struct fpdf_form_handle_t__* FPDF_FORMHANDLE;
+typedef struct fpdf_javascript_action_t* FPDF_JAVASCRIPT_ACTION;
typedef struct fpdf_link_t__* FPDF_LINK;
typedef struct fpdf_page_t__* FPDF_PAGE;
typedef struct fpdf_pagelink_t__* FPDF_PAGELINK;
diff --git a/testing/resources/js.in b/testing/resources/js.in
new file mode 100644
index 0000000..7d35139
--- /dev/null
+++ b/testing/resources/js.in
@@ -0,0 +1,83 @@
+{{header}}
+{{object 1 0}} <<
+ /Type /Catalog
+ /Pages 2 0 R
+ /Names <</JavaScript 4 0 R>>
+>>
+endobj
+{{object 2 0}} <<
+ /Type /Pages
+ /Count 1
+ /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+ /Type /Page
+ /Parent 2 0 R
+ /MediaBox [0 0 612 792]
+ /CropBox [0 0 612 792]
+>>
+endobj
+{{object 4 0}} <<
+ /Names [
+ (normal) 5 0 R
+ (encoded_subtype) 6 0 R
+ (no_type) 7 0 R
+ (wrongtype) 8 0 R
+ (wrongsubtype) 9 0 R
+ (nojs) 10 0 R
+ ]
+>>
+endobj
+{{object 5 0}} <<
+ /Type /Action
+ /S /JavaScript
+ /JS 11 0 R
+>>
+endobj
+{{object 6 0}} <<
+ /Type /Action
+ /S /J#61v#61Script
+ /JS 11 0 R
+>>
+endobj
+{{object 7 0}} <<
+ /S /JavaScript
+ /JS 12 0 R
+>>
+endobj
+{{object 8 0}} <<
+ /Type /action
+ /S /JavaScript
+ /JS 12 0 R
+>>
+endobj
+{{object 9 0}} <<
+ /Type /Action
+ /S /Javascript
+ /JS 12 0 R
+>>
+endobj
+{{object 10 0}} <<
+ /Type /Action
+ /S /JavaScript
+>>
+endobj
+{{object 11 0}} <<
+>>
+stream
+app.alert("ping");
+endstream
+endobj
+{{object 12 0}} <<
+>>
+stream
+app.alert("pong");
+endstream
+endobj
+{{xref}}
+trailer <<
+ /Root 1 0 R
+>>
+{{startxref}}
+%%EOF
diff --git a/testing/resources/js.pdf b/testing/resources/js.pdf
new file mode 100644
index 0000000..07e9b75
--- /dev/null
+++ b/testing/resources/js.pdf
@@ -0,0 +1,99 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /Pages 2 0 R
+ /Names <</JavaScript 4 0 R>>
+>>
+endobj
+2 0 obj <<
+ /Type /Pages
+ /Count 1
+ /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+ /Type /Page
+ /Parent 2 0 R
+ /MediaBox [0 0 612 792]
+ /CropBox [0 0 612 792]
+>>
+endobj
+4 0 obj <<
+ /Names [
+ (normal) 5 0 R
+ (encoded_subtype) 6 0 R
+ (no_type) 7 0 R
+ (wrongtype) 8 0 R
+ (wrongsubtype) 9 0 R
+ (nojs) 10 0 R
+ ]
+>>
+endobj
+5 0 obj <<
+ /Type /Action
+ /S /JavaScript
+ /JS 11 0 R
+>>
+endobj
+6 0 obj <<
+ /Type /Action
+ /S /J#61v#61Script
+ /JS 11 0 R
+>>
+endobj
+7 0 obj <<
+ /S /JavaScript
+ /JS 12 0 R
+>>
+endobj
+8 0 obj <<
+ /Type /action
+ /S /JavaScript
+ /JS 12 0 R
+>>
+endobj
+9 0 obj <<
+ /Type /Action
+ /S /Javascript
+ /JS 12 0 R
+>>
+endobj
+10 0 obj <<
+ /Type /Action
+ /S /JavaScript
+>>
+endobj
+11 0 obj <<
+>>
+stream
+app.alert("ping");
+endstream
+endobj
+12 0 obj <<
+>>
+stream
+app.alert("pong");
+endstream
+endobj
+xref
+0 13
+0000000000 65535 f
+0000000015 00000 n
+0000000099 00000 n
+0000000162 00000 n
+0000000264 00000 n
+0000000432 00000 n
+0000000499 00000 n
+0000000570 00000 n
+0000000621 00000 n
+0000000688 00000 n
+0000000755 00000 n
+0000000810 00000 n
+0000000868 00000 n
+trailer <<
+ /Root 1 0 R
+>>
+startxref
+926
+%%EOF