Push/pop the graphics state stack when flattening.

When FPDFPage_Flatten() manipulates a content stream, it replaces the
content stream with a content stream array. The first element in the
array is the original content stream and the second element in the array
is the flattened annotations content. To make sure the original content
stream's graphics state stack does not affect the flattened annotations,
FPDFPage_Flatten() rewrites the original content stream to be:

q
$contents
Q

When FPDFPage_Flatten() manipulates a content stream array, it just
appends the flattened annotations as a new element in the array. This
may result in graphics state stack leaking out.

To fix this, wrap the content stream array to be:

["q", $content1, ..., $contentN, "Q"]

And then append the flattened annotations.

BUG=chromium:896366

Change-Id: Ic6499e39eb4c9f1fe45d037622bf02be724b6cae
Reviewed-on: https://pdfium-review.googlesource.com/c/44252
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/fpdfsdk/fpdf_flatten.cpp b/fpdfsdk/fpdf_flatten.cpp
index 98529c5..0cee611 100644
--- a/fpdfsdk/fpdf_flatten.cpp
+++ b/fpdfsdk/fpdf_flatten.cpp
@@ -167,52 +167,61 @@
   return rcRet;
 }
 
-CPDF_Object* NewIndirectContentsStream(const ByteString& key,
-                                       CPDF_Document* pDocument) {
+ByteString GenerateFlattenedContent(const ByteString& key) {
+  return "q 1 0 0 1 0 0 cm /" + key + " Do Q";
+}
+
+CPDF_Object* NewIndirectContentsStream(CPDF_Document* pDocument,
+                                       const ByteString& contents) {
   CPDF_Stream* pNewContents = pDocument->NewIndirect<CPDF_Stream>(
       nullptr, 0,
       pdfium::MakeUnique<CPDF_Dictionary>(pDocument->GetByteStringPool()));
-  ByteString sStream =
-      ByteString::Format("q 1 0 0 1 0 0 cm /%s Do Q", key.c_str());
-  pNewContents->SetData(sStream.AsRawSpan());
+  pNewContents->SetData(contents.AsRawSpan());
   return pNewContents;
 }
 
 void SetPageContents(const ByteString& key,
                      CPDF_Dictionary* pPage,
                      CPDF_Document* pDocument) {
-  CPDF_Array* pContentsArray = nullptr;
+  CPDF_Array* pContentsArray =
+      pPage->GetArrayFor(pdfium::page_object::kContents);
   CPDF_Stream* pContentsStream =
       pPage->GetStreamFor(pdfium::page_object::kContents);
-  if (!pContentsStream) {
-    pContentsArray = pPage->GetArrayFor(pdfium::page_object::kContents);
-    if (!pContentsArray) {
-      if (!key.IsEmpty()) {
-        pPage->SetFor(pdfium::page_object::kContents,
-                      NewIndirectContentsStream(key, pDocument)
-                          ->MakeReference(pDocument));
-      }
-      return;
+  if (!pContentsStream && !pContentsArray) {
+    if (!key.IsEmpty()) {
+      pPage->SetFor(
+          pdfium::page_object::kContents,
+          NewIndirectContentsStream(pDocument, GenerateFlattenedContent(key))
+              ->MakeReference(pDocument));
     }
+    return;
   }
+
   pPage->ConvertToIndirectObjectFor(pdfium::page_object::kContents, pDocument);
-  if (!pContentsArray) {
-    pContentsArray = pDocument->NewIndirect<CPDF_Array>();
+  if (pContentsArray) {
+    pContentsArray->InsertAt(
+        0, NewIndirectContentsStream(pDocument, "q")->MakeReference(pDocument));
+    pContentsArray->Add(
+        NewIndirectContentsStream(pDocument, "Q")->MakeReference(pDocument));
+  } else {
     ByteString sStream = "q\n";
     {
       auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(pContentsStream);
       pAcc->LoadAllDataFiltered();
-      sStream += ByteString(pAcc->GetData(), pAcc->GetSize());
+      sStream += ByteString(pAcc->GetSpan());
       sStream += "\nQ";
     }
     pContentsStream->SetDataAndRemoveFilter(sStream.AsRawSpan());
+
+    pContentsArray = pDocument->NewIndirect<CPDF_Array>();
     pContentsArray->Add(pContentsStream->MakeReference(pDocument));
     pPage->SetFor(pdfium::page_object::kContents,
                   pContentsArray->MakeReference(pDocument));
   }
   if (!key.IsEmpty()) {
     pContentsArray->Add(
-        NewIndirectContentsStream(key, pDocument)->MakeReference(pDocument));
+        NewIndirectContentsStream(pDocument, GenerateFlattenedContent(key))
+            ->MakeReference(pDocument));
   }
 }
 
diff --git a/fpdfsdk/fpdf_flatten_embeddertest.cpp b/fpdfsdk/fpdf_flatten_embeddertest.cpp
index c861d75..450c30c 100644
--- a/fpdfsdk/fpdf_flatten_embeddertest.cpp
+++ b/fpdfsdk/fpdf_flatten_embeddertest.cpp
@@ -54,3 +54,20 @@
 
   VerifySavedDocument(200, 200, md5_hash);
 }
+
+TEST_F(FPDFFlattenEmbeddertest, BUG_896366) {
+  static const char md5_hash[] = "f71ab085c52c8445ae785eca3ec858b1";
+  EXPECT_TRUE(OpenDocument("bug_896366.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  ScopedFPDFBitmap bitmap = RenderLoadedPageWithFlags(page, FPDF_ANNOT);
+  CompareBitmap(bitmap.get(), 612, 792, md5_hash);
+
+  EXPECT_EQ(FLATTEN_SUCCESS, FPDFPage_Flatten(page, FLAT_PRINT));
+  EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
+
+  UnloadPage(page);
+
+  VerifySavedDocument(612, 792, md5_hash);
+}
diff --git a/testing/resources/bug_890322.in b/testing/resources/bug_890322.in
index ea55ac6..0c68775 100644
--- a/testing/resources/bug_890322.in
+++ b/testing/resources/bug_890322.in
@@ -42,6 +42,7 @@
 0 0 50 40 re B*
 Q
 endstream
+endobj
 {{object 6 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/bug_890322.pdf b/testing/resources/bug_890322.pdf
index 2183aaf..2900c1b 100644
--- a/testing/resources/bug_890322.pdf
+++ b/testing/resources/bug_890322.pdf
@@ -43,6 +43,7 @@
 0 0 50 40 re B*
 Q
 endstream
+endobj
 6 0 obj <<
   /Length 4
 >>
@@ -59,11 +60,11 @@
 0000000165 00000 n 
 0000000282 00000 n 
 0000000474 00000 n 
-0000000587 00000 n 
+0000000594 00000 n 
 trailer <<
   /Root 1 0 R
   /Size 7
 >>
 startxref
-641
+648
 %%EOF
diff --git a/testing/resources/bug_896366.in b/testing/resources/bug_896366.in
new file mode 100644
index 0000000..86bd6fe
--- /dev/null
+++ b/testing/resources/bug_896366.in
@@ -0,0 +1,60 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [4 0 R]
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Kids [3 0 R]
+  /Count 1
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [4 0 R]
+  /Contents [6 0 R]
+  /MediaBox [0 0 612 792]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /F 4
+  /FT /Btn
+  /Rect [ 75 80 125 120 ]
+  /T (PushButton)
+  /Ff 65536
+  /H
+  /P
+  /AP << /N 5 0 R >>
+  /MK << /BG [ 1.0 0.0 0.0 ] >>
+>>
+endobj
+{{object 5 0}} <<
+  /Subtype /Form
+  /BBox [ 0 40 50 00 ]
+  {{streamlen}}
+>>
+stream
+q
+0 0 1 rg
+0 0 50 40 re B*
+Q
+endstream
+endobj
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+1 0 0 -1 0 792 cm
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_896366.pdf b/testing/resources/bug_896366.pdf
new file mode 100644
index 0000000..0310fb9
--- /dev/null
+++ b/testing/resources/bug_896366.pdf
@@ -0,0 +1,73 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [4 0 R]
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Kids [3 0 R]
+  /Count 1
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [4 0 R]
+  /Contents [6 0 R]
+  /MediaBox [0 0 612 792]
+>>
+endobj
+4 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /F 4
+  /FT /Btn
+  /Rect [ 75 80 125 120 ]
+  /T (PushButton)
+  /Ff 65536
+  /H
+  /P
+  /AP << /N 5 0 R >>
+  /MK << /BG [ 1.0 0.0 0.0 ] >>
+>>
+endobj
+5 0 obj <<
+  /Subtype /Form
+  /BBox [ 0 40 50 00 ]
+  /Length 29
+>>
+stream
+q
+0 0 1 rg
+0 0 50 40 re B*
+Q
+endstream
+endobj
+6 0 obj <<
+  /Length 18
+>>
+stream
+1 0 0 -1 0 792 cm
+endstream
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000108 00000 n 
+0000000171 00000 n 
+0000000286 00000 n 
+0000000478 00000 n 
+0000000598 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
+startxref
+667
+%%EOF