add file annot api
Added experimental public API for getting
and creating file attachment annotations.
Bug: pdfium:2125
Change-Id: Idc405e88eed1c8efaa7d690c11251ad8f4ede64d
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/116490
Reviewed-by: Thomas Sepez <tsepez@google.com>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Thomas Sepez <tsepez@google.com>
diff --git a/AUTHORS b/AUTHORS
index 0689774..6e4b6e4 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -22,6 +22,7 @@
Felix Kauselmann <licorn@gmail.com>
GiWan Go <gogil@stealien.com>
Huy Ngo <huyna89@gmail.com>
+Ivan Odulo <ivanodulo@gmail.com>
Jiang Jiang <jiangj@opera.com>
Justin Pierce <brkfstmnchr@gmail.com>
Ke Liu <stackexploit@gmail.com>
diff --git a/fpdfsdk/fpdf_annot.cpp b/fpdfsdk/fpdf_annot.cpp
index c4b912b..573a072 100644
--- a/fpdfsdk/fpdf_annot.cpp
+++ b/fpdfsdk/fpdf_annot.cpp
@@ -341,6 +341,7 @@
// The supported subtypes must also be communicated in the user doc.
switch (subtype) {
case FPDF_ANNOT_CIRCLE:
+ case FPDF_ANNOT_FILEATTACHMENT:
case FPDF_ANNOT_FREETEXT:
case FPDF_ANNOT_HIGHLIGHT:
case FPDF_ANNOT_INK:
@@ -1473,3 +1474,51 @@
action->SetNewFor<CPDF_String>("URI", uri, /*bHex=*/false);
return true;
}
+
+FPDF_EXPORT FPDF_ATTACHMENT FPDF_CALLCONV
+FPDFAnnot_GetFileAttachment(FPDF_ANNOTATION annot) {
+ if (FPDFAnnot_GetSubtype(annot) != FPDF_ANNOT_FILEATTACHMENT) {
+ return nullptr;
+ }
+
+ RetainPtr<CPDF_Dictionary> annot_dict =
+ GetMutableAnnotDictFromFPDFAnnotation(annot);
+ if (!annot_dict) {
+ return nullptr;
+ }
+
+ return FPDFAttachmentFromCPDFObject(
+ annot_dict->GetMutableDirectObjectFor("FS"));
+}
+
+FPDF_EXPORT FPDF_ATTACHMENT FPDF_CALLCONV
+FPDFAnnot_AddFileAttachment(FPDF_ANNOTATION annot, FPDF_WIDESTRING name) {
+ if (FPDFAnnot_GetSubtype(annot) != FPDF_ANNOT_FILEATTACHMENT) {
+ return nullptr;
+ }
+
+ CPDF_AnnotContext* context = CPDFAnnotContextFromFPDFAnnotation(annot);
+ if (!context) {
+ return nullptr;
+ }
+
+ RetainPtr<CPDF_Dictionary> annot_dict = context->GetMutableAnnotDict();
+ if (!annot_dict) {
+ return nullptr;
+ }
+
+ WideString ws_name = WideStringFromFPDFWideString(name);
+ if (ws_name.IsEmpty()) {
+ return nullptr;
+ }
+
+ CPDF_Document* doc = context->GetPage()->GetDocument();
+ auto fs_obj = doc->NewIndirect<CPDF_Dictionary>();
+
+ fs_obj->SetNewFor<CPDF_Name>("Type", "Filespec");
+ fs_obj->SetNewFor<CPDF_String>("UF", ws_name.AsStringView());
+ fs_obj->SetNewFor<CPDF_String>("F", ws_name.AsStringView());
+
+ annot_dict->SetNewFor<CPDF_Reference>("FS", doc, fs_obj->GetObjNum());
+ return FPDFAttachmentFromCPDFObject(fs_obj);
+}
diff --git a/fpdfsdk/fpdf_annot_embeddertest.cpp b/fpdfsdk/fpdf_annot_embeddertest.cpp
index ca3efa7..17fde6a 100644
--- a/fpdfsdk/fpdf_annot_embeddertest.cpp
+++ b/fpdfsdk/fpdf_annot_embeddertest.cpp
@@ -21,6 +21,7 @@
#include "core/fxge/cfx_defaultrenderdevice.h"
#include "fpdfsdk/cpdfsdk_helpers.h"
#include "public/cpp/fpdf_scopers.h"
+#include "public/fpdf_attachment.h"
#include "public/fpdf_edit.h"
#include "public/fpdf_formfill.h"
#include "public/fpdfview.h"
@@ -3830,3 +3831,116 @@
CloseSavedPage(page);
CloseSavedDocument();
}
+
+TEST_F(FPDFAnnotEmbedderTest, GetAndAddFileAttachmentAnnotation) {
+ ASSERT_TRUE(OpenDocument("annotation_fileattachment.pdf"));
+ FPDF_PAGE page = LoadPage(0);
+ ASSERT_TRUE(page);
+ EXPECT_EQ(1, FPDFPage_GetAnnotCount(page));
+
+ {
+ ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 0));
+ ASSERT_TRUE(annot);
+ EXPECT_EQ(FPDF_ANNOT_FILEATTACHMENT, FPDFAnnot_GetSubtype(annot.get()));
+
+ FPDF_ATTACHMENT attachment = FPDFAnnot_GetFileAttachment(annot.get());
+ ASSERT_TRUE(attachment);
+
+ // Check that the name of the attachment is correct.
+ unsigned long length_bytes = FPDFAttachment_GetName(attachment, nullptr, 0);
+ ASSERT_EQ(18u, length_bytes);
+ std::vector<FPDF_WCHAR> buf = GetFPDFWideStringBuffer(length_bytes);
+ EXPECT_EQ(18u,
+ FPDFAttachment_GetName(attachment, buf.data(), length_bytes));
+ EXPECT_EQ(L"test.txt", GetPlatformWString(buf.data()));
+
+ // Check that the content of the attachment is correct.
+ ASSERT_TRUE(FPDFAttachment_GetFile(attachment, nullptr, 0, &length_bytes));
+ std::vector<uint8_t> content_buf(length_bytes);
+ unsigned long actual_length_bytes;
+ ASSERT_TRUE(FPDFAttachment_GetFile(attachment, content_buf.data(),
+ length_bytes, &actual_length_bytes));
+ ASSERT_THAT(content_buf, testing::ElementsAre('t', 'e', 's', 't', ' ', 't',
+ 'e', 'x', 't'));
+ }
+
+ {
+ // Add a file attachment annotation to the page.
+ ScopedFPDFAnnotation annot(
+ FPDFPage_CreateAnnot(page, FPDF_ANNOT_FILEATTACHMENT));
+ ASSERT_TRUE(annot);
+
+ // Check that there is now 2 annotations on this page.
+ EXPECT_EQ(2, FPDFPage_GetAnnotCount(page));
+
+ ScopedFPDFWideString file_name = GetFPDFWideString(L"0.txt");
+ FPDF_ATTACHMENT attachment =
+ FPDFAnnot_AddFileAttachment(annot.get(), file_name.get());
+ ASSERT_TRUE(attachment);
+
+ // The filling of the FPDF_ATTACHMENT has been tested in
+ // fpdf_attachment_embeddertest.cpp
+ }
+
+ {
+ ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 1));
+ ASSERT_TRUE(annot);
+ EXPECT_EQ(FPDF_ANNOT_FILEATTACHMENT, FPDFAnnot_GetSubtype(annot.get()));
+
+ // Check that we can read newly created file spec
+ FPDF_ATTACHMENT attachment = FPDFAnnot_GetFileAttachment(annot.get());
+ ASSERT_TRUE(attachment);
+
+ // Verify the name of the new attachment.
+ unsigned long length_bytes = FPDFAttachment_GetName(attachment, nullptr, 0);
+ ASSERT_EQ(12u, length_bytes);
+ std::vector<FPDF_WCHAR> buf = GetFPDFWideStringBuffer(length_bytes);
+ EXPECT_EQ(12u,
+ FPDFAttachment_GetName(attachment, buf.data(), length_bytes));
+ EXPECT_EQ(L"0.txt", GetPlatformWString(buf.data()));
+ }
+
+ UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, BadCasesFileAttachmentAnnotation) {
+ ASSERT_TRUE(OpenDocument("annotation_fileattachment.pdf"));
+ FPDF_PAGE page = LoadPage(0);
+ ASSERT_TRUE(page);
+ EXPECT_EQ(1, FPDFPage_GetAnnotCount(page));
+
+ {
+ ASSERT_FALSE(FPDFAnnot_GetFileAttachment(nullptr));
+
+ ScopedFPDFAnnotation text_annot(
+ FPDFPage_CreateAnnot(page, FPDF_ANNOT_TEXT));
+ ASSERT_TRUE(text_annot);
+ ASSERT_FALSE(FPDFAnnot_GetFileAttachment(text_annot.get()));
+
+ ScopedFPDFAnnotation newly_file_annot(
+ FPDFPage_CreateAnnot(page, FPDF_ANNOT_FILEATTACHMENT));
+ ASSERT_TRUE(newly_file_annot);
+ ASSERT_FALSE(FPDFAnnot_GetFileAttachment(newly_file_annot.get()));
+ }
+
+ {
+ ScopedFPDFWideString empty_name = GetFPDFWideString(L"");
+ ScopedFPDFWideString not_empty_name = GetFPDFWideString(L"0.txt");
+
+ ASSERT_FALSE(FPDFAnnot_AddFileAttachment(nullptr, nullptr));
+ ASSERT_FALSE(FPDFAnnot_AddFileAttachment(nullptr, empty_name.get()));
+ ASSERT_FALSE(FPDFAnnot_AddFileAttachment(nullptr, not_empty_name.get()));
+
+ ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 0));
+ ASSERT_TRUE(annot);
+
+ ASSERT_FALSE(FPDFAnnot_AddFileAttachment(annot.get(), nullptr));
+ ASSERT_FALSE(FPDFAnnot_AddFileAttachment(annot.get(), empty_name.get()));
+
+ FPDF_ATTACHMENT old_attachment = FPDFAnnot_GetFileAttachment(annot.get());
+ EXPECT_NE(old_attachment,
+ FPDFAnnot_AddFileAttachment(annot.get(), not_empty_name.get()));
+ }
+
+ UnloadPage(page);
+}
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 4f8bccf..f8c5483 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -40,6 +40,7 @@
// Function to call from gtest harness to ensure linker resolution.
int CheckPDFiumCApi() {
// fpdf_annot.h
+ CHK(FPDFAnnot_AddFileAttachment);
CHK(FPDFAnnot_AddInkStroke);
CHK(FPDFAnnot_AppendAttachmentPoints);
CHK(FPDFAnnot_AppendObject);
@@ -48,6 +49,7 @@
CHK(FPDFAnnot_GetAttachmentPoints);
CHK(FPDFAnnot_GetBorder);
CHK(FPDFAnnot_GetColor);
+ CHK(FPDFAnnot_GetFileAttachment);
CHK(FPDFAnnot_GetFlags);
CHK(FPDFAnnot_GetFocusableSubtypes);
CHK(FPDFAnnot_GetFocusableSubtypesCount);
diff --git a/public/fpdf_annot.h b/public/fpdf_annot.h
index abcef6b..337da58 100644
--- a/public/fpdf_annot.h
+++ b/public/fpdf_annot.h
@@ -101,6 +101,7 @@
// Check if an annotation subtype is currently supported for creation.
// Currently supported subtypes:
// - circle
+// - fileattachment
// - freetext
// - highlight
// - ink
@@ -968,6 +969,25 @@
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_SetURI(FPDF_ANNOTATION annot,
const char* uri);
+// Experimental API.
+// Get the attachment from |annot|.
+//
+// annot - handle to a file annotation.
+//
+// Returns the handle to the attachment object, or NULL on failure.
+FPDF_EXPORT FPDF_ATTACHMENT FPDF_CALLCONV
+FPDFAnnot_GetFileAttachment(FPDF_ANNOTATION annot);
+
+// Experimental API.
+// Add an embedded file with |name| to |annot|.
+//
+// annot - handle to a file annotation.
+// name - name of the new attachment.
+//
+// Returns a handle to the new attachment object, or NULL on failure.
+FPDF_EXPORT FPDF_ATTACHMENT FPDF_CALLCONV
+FPDFAnnot_AddFileAttachment(FPDF_ANNOTATION annot, FPDF_WIDESTRING name);
+
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
diff --git a/testing/resources/annotation_fileattachment.pdf b/testing/resources/annotation_fileattachment.pdf
new file mode 100644
index 0000000..6d1f954
--- /dev/null
+++ b/testing/resources/annotation_fileattachment.pdf
Binary files differ