From 0c17bdac6ce07754402385720d3a0e70ce179949 Mon Sep 17 00:00:00 2001 From: Dan Sinclair Date: Wed, 14 Jun 2017 13:50:05 -0400 Subject: [Merge M59] Allow zero length streams when parsing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's possible to create a stream of length 0 in a PDF document. Currently the code will early exit and return a nullptr. This causes issues when you want to print the given PDF as the FPDF_ImportPages code ends up only generating up to the zero length object. This CL allows creating streams with length 0 and updates the PDF saving code to output a blank stream. Bug: chromium:732380 Change-Id: I44182ba4aaac7c51284b002ba01bbc34b6bcf9e0 Reviewed-on: https://pdfium-review.googlesource.com/6490 Reviewed-by: Lei Zhang Commit-Queue: dsinclair (cherry picked from commit 957480c17682008ae2a14723868fcdcab89b6577) Reviewed-on: https://pdfium-review.googlesource.com/6556 Reviewed-by: Nicolás Peña --- core/fpdfapi/edit/fpdf_edit_create.cpp | 13 ++++-- core/fpdfapi/parser/cpdf_syntax_parser.cpp | 8 ++-- fpdfsdk/fpdfppo_embeddertest.cpp | 40 ++++++++++++++-- testing/embedder_test.cpp | 15 ++++-- testing/embedder_test.h | 5 ++ testing/resources/zero_length_stream.in | 63 +++++++++++++++++++++++++ testing/resources/zero_length_stream.pdf | 74 ++++++++++++++++++++++++++++++ 7 files changed, 202 insertions(+), 16 deletions(-) create mode 100644 testing/resources/zero_length_stream.in create mode 100644 testing/resources/zero_length_stream.pdf diff --git a/core/fpdfapi/edit/fpdf_edit_create.cpp b/core/fpdfapi/edit/fpdf_edit_create.cpp index f643d3f24e..4bdb63cb3c 100644 --- a/core/fpdfapi/edit/fpdf_edit_create.cpp +++ b/core/fpdfapi/edit/fpdf_edit_create.cpp @@ -182,12 +182,15 @@ int32_t PDF_CreatorAppendObject(const CPDF_Object* pObj, return -1; } offset += 8; - auto pAcc = pdfium::MakeRetain(p); - pAcc->LoadAllData(true); - if (pFile->AppendBlock(pAcc->GetData(), pAcc->GetSize()) < 0) { - return -1; + if (p->GetRawSize() > 0) { + auto pAcc = pdfium::MakeRetain(p); + pAcc->LoadAllData(true); + + if (pFile->AppendBlock(pAcc->GetData(), pAcc->GetSize()) < 0) { + return -1; + } + offset += pAcc->GetSize(); } - offset += pAcc->GetSize(); if ((len = pFile->AppendString("\r\nendstream")) < 0) { return -1; } diff --git a/core/fpdfapi/parser/cpdf_syntax_parser.cpp b/core/fpdfapi/parser/cpdf_syntax_parser.cpp index ecf2cf6e5b..7b7495dfc6 100644 --- a/core/fpdfapi/parser/cpdf_syntax_parser.cpp +++ b/core/fpdfapi/parser/cpdf_syntax_parser.cpp @@ -724,10 +724,11 @@ std::unique_ptr CPDF_SyntaxParser::ReadStream( } m_Pos = streamStartPos; } - - // Read up to the end of the buffer. + // Read up to the end of the buffer. Note, we allow zero length streams as + // we need to pass them through when we are importing pages into a new + // document. len = std::min(len, m_FileLen - m_Pos - m_HeaderOffset); - if (len <= 0) + if (len < 0) return nullptr; std::unique_ptr pData; @@ -745,7 +746,6 @@ std::unique_ptr CPDF_SyntaxParser::ReadStream( pData = dest_buf.DetachBuffer(); } } - auto pStream = pdfium::MakeUnique(std::move(pData), len, std::move(pDict)); streamStartPos = m_Pos; diff --git a/fpdfsdk/fpdfppo_embeddertest.cpp b/fpdfsdk/fpdfppo_embeddertest.cpp index db39700ca1..7e6ff33fc3 100644 --- a/fpdfsdk/fpdfppo_embeddertest.cpp +++ b/fpdfsdk/fpdfppo_embeddertest.cpp @@ -1,11 +1,11 @@ // Copyright 2016 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_ppo.h" +#include #include "core/fxcrt/fx_basic.h" #include "public/fpdf_edit.h" +#include "public/fpdf_ppo.h" #include "public/fpdfview.h" #include "testing/embedder_test.h" #include "testing/gtest/include/gtest/gtest.h" @@ -131,7 +131,7 @@ TEST_F(FPDFPPOEmbeddertest, BUG_664284) { EXPECT_TRUE(OpenDocument("bug_664284.pdf")); FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); + ASSERT_NE(nullptr, page); FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); EXPECT_TRUE(output_doc); @@ -140,3 +140,37 @@ TEST_F(FPDFPPOEmbeddertest, BUG_664284) { UnloadPage(page); } + +TEST_F(FPDFPPOEmbeddertest, ImportWithZeroLengthStream) { + EXPECT_TRUE(OpenDocument("zero_length_stream.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_NE(nullptr, page); + + FPDF_BITMAP bitmap = RenderPage(page); + ASSERT_EQ(200, FPDFBitmap_GetWidth(bitmap)); + ASSERT_EQ(200, FPDFBitmap_GetHeight(bitmap)); + ASSERT_EQ(800, FPDFBitmap_GetStride(bitmap)); + + std::string digest = HashBitmap(bitmap, 200, 200); + FPDFBitmap_Destroy(bitmap); + FPDF_ClosePage(page); + + FPDF_DOCUMENT new_doc = FPDF_CreateNewDocument(); + EXPECT_TRUE(new_doc); + EXPECT_TRUE(FPDF_ImportPages(new_doc, document(), "1", 0)); + + EXPECT_EQ(1, FPDF_GetPageCount(new_doc)); + FPDF_PAGE new_page = FPDF_LoadPage(new_doc, 0); + ASSERT_NE(nullptr, new_page); + FPDF_BITMAP new_bitmap = RenderPage(new_page); + ASSERT_EQ(200, FPDFBitmap_GetWidth(new_bitmap)); + ASSERT_EQ(200, FPDFBitmap_GetHeight(new_bitmap)); + ASSERT_EQ(800, FPDFBitmap_GetStride(new_bitmap)); + + std::string new_digest = HashBitmap(new_bitmap, 200, 200); + FPDFBitmap_Destroy(new_bitmap); + FPDF_ClosePage(new_page); + FPDF_CloseDocument(new_doc); + + EXPECT_EQ(digest, new_digest); +} diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp index 79074153fb..f2a27009e1 100644 --- a/testing/embedder_test.cpp +++ b/testing/embedder_test.cpp @@ -336,6 +336,15 @@ FPDF_PAGE EmbedderTest::GetPageTrampoline(FPDF_FORMFILLINFO* info, page_index); } +std::string EmbedderTest::HashBitmap(FPDF_BITMAP bitmap, + int expected_width, + int expected_height) { + uint8_t digest[16]; + CRYPT_MD5Generate(static_cast(FPDFBitmap_GetBuffer(bitmap)), + expected_width * 4 * expected_height, digest); + return CRYPT_ToBase16(digest); +} + // static void EmbedderTest::CompareBitmap(FPDF_BITMAP bitmap, int expected_width, @@ -349,10 +358,8 @@ void EmbedderTest::CompareBitmap(FPDF_BITMAP bitmap, if (!expected_md5sum) return; - uint8_t digest[16]; - CRYPT_MD5Generate(static_cast(FPDFBitmap_GetBuffer(bitmap)), - expected_stride * expected_height, digest); - EXPECT_EQ(expected_md5sum, CRYPT_ToBase16(digest)); + EXPECT_EQ(expected_md5sum, + HashBitmap(bitmap, expected_width, expected_height)); } // Can't use gtest-provided main since we need to stash the path to the diff --git a/testing/embedder_test.h b/testing/embedder_test.h index 619fc5d699..2bb796d905 100644 --- a/testing/embedder_test.h +++ b/testing/embedder_test.h @@ -108,6 +108,11 @@ class EmbedderTest : public ::testing::Test, protected: void SetupFormFillEnvironment(); + // Return the hash of |bitmap|. + static std::string HashBitmap(FPDF_BITMAP bitmap, + int expected_width, + int expected_height); + // Check |bitmap| to make sure it has the right dimensions and content. static void CompareBitmap(FPDF_BITMAP bitmap, int expected_width, diff --git a/testing/resources/zero_length_stream.in b/testing/resources/zero_length_stream.in new file mode 100644 index 0000000000..5b258d4d74 --- /dev/null +++ b/testing/resources/zero_length_stream.in @@ -0,0 +1,63 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +{{object 2 0}} << + /Type /Pages + /MediaBox [ 0 0 200 200 ] + /Count 1 + /Kids [ 3 0 R ] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Resources << + /Font << + /F1 4 0 R + /F2 5 0 R + >> + >> + /Contents [6 0 R 7 0 R] +>> +endobj +{{object 4 0}} << + /Type /Font + /Subtype /Type1 + /BaseFont /Times-Roman +>> +endobj +{{object 5 0}} << + /Type /Font + /Subtype /Type1 + /BaseFont /Helvetica +>> +endobj +{{object 6 0}} << + /Filter /FlateDecode + /Length 0 +>> +stream +endstream +endobj +{{object 7 0}} << +>> +stream +BT +20 50 Td +/F1 12 Tf +(Hello, world!) Tj +0 50 Td +/F2 16 Tf +(Goodbye, world!) Tj +ET +endstream +endobj +{{xref}} +trailer << + /Size 6 + /Root 1 0 R +>> +{{startxref}} +%%EOF diff --git a/testing/resources/zero_length_stream.pdf b/testing/resources/zero_length_stream.pdf new file mode 100644 index 0000000000..90fae5be7f --- /dev/null +++ b/testing/resources/zero_length_stream.pdf @@ -0,0 +1,74 @@ +%PDF-1.7 +% ò¤ô +1 0 obj << + /Type /Catalog + /Pages 2 0 R +>> +2 0 obj << + /Type /Pages + /MediaBox [ 0 0 200 200 ] + /Count 1 + /Kids [ 3 0 R ] +>> +endobj +3 0 obj << + /Type /Page + /Parent 2 0 R + /Resources << + /Font << + /F1 4 0 R + /F2 5 0 R + >> + >> + /Contents [6 0 R 7 0 R] +>> +endobj +4 0 obj << + /Type /Font + /Subtype /Type1 + /BaseFont /Times-Roman +>> +endobj +5 0 obj << + /Type /Font + /Subtype /Type1 + /BaseFont /Helvetica +>> +endobj +6 0 obj << + /Filter /FlateDecode + /Length 0 +>> +stream +endstream +endobj +7 0 obj << +>> +stream +BT +20 50 Td +/F1 12 Tf +(Hello, world!) Tj +0 50 Td +/F2 16 Tf +(Goodbye, world!) Tj +ET +endstream +endobj +xref +0 8 +0000000000 65535 f +0000000015 00000 n +0000000061 00000 n +0000000154 00000 n +0000000304 00000 n +0000000382 00000 n +0000000458 00000 n +0000000531 00000 n +trailer << + /Size 6 + /Root 1 0 R +>> +startxref +652 +%%EOF -- cgit v1.2.3