Add FPDFSignatureObj_GetReason() API

This follows the same pattern as FPDFSignatureObj_GetSubFilter(), so the
client has to call this function twice, but allocation of the buffer
happens outside pdfium.

The string is user input, so work with UTF-16LE.

Change-Id: Ib8359b1f9e04c64d3a2dd082b4a34b2d25268eef
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/72310
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_signature.cpp b/fpdfsdk/fpdf_signature.cpp
index 9be14bd..548aa5f 100644
--- a/fpdfsdk/fpdf_signature.cpp
+++ b/fpdfsdk/fpdf_signature.cpp
@@ -122,3 +122,23 @@
 
   return sub_filter_len;
 }
+
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFSignatureObj_GetReason(FPDF_SIGNATURE signature,
+                           void* buffer,
+                           unsigned long length) {
+  CPDF_Dictionary* signature_dict = CPDFDictionaryFromFPDFSignature(signature);
+  if (!signature_dict)
+    return 0;
+
+  CPDF_Dictionary* value_dict = signature_dict->GetDictFor("V");
+  if (!value_dict)
+    return 0;
+
+  const CPDF_Object* obj = value_dict->GetObjectFor("Reason");
+  if (!obj || !obj->IsString())
+    return 0;
+
+  return Utf16EncodeMaybeCopyAndReturnLength(obj->GetUnicodeText(), buffer,
+                                             length);
+}
diff --git a/fpdfsdk/fpdf_signature_embeddertest.cpp b/fpdfsdk/fpdf_signature_embeddertest.cpp
index 7e1f0fc..9b04230 100644
--- a/fpdfsdk/fpdf_signature_embeddertest.cpp
+++ b/fpdfsdk/fpdf_signature_embeddertest.cpp
@@ -4,6 +4,8 @@
 
 #include "public/fpdf_signature.h"
 #include "testing/embedder_test.h"
+#include "testing/fx_string_testhelpers.h"
+#include "third_party/base/stl_util.h"
 
 class FPDFSignatureEmbedderTest : public EmbedderTest {};
 
@@ -128,3 +130,33 @@
   // FPDFSignatureObj_GetSubFilter() negative testing: no SubFilter
   ASSERT_EQ(0U, FPDFSignatureObj_GetSubFilter(signature, nullptr, 0));
 }
+
+TEST_F(FPDFSignatureEmbedderTest, GetReason) {
+  ASSERT_TRUE(OpenDocument("signature_reason.pdf"));
+  FPDF_SIGNATURE signature = FPDF_GetSignatureObject(document(), 0);
+  EXPECT_NE(nullptr, signature);
+
+  // FPDFSignatureObj_GetReason() positive testing.
+  constexpr char kReason[] = "test reason";
+  // Return value includes the terminating NUL that is provided.
+  constexpr unsigned long kReasonUTF16Size = pdfium::size(kReason) * 2;
+  constexpr wchar_t kReasonWide[] = L"test reason";
+  unsigned long size = FPDFSignatureObj_GetReason(signature, nullptr, 0);
+  ASSERT_EQ(kReasonUTF16Size, size);
+
+  std::vector<unsigned short> buffer(size);
+  ASSERT_EQ(size, FPDFSignatureObj_GetReason(signature, buffer.data(), size));
+  ASSERT_EQ(kReasonWide, GetPlatformWString(buffer.data()));
+
+  // FPDFSignatureObj_GetReason() negative testing.
+  ASSERT_EQ(0U, FPDFSignatureObj_GetReason(nullptr, nullptr, 0));
+
+  // Buffer is too small, ensure it's not modified.
+  buffer.resize(2);
+  buffer[0] = 'x';
+  buffer[1] = '\0';
+  size = FPDFSignatureObj_GetReason(signature, buffer.data(), buffer.size());
+  ASSERT_EQ(kReasonUTF16Size, size);
+  EXPECT_EQ('x', buffer[0]);
+  EXPECT_EQ('\0', buffer[1]);
+}
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 9486e29..75d2367 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -319,6 +319,7 @@
     // fpdf_signature.h
     CHK(FPDFSignatureObj_GetByteRange);
     CHK(FPDFSignatureObj_GetContents);
+    CHK(FPDFSignatureObj_GetReason);
     CHK(FPDFSignatureObj_GetSubFilter);
     CHK(FPDF_GetSignatureCount);
     CHK(FPDF_GetSignatureObject);
diff --git a/public/fpdf_signature.h b/public/fpdf_signature.h
index 1dc582e..6f079fb 100644
--- a/public/fpdf_signature.h
+++ b/public/fpdf_signature.h
@@ -94,6 +94,25 @@
                               char* buffer,
                               unsigned long length);
 
+// Experimental API.
+// Function: FPDFSignatureObj_GetReason
+//          Get the reason (comment) of the signature object.
+// Parameters:
+//          signature   -   Handle to the signature object. Returned by
+//                          FPDF_GetSignatureObject().
+//          buffer      -   The address of a buffer that receives the reason.
+//          length      -   The size, in bytes, of |buffer|.
+// Return value:
+//          Returns the number of bytes in the reason on success, 0 on error.
+//
+// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. The
+// string is terminated by a UTF16 NUL character. If |length| is less than the
+// returned length, or |buffer| is NULL, |buffer| will not be modified.
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFSignatureObj_GetReason(FPDF_SIGNATURE signature,
+                           void* buffer,
+                           unsigned long length);
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif  // __cplusplus
diff --git a/testing/resources/signature_reason.in b/testing/resources/signature_reason.in
new file mode 100644
index 0000000..2c5d0f9
--- /dev/null
+++ b/testing/resources/signature_reason.in
@@ -0,0 +1,101 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 200 300]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
+
+%% First incremental update adds an initial signature and update objects to
+%% refer to it.
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [7 0 R]
+    /SigFlags 3
+  >>
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Annots [7 0 R]
+>>
+endobj
+%% ByteRange is a pairs of integers (starting byte offset, length in bytes)
+{{object 5 0}} <<
+  /Type /Sig
+  /Filter /Adobe.PPKMS
+  /SubFilter /ETSI.CAdES.detached
+  /ByteRange [0 10 30 10]
+  /Contents <308006092A864886F70D010702A0803080020101>
+  /M (D:20200624093114+02'00')
+  /Reason <FEFF007400650073007400200072006500610073006F006E>
+>>
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 0 0]
+  /Length 0
+>>
+stream
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Sig
+  /F 132
+  /Rect [0 0 0 0]
+  /P 3 0 R
+  /T (Signature1)
+  /V 5 0 R
+  /DV 5 0 R
+  /AP <<
+    /N 6 0 R
+  >>
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/signature_reason.pdf b/testing/resources/signature_reason.pdf
new file mode 100644
index 0000000..3ad0205
--- /dev/null
+++ b/testing/resources/signature_reason.pdf
@@ -0,0 +1,125 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 200 300]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+  /Length 188
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000226 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+466
+%%EOF
+
+%% First incremental update adds an initial signature and update objects to
+%% refer to it.
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [7 0 R]
+    /SigFlags 3
+  >>
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Annots [7 0 R]
+>>
+endobj
+%% ByteRange is a pairs of integers (starting byte offset, length in bytes)
+5 0 obj <<
+  /Type /Sig
+  /Filter /Adobe.PPKMS
+  /SubFilter /ETSI.CAdES.detached
+  /ByteRange [0 10 30 10]
+  /Contents <308006092A864886F70D010702A0803080020101>
+  /M (D:20200624093114+02'00')
+  /Reason <FEFF007400650073007400200072006500610073006F006E>
+>>
+endobj
+6 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 0 0]
+  /Length 0
+>>
+stream
+endstream
+endobj
+7 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Sig
+  /F 132
+  /Rect [0 0 0 0]
+  /P 3 0 R
+  /T (Signature1)
+  /V 5 0 R
+  /DV 5 0 R
+  /AP <<
+    /N 6 0 R
+  >>
+>>
+endobj
+xref
+0 8
+0000000000 65535 f 
+0000000726 00000 n 
+0000000068 00000 n 
+0000000835 00000 n 
+0000000226 00000 n 
+0000000998 00000 n 
+0000001262 00000 n 
+0000001364 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 8
+>>
+startxref
+1536
+%%EOF