Allow zero length streams when parsing.

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 <thestig@chromium.org>
Commit-Queue: dsinclair <dsinclair@chromium.org>
diff --git a/core/fpdfapi/edit/cpdf_creator.cpp b/core/fpdfapi/edit/cpdf_creator.cpp
index 1c0cd69..858d56c 100644
--- a/core/fpdfapi/edit/cpdf_creator.cpp
+++ b/core/fpdfapi/edit/cpdf_creator.cpp
@@ -171,13 +171,21 @@
     encoder.GetDict()->SetNewFor<CPDF_Number>(
         "Length", static_cast<int>(encryptor.GetSize()));
   }
+
   if (!WriteDirectObj(objnum, encoder.GetDict(), true) ||
-      !m_Archive->WriteString("stream\r\n") ||
-      !m_Archive->WriteBlock(encryptor.GetData(), encryptor.GetSize()) ||
-      !m_Archive->WriteString("\r\nendstream")) {
+      !m_Archive->WriteString("stream\r\n")) {
     return false;
   }
 
+  // Allow for empty streams.
+  if (encryptor.GetSize() > 0 &&
+      !m_Archive->WriteBlock(encryptor.GetData(), encryptor.GetSize())) {
+    return false;
+  }
+
+  if (!m_Archive->WriteString("\r\nendstream"))
+    return false;
+
   return true;
 }
 
diff --git a/core/fpdfapi/parser/cpdf_syntax_parser.cpp b/core/fpdfapi/parser/cpdf_syntax_parser.cpp
index fe1a197..9dccbd9 100644
--- a/core/fpdfapi/parser/cpdf_syntax_parser.cpp
+++ b/core/fpdfapi/parser/cpdf_syntax_parser.cpp
@@ -725,10 +725,11 @@
     }
     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<uint8_t, FxFreeDeleter> pData;
@@ -746,7 +747,6 @@
       pData = dest_buf.DetachBuffer();
     }
   }
-
   auto pStream =
       pdfium::MakeUnique<CPDF_Stream>(std::move(pData), len, std::move(pDict));
   streamStartPos = m_Pos;
diff --git a/fpdfsdk/fpdfppo_embeddertest.cpp b/fpdfsdk/fpdfppo_embeddertest.cpp
index db39700..7e6ff33 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 <string>
 
 #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 @@
   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 @@
 
   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 bf00c11..6cea598 100644
--- a/testing/embedder_test.cpp
+++ b/testing/embedder_test.cpp
@@ -326,6 +326,15 @@
                                                               page_index);
 }
 
+std::string EmbedderTest::HashBitmap(FPDF_BITMAP bitmap,
+                                     int expected_width,
+                                     int expected_height) {
+  uint8_t digest[16];
+  CRYPT_MD5Generate(static_cast<uint8_t*>(FPDFBitmap_GetBuffer(bitmap)),
+                    expected_width * 4 * expected_height, digest);
+  return CryptToBase16(digest);
+}
+
 // static
 void EmbedderTest::CompareBitmap(FPDF_BITMAP bitmap,
                                  int expected_width,
@@ -339,10 +348,8 @@
   if (!expected_md5sum)
     return;
 
-  uint8_t digest[16];
-  CRYPT_MD5Generate(static_cast<uint8_t*>(FPDFBitmap_GetBuffer(bitmap)),
-                    expected_stride * expected_height, digest);
-  EXPECT_EQ(expected_md5sum, CryptToBase16(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 619fc5d..2bb796d 100644
--- a/testing/embedder_test.h
+++ b/testing/embedder_test.h
@@ -108,6 +108,11 @@
  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 0000000..5b258d4
--- /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 0000000..90fae5b
--- /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