| // Copyright 2017 The PDFium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string> |
| #include <vector> |
| |
| #include "public/fpdf_attachment.h" |
| #include "public/fpdfview.h" |
| #include "testing/embedder_test.h" |
| #include "testing/fx_string_testhelpers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/utils/hash.h" |
| |
| static constexpr char kDateKey[] = "CreationDate"; |
| static constexpr char kChecksumKey[] = "CheckSum"; |
| |
| class FPDFAttachmentEmbedderTest : public EmbedderTest {}; |
| |
| TEST_F(FPDFAttachmentEmbedderTest, ExtractAttachments) { |
| // Open a file with two attachments. |
| ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); |
| EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); |
| |
| // Try to retrieve attachments at bad indices. |
| EXPECT_FALSE(FPDFDoc_GetAttachment(document(), -1)); |
| EXPECT_FALSE(FPDFDoc_GetAttachment(document(), 2)); |
| |
| // Retrieve the first attachment. |
| FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(document(), 0); |
| ASSERT_TRUE(attachment); |
| |
| // Check that the name of the first attachment is correct. |
| 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"1.txt", GetPlatformWString(buf.data())); |
| |
| // Check some unsuccessful cases of FPDFAttachment_GetFile. |
| EXPECT_FALSE(FPDFAttachment_GetFile(attachment, nullptr, 0, nullptr)); |
| EXPECT_FALSE(FPDFAttachment_GetFile(nullptr, nullptr, 0, &length_bytes)); |
| |
| // Check that the content of the first 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')); |
| |
| // Check that a non-existent key does not exist. |
| EXPECT_FALSE(FPDFAttachment_HasKey(attachment, "none")); |
| |
| // Check that the string value of a non-string dictionary entry is empty. |
| static constexpr char kSizeKey[] = "Size"; |
| EXPECT_EQ(FPDF_OBJECT_NUMBER, |
| FPDFAttachment_GetValueType(attachment, kSizeKey)); |
| EXPECT_EQ(2u, |
| FPDFAttachment_GetStringValue(attachment, kSizeKey, nullptr, 0)); |
| |
| // Check that the creation date of the first attachment is correct. |
| length_bytes = |
| FPDFAttachment_GetStringValue(attachment, kDateKey, nullptr, 0); |
| ASSERT_EQ(48u, length_bytes); |
| buf = GetFPDFWideStringBuffer(length_bytes); |
| EXPECT_EQ(48u, FPDFAttachment_GetStringValue(attachment, kDateKey, buf.data(), |
| length_bytes)); |
| EXPECT_EQ(L"D:20170712214438-07'00'", GetPlatformWString(buf.data())); |
| |
| // Retrieve the second attachment. |
| attachment = FPDFDoc_GetAttachment(document(), 1); |
| ASSERT_TRUE(attachment); |
| |
| // Retrieve the second attachment file. |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, nullptr, 0, &length_bytes)); |
| content_buf.clear(); |
| content_buf.resize(length_bytes); |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, content_buf.data(), |
| length_bytes, &actual_length_bytes)); |
| ASSERT_EQ(5869u, actual_length_bytes); |
| |
| // Check that the calculated checksum of the file data matches expectation. |
| const char kCheckSum[] = "72afcddedf554dda63c0c88e06f1ce18"; |
| const wchar_t kCheckSumW[] = L"<72AFCDDEDF554DDA63C0C88E06F1CE18>"; |
| const std::string generated_checksum = GenerateMD5Base16(content_buf); |
| EXPECT_EQ(kCheckSum, generated_checksum); |
| |
| // Check that the stored checksum matches expectation. |
| length_bytes = |
| FPDFAttachment_GetStringValue(attachment, kChecksumKey, nullptr, 0); |
| ASSERT_EQ(70u, length_bytes); |
| buf = GetFPDFWideStringBuffer(length_bytes); |
| EXPECT_EQ(70u, FPDFAttachment_GetStringValue(attachment, kChecksumKey, |
| buf.data(), length_bytes)); |
| EXPECT_EQ(kCheckSumW, GetPlatformWString(buf.data())); |
| } |
| |
| TEST_F(FPDFAttachmentEmbedderTest, NoAttachmentToExtract) { |
| // Open a file with no attachments. |
| ASSERT_TRUE(OpenDocument("hello_world.pdf")); |
| EXPECT_EQ(0, FPDFDoc_GetAttachmentCount(document())); |
| |
| // Try to retrieve attachments at bad indices. |
| EXPECT_FALSE(FPDFDoc_GetAttachment(document(), -1)); |
| EXPECT_FALSE(FPDFDoc_GetAttachment(document(), 0)); |
| } |
| |
| TEST_F(FPDFAttachmentEmbedderTest, InvalidAttachmentData) { |
| // Open a file with an attachment that is missing the embedded file (/EF). |
| ASSERT_TRUE(OpenDocument("embedded_attachments_invalid_data.pdf")); |
| ASSERT_EQ(1, FPDFDoc_GetAttachmentCount(document())); |
| |
| // Retrieve the first attachment. |
| FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(document(), 0); |
| ASSERT_TRUE(attachment); |
| |
| // Check that the name of the attachment is correct. |
| 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("1.txt", GetPlatformString(buf.data())); |
| |
| // Check that is is not possible to retrieve the file data. |
| EXPECT_FALSE(FPDFAttachment_GetFile(attachment, nullptr, 0, &length_bytes)); |
| |
| // Check that the attachment can be deleted. |
| EXPECT_TRUE(FPDFDoc_DeleteAttachment(document(), 0)); |
| EXPECT_EQ(0, FPDFDoc_GetAttachmentCount(document())); |
| } |
| |
| TEST_F(FPDFAttachmentEmbedderTest, AddAttachments) { |
| // Open a file with two attachments. |
| ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); |
| EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); |
| |
| // Check that adding an attachment with an empty name would fail. |
| EXPECT_FALSE(FPDFDoc_AddAttachment(document(), nullptr)); |
| |
| // Add an attachment to the beginning of the embedded file list. |
| ScopedFPDFWideString file_name = GetFPDFWideString(L"0.txt"); |
| FPDF_ATTACHMENT attachment = |
| FPDFDoc_AddAttachment(document(), file_name.get()); |
| ASSERT_TRUE(attachment); |
| |
| // Check that writing to a file with nullptr but non-zero bytes would fail. |
| EXPECT_FALSE(FPDFAttachment_SetFile(attachment, document(), nullptr, 10)); |
| |
| // Set the new attachment's file. |
| constexpr char kContents1[] = "Hello!"; |
| EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents1, |
| strlen(kContents1))); |
| EXPECT_EQ(3, FPDFDoc_GetAttachmentCount(document())); |
| |
| // Verify the name of the new attachment (i.e. the first attachment). |
| attachment = FPDFDoc_GetAttachment(document(), 0); |
| ASSERT_TRUE(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())); |
| |
| // Verify the content of the new attachment (i.e. the first attachment). |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, nullptr, 0, &length_bytes)); |
| std::vector<char> content_buf(length_bytes); |
| unsigned long actual_length_bytes; |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, content_buf.data(), |
| length_bytes, &actual_length_bytes)); |
| ASSERT_EQ(6u, actual_length_bytes); |
| EXPECT_EQ(std::string(kContents1), std::string(content_buf.data(), 6)); |
| |
| // Add an attachment to the end of the embedded file list and set its file. |
| file_name = GetFPDFWideString(L"z.txt"); |
| attachment = FPDFDoc_AddAttachment(document(), file_name.get()); |
| ASSERT_TRUE(attachment); |
| constexpr char kContents2[] = "World!"; |
| EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents2, |
| strlen(kContents2))); |
| EXPECT_EQ(4, FPDFDoc_GetAttachmentCount(document())); |
| |
| // Verify the name of the new attachment (i.e. the fourth attachment). |
| attachment = FPDFDoc_GetAttachment(document(), 3); |
| ASSERT_TRUE(attachment); |
| length_bytes = FPDFAttachment_GetName(attachment, nullptr, 0); |
| ASSERT_EQ(12u, length_bytes); |
| buf = GetFPDFWideStringBuffer(length_bytes); |
| EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), length_bytes)); |
| EXPECT_EQ(L"z.txt", GetPlatformWString(buf.data())); |
| |
| // Verify the content of the new attachment (i.e. the fourth attachment). |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, nullptr, 0, &length_bytes)); |
| content_buf.clear(); |
| content_buf.resize(length_bytes); |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, content_buf.data(), |
| length_bytes, &actual_length_bytes)); |
| ASSERT_EQ(6u, actual_length_bytes); |
| EXPECT_EQ(std::string(kContents2), std::string(content_buf.data(), 6)); |
| } |
| |
| TEST_F(FPDFAttachmentEmbedderTest, AddAttachmentsWithParams) { |
| // Open a file with two attachments. |
| ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); |
| EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); |
| |
| // Add an attachment to the embedded file list. |
| ScopedFPDFWideString file_name = GetFPDFWideString(L"5.txt"); |
| FPDF_ATTACHMENT attachment = |
| FPDFDoc_AddAttachment(document(), file_name.get()); |
| ASSERT_TRUE(attachment); |
| constexpr char kContents[] = "Hello World!"; |
| EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents, |
| strlen(kContents))); |
| |
| // Set the date to be an arbitrary value. |
| constexpr wchar_t kDateW[] = L"D:20170720161527-04'00'"; |
| ScopedFPDFWideString ws_date = GetFPDFWideString(kDateW); |
| EXPECT_TRUE( |
| FPDFAttachment_SetStringValue(attachment, kDateKey, ws_date.get())); |
| |
| // Set the checksum to be an arbitrary value. |
| constexpr wchar_t kCheckSumW[] = L"<ABCDEF01234567899876543210FEDCBA>"; |
| ScopedFPDFWideString ws_checksum = GetFPDFWideString(kCheckSumW); |
| EXPECT_TRUE(FPDFAttachment_SetStringValue(attachment, kChecksumKey, |
| ws_checksum.get())); |
| |
| // Verify the name of the new attachment (i.e. the second attachment). |
| attachment = FPDFDoc_GetAttachment(document(), 1); |
| ASSERT_TRUE(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"5.txt", GetPlatformWString(buf.data())); |
| |
| // Verify the content of the new attachment. |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, nullptr, 0, &length_bytes)); |
| std::vector<char> content_buf(length_bytes); |
| unsigned long actual_length_bytes; |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, content_buf.data(), |
| length_bytes, &actual_length_bytes)); |
| ASSERT_EQ(12u, actual_length_bytes); |
| EXPECT_EQ(std::string(kContents), std::string(content_buf.data(), 12)); |
| |
| // Verify the creation date of the new attachment. |
| length_bytes = |
| FPDFAttachment_GetStringValue(attachment, kDateKey, nullptr, 0); |
| ASSERT_EQ(48u, length_bytes); |
| buf = GetFPDFWideStringBuffer(length_bytes); |
| EXPECT_EQ(48u, FPDFAttachment_GetStringValue(attachment, kDateKey, buf.data(), |
| length_bytes)); |
| EXPECT_EQ(kDateW, GetPlatformWString(buf.data())); |
| |
| // Verify the checksum of the new attachment. |
| length_bytes = |
| FPDFAttachment_GetStringValue(attachment, kChecksumKey, nullptr, 0); |
| ASSERT_EQ(70u, length_bytes); |
| buf = GetFPDFWideStringBuffer(length_bytes); |
| EXPECT_EQ(70u, FPDFAttachment_GetStringValue(attachment, kChecksumKey, |
| buf.data(), length_bytes)); |
| EXPECT_EQ(kCheckSumW, GetPlatformWString(buf.data())); |
| |
| // Overwrite the existing file with empty content, and check that the checksum |
| // gets updated to the correct value. |
| EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), nullptr, 0)); |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, nullptr, 0, &length_bytes)); |
| EXPECT_EQ(0u, length_bytes); |
| length_bytes = |
| FPDFAttachment_GetStringValue(attachment, kChecksumKey, nullptr, 0); |
| ASSERT_EQ(70u, length_bytes); |
| buf = GetFPDFWideStringBuffer(length_bytes); |
| EXPECT_EQ(70u, FPDFAttachment_GetStringValue(attachment, kChecksumKey, |
| buf.data(), length_bytes)); |
| EXPECT_EQ(L"<D41D8CD98F00B204E9800998ECF8427E>", |
| GetPlatformWString(buf.data())); |
| } |
| |
| TEST_F(FPDFAttachmentEmbedderTest, AddAttachmentsToFileWithNoAttachments) { |
| // Open a file with no attachments. |
| ASSERT_TRUE(OpenDocument("hello_world.pdf")); |
| EXPECT_EQ(0, FPDFDoc_GetAttachmentCount(document())); |
| |
| // Add an attachment to the beginning of the embedded file list. |
| ScopedFPDFWideString file_name = GetFPDFWideString(L"0.txt"); |
| FPDF_ATTACHMENT attachment = |
| FPDFDoc_AddAttachment(document(), file_name.get()); |
| ASSERT_TRUE(attachment); |
| |
| // Set the new attachment's file. |
| constexpr char kContents1[] = "Hello!"; |
| EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents1, |
| strlen(kContents1))); |
| EXPECT_EQ(1, FPDFDoc_GetAttachmentCount(document())); |
| |
| // Verify the name of the new attachment (i.e. the first attachment). |
| attachment = FPDFDoc_GetAttachment(document(), 0); |
| ASSERT_TRUE(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())); |
| |
| // Verify the content of the new attachment (i.e. the first attachment). |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, nullptr, 0, &length_bytes)); |
| std::vector<char> content_buf(length_bytes); |
| unsigned long actual_length_bytes; |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, content_buf.data(), |
| length_bytes, &actual_length_bytes)); |
| ASSERT_EQ(6u, actual_length_bytes); |
| EXPECT_EQ(std::string(kContents1), std::string(content_buf.data(), 6)); |
| |
| // Add an attachment to the end of the embedded file list and set its file. |
| file_name = GetFPDFWideString(L"z.txt"); |
| attachment = FPDFDoc_AddAttachment(document(), file_name.get()); |
| ASSERT_TRUE(attachment); |
| constexpr char kContents2[] = "World!"; |
| EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents2, |
| strlen(kContents2))); |
| EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); |
| |
| // Verify the name of the new attachment (i.e. the second attachment). |
| attachment = FPDFDoc_GetAttachment(document(), 1); |
| ASSERT_TRUE(attachment); |
| length_bytes = FPDFAttachment_GetName(attachment, nullptr, 0); |
| ASSERT_EQ(12u, length_bytes); |
| buf = GetFPDFWideStringBuffer(length_bytes); |
| EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), length_bytes)); |
| EXPECT_EQ(L"z.txt", GetPlatformWString(buf.data())); |
| |
| // Verify the content of the new attachment (i.e. the second attachment). |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, nullptr, 0, &length_bytes)); |
| content_buf.clear(); |
| content_buf.resize(length_bytes); |
| ASSERT_TRUE(FPDFAttachment_GetFile(attachment, content_buf.data(), |
| length_bytes, &actual_length_bytes)); |
| ASSERT_EQ(6u, actual_length_bytes); |
| EXPECT_EQ(std::string(kContents2), std::string(content_buf.data(), 6)); |
| } |
| |
| TEST_F(FPDFAttachmentEmbedderTest, DeleteAttachment) { |
| // Open a file with two attachments. |
| ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); |
| EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); |
| |
| // Verify the name of the first attachment. |
| FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(document(), 0); |
| ASSERT_TRUE(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"1.txt", GetPlatformWString(buf.data())); |
| |
| // Delete the first attachment. |
| EXPECT_TRUE(FPDFDoc_DeleteAttachment(document(), 0)); |
| EXPECT_EQ(1, FPDFDoc_GetAttachmentCount(document())); |
| |
| // Verify the name of the new first attachment. |
| attachment = FPDFDoc_GetAttachment(document(), 0); |
| ASSERT_TRUE(attachment); |
| length_bytes = FPDFAttachment_GetName(attachment, nullptr, 0); |
| ASSERT_EQ(26u, length_bytes); |
| buf = GetFPDFWideStringBuffer(length_bytes); |
| EXPECT_EQ(26u, FPDFAttachment_GetName(attachment, buf.data(), length_bytes)); |
| EXPECT_EQ(L"attached.pdf", GetPlatformWString(buf.data())); |
| } |
| |
| TEST_F(FPDFAttachmentEmbedderTest, GetStringValueForChecksumNotString) { |
| ASSERT_TRUE(OpenDocument("embedded_attachments_invalid_types.pdf")); |
| EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); |
| |
| FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(document(), 0); |
| ASSERT_TRUE(attachment); |
| |
| // The checksum key is a name, which violates the spec. This will still return |
| // the value, but should not crash. |
| constexpr unsigned long kExpectedLength = 8u; |
| ASSERT_EQ(kExpectedLength, FPDFAttachment_GetStringValue( |
| attachment, kChecksumKey, nullptr, 0)); |
| std::vector<FPDF_WCHAR> buf = GetFPDFWideStringBuffer(kExpectedLength); |
| EXPECT_EQ(kExpectedLength, |
| FPDFAttachment_GetStringValue(attachment, kChecksumKey, buf.data(), |
| kExpectedLength)); |
| EXPECT_EQ(L"Bad", GetPlatformWString(buf.data())); |
| } |
| |
| TEST_F(FPDFAttachmentEmbedderTest, GetStringValueForNotString) { |
| ASSERT_TRUE(OpenDocument("embedded_attachments_invalid_types.pdf")); |
| EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); |
| |
| FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(document(), 1); |
| ASSERT_TRUE(attachment); |
| |
| // The checksum key is a stream, while the API requires a string or name. |
| constexpr unsigned long kExpectedLength = 2u; |
| ASSERT_EQ(kExpectedLength, FPDFAttachment_GetStringValue( |
| attachment, kChecksumKey, nullptr, 0)); |
| std::vector<FPDF_WCHAR> buf = GetFPDFWideStringBuffer(kExpectedLength); |
| EXPECT_EQ(kExpectedLength, |
| FPDFAttachment_GetStringValue(attachment, kChecksumKey, buf.data(), |
| kExpectedLength)); |
| EXPECT_EQ(L"", GetPlatformWString(buf.data())); |
| } |