You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@xmlgraphics.apache.org by jg...@apache.org on 2025/09/05 00:20:09 UTC
(xmlgraphics-fop-pdf-images) branch main updated: FOP-3267 Check COSArray content for possible filters
This is an automated email from the ASF dual-hosted git repository.
jgoncalves pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/xmlgraphics-fop-pdf-images.git
The following commit(s) were added to refs/heads/main by this push:
new 63ae56a FOP-3267 Check COSArray content for possible filters
63ae56a is described below
commit 63ae56a12eb864534f89bbb1745e3d24ed30a0ea
Author: Joao Goncalves <jg...@smartcommunications.com>
AuthorDate: Fri Aug 29 12:59:09 2025 -0500
FOP-3267 Check COSArray content for possible filters
---
.../apache/fop/render/pdf/pdfbox/PDFCloner.java | 41 +++++++++-
.../render/pdf/pdfbox/PDFBoxAdapterTestCase.java | 88 +++++++++++++--------
.../org/apache/fop/render/pdf/pdfbox/dctArray.pdf | Bin 0 -> 77439 bytes
.../org/apache/fop/render/pdf/pdfbox/dctArray2.pdf | Bin 0 -> 92861 bytes
4 files changed, 97 insertions(+), 32 deletions(-)
diff --git a/src/java/org/apache/fop/render/pdf/pdfbox/PDFCloner.java b/src/java/org/apache/fop/render/pdf/pdfbox/PDFCloner.java
index 858055a..0a5d01b 100644
--- a/src/java/org/apache/fop/render/pdf/pdfbox/PDFCloner.java
+++ b/src/java/org/apache/fop/render/pdf/pdfbox/PDFCloner.java
@@ -23,6 +23,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -39,10 +40,13 @@ import org.apache.pdfbox.cos.COSNull;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.filter.Filter;
+import org.apache.pdfbox.filter.FilterFactory;
import org.apache.pdfbox.pdmodel.common.COSObjectable;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.fop.pdf.DCTFilter;
+import org.apache.fop.pdf.FlateFilter;
import org.apache.fop.pdf.PDFArray;
import org.apache.fop.pdf.PDFDictionary;
import org.apache.fop.pdf.PDFDocument;
@@ -178,8 +182,23 @@ public class PDFCloner {
InputStream in;
Set filter;
PDFStream stream = new PDFStream();
- if (adapter.pdfDoc.isEncryptionActive() && originalStream.getItem(COSName.FILTER) == COSName.DCT_DECODE) {
+ if (adapter.pdfDoc.isEncryptionActive() && isDctDecodeFilter(originalStream.getItem(COSName.FILTER))) {
stream.getFilterList().addFilter(new DCTFilter());
+ in = originalStream.createRawInputStream();
+ filter = Collections.EMPTY_SET;
+ } else if (adapter.pdfDoc.isEncryptionActive()
+ && isDctAndFlateDecodeFilter(originalStream.getItem(COSName.FILTER))) {
+ in = originalStream.createRawInputStream();
+ OutputStream out = originalStream.createRawOutputStream();
+
+ Filter flateFilter = FilterFactory.INSTANCE.getFilter(COSName.FLATE_DECODE);
+ flateFilter.decode(in, out, originalStream, 0);
+ IOUtils.closeQuietly(in);
+ IOUtils.closeQuietly(out);
+
+ stream.getFilterList().addFilter(new DCTFilter());
+ stream.getFilterList().addFilter(new FlateFilter());
+
in = originalStream.createRawInputStream();
filter = Collections.EMPTY_SET;
} else if (adapter.pdfDoc.isEncryptionActive()
@@ -219,6 +238,26 @@ public class PDFCloner {
return cacheClonedObject(keyBase, stream);
}
+ private boolean isDctDecodeFilter(COSBase filterItem) {
+ if (filterItem instanceof COSArray) {
+ COSArray arrayFilters = (COSArray) filterItem;
+ List<?> list = arrayFilters.toList();
+ return list.size() == 1 && list.contains(COSName.DCT_DECODE);
+ }
+
+ return filterItem == COSName.DCT_DECODE;
+ }
+
+ private boolean isDctAndFlateDecodeFilter(COSBase filterItem) {
+ if (filterItem instanceof COSArray) {
+ COSArray arrayFilters = (COSArray) filterItem;
+ List<?> list = arrayFilters.toList();
+ return list.size() == 2 && list.contains(COSName.DCT_DECODE) && list.contains(COSName.FLATE_DECODE);
+ }
+
+ return false;
+ }
+
protected Object cacheClonedObject(Object base, Object cloned) throws IOException {
Object key = PDFBoxAdapterUtil.getBaseKey(base);
if (key == null) {
diff --git a/test/java/org/apache/fop/render/pdf/pdfbox/PDFBoxAdapterTestCase.java b/test/java/org/apache/fop/render/pdf/pdfbox/PDFBoxAdapterTestCase.java
index 3aa729d..82afb7d 100644
--- a/test/java/org/apache/fop/render/pdf/pdfbox/PDFBoxAdapterTestCase.java
+++ b/test/java/org/apache/fop/render/pdf/pdfbox/PDFBoxAdapterTestCase.java
@@ -39,6 +39,7 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -102,6 +103,8 @@ public class PDFBoxAdapterTestCase {
protected static final String PATTERN = "pattern.pdf";
protected static final String FORMROTATED = "formrotated.pdf";
protected static final String SOFTMASK = "softmask.pdf";
+ protected static final String DCT_ARRAY = "dctArray.pdf";
+ protected static final String DCT_ARRAY2 = "dctArray2.pdf";
protected static PDFPage getPDFPage(PDFDocument doc) {
final Rectangle2D r = new Rectangle2D.Double();
@@ -139,7 +142,7 @@ public class PDFBoxAdapterTestCase {
AffineTransform pageAdjust = new AffineTransform();
Rectangle r = new Rectangle(0, 1650, 842000, 595000);
String stream = (String) adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
- Assert.assertTrue(stream, stream.contains("/P <</MCID 5 >>BDC"));
+ assertTrue(stream, stream.contains("/P <</MCID 5 >>BDC"));
doc.close();
}
@@ -160,7 +163,7 @@ public class PDFBoxAdapterTestCase {
os.reset();
adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
pdfdoc.outputTrailer(os);
- Assert.assertTrue(os.toString(StandardCharsets.UTF_8.name()).contains("/Fields ["));
+ assertTrue(os.toString(StandardCharsets.UTF_8.name()).contains("/Fields ["));
doc.close();
}
@@ -196,8 +199,8 @@ public class PDFBoxAdapterTestCase {
ByteArrayOutputStream os = new ByteArrayOutputStream();
pdfdoc.output(os);
String out = os.toString(StandardCharsets.UTF_8.name());
- Assert.assertTrue(out.contains("/Parent "));
- Assert.assertTrue(out.contains("/Kids "));
+ assertTrue(out.contains("/Parent "));
+ assertTrue(out.contains("/Kids "));
doc.close();
}
@@ -219,7 +222,7 @@ public class PDFBoxAdapterTestCase {
adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
ByteArrayOutputStream os = new ByteArrayOutputStream();
pdfdoc.outputTrailer(os);
- Assert.assertTrue(os.toString(StandardCharsets.UTF_8.name()).contains("/Fields []"));
+ assertTrue(os.toString(StandardCharsets.UTF_8.name()).contains("/Fields []"));
doc.close();
}
@@ -239,7 +242,7 @@ public class PDFBoxAdapterTestCase {
adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
ByteArrayOutputStream os = new ByteArrayOutputStream();
pdfdoc.outputTrailer(os);
- Assert.assertTrue(os.toString(StandardCharsets.UTF_8.name()).contains("/Fields []"));
+ assertTrue(os.toString(StandardCharsets.UTF_8.name()).contains("/Fields []"));
doc.close();
}
@@ -257,7 +260,7 @@ public class PDFBoxAdapterTestCase {
AffineTransform pageAdjust = new AffineTransform();
Rectangle r = new Rectangle(0, 1650, 842000, 595000);
String stream = (String) adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
- Assert.assertTrue(stream.contains("/Link <</MCID 5 >>BDC"));
+ assertTrue(stream.contains("/Link <</MCID 5 >>BDC"));
assertEquals(pageNumbers.size(), 4);
PDFAnnotList annots = (PDFAnnotList) pdfpage.get("Annots");
assertEquals(annots.toPDFString(), "[\n1 0 R\n2 0 R\n]");
@@ -289,7 +292,7 @@ public class PDFBoxAdapterTestCase {
pdfToPS(LINK);
pdfToPS(LOOP);
stream = pdfToPS(LIBREOFFICE);
- Assert.assertTrue(stream.toString(StandardCharsets.UTF_8.name()).contains("/MaskColor [ 255 255 255 ]"));
+ assertTrue(stream.toString(StandardCharsets.UTF_8.name()).contains("/MaskColor [ 255 255 255 ]"));
}
private int countString(String s, String value) {
@@ -321,15 +324,15 @@ public class PDFBoxAdapterTestCase {
@Test
public void testSmask() throws IOException, ImageException {
ByteArrayOutputStream ps = pdfToPS(SMASK);
- Assert.assertTrue(ps.toString(StandardCharsets.UTF_8.name()).contains("/Pattern"));
- Assert.assertTrue(ps.toString(StandardCharsets.UTF_8.name()).contains("{<\nf1f1f1"));
+ assertTrue(ps.toString(StandardCharsets.UTF_8.name()).contains("/Pattern"));
+ assertTrue(ps.toString(StandardCharsets.UTF_8.name()).contains("{<\nf1f1f1"));
}
@Test
public void testPCL() {
UnsupportedOperationException ex = Assert.assertThrows(UnsupportedOperationException.class, () ->
pdfToPCL(SHADING));
- Assert.assertTrue(ex.getMessage().contains("Clipping is not supported."));
+ assertTrue(ex.getMessage().contains("Clipping is not supported."));
}
private void pdfToPCL(String pdf) throws IOException, ImageException {
@@ -423,7 +426,7 @@ public class PDFBoxAdapterTestCase {
PDFResources res = c.getPage().getPDFResources();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
res.output(bos);
- Assert.assertTrue(bos.toString(StandardCharsets.UTF_8.name()).contains("/ExtGState << /GS1"));
+ assertTrue(bos.toString(StandardCharsets.UTF_8.name()).contains("/ExtGState << /GS1"));
}
@Test
@@ -451,8 +454,8 @@ public class PDFBoxAdapterTestCase {
LoadPDFWithCache loadPDFWithCache = new LoadPDFWithCache();
String stream = loadPDFWithCache.run(LOOP);
String cachedStream = (String) loadPDFWithCache.objectCachePerFile.get(LOOP);
- Assert.assertTrue(cachedStream.contains("EMC"));
- Assert.assertTrue(stream.endsWith(cachedStream));
+ assertTrue(cachedStream.contains("EMC"));
+ assertTrue(stream.endsWith(cachedStream));
}
private static class LoadPDFWithCache {
@@ -482,7 +485,7 @@ public class PDFBoxAdapterTestCase {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
loadPDFWithCache.pdfdoc.output(bos);
assertEquals(loadPDFWithCache.pdfCache.size(), 2);
- Assert.assertTrue(bos.size() <= 6418);
+ assertTrue(bos.size() <= 6418);
}
@Test
@@ -492,7 +495,7 @@ public class PDFBoxAdapterTestCase {
page.setContents(new PDStream(doc, new ByteArrayInputStream("<".getBytes(StandardCharsets.UTF_8))));
doc.addPage(page);
RuntimeException ex = Assert.assertThrows(RuntimeException.class, () -> pdfToPCL(doc, ERROR));
- Assert.assertTrue(ex.getMessage().startsWith("Error while painting PDF page: " + ERROR));
+ assertTrue(ex.getMessage().startsWith("Error while painting PDF page: " + ERROR));
}
@Test
@@ -503,7 +506,7 @@ public class PDFBoxAdapterTestCase {
ImagePDF imagePDF = new ImagePDF(new ImageInfo(ERROR, null), null);
RuntimeException ex = Assert.assertThrows(RuntimeException.class, () ->
new PDFBoxImageHandler().handleImage(context, imagePDF, null));
- Assert.assertTrue(ex.getMessage().startsWith("Error on PDF page: " + ERROR));
+ assertTrue(ex.getMessage().startsWith("Error on PDF page: " + ERROR));
}
@Test
@@ -544,12 +547,12 @@ public class PDFBoxAdapterTestCase {
doc.close();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
formXObject.output(bos);
- Assert.assertTrue(bos.toString(StandardCharsets.UTF_8.name()).contains("/Type /XObject"));
+ assertTrue(bos.toString(StandardCharsets.UTF_8.name()).contains("/Type /XObject"));
}
@Test
public void testRewriteOfForms() throws IOException {
- Assert.assertTrue(getPDFToPDF(ACCESSIBLERADIOBUTTONS).contains("/F15106079 12 Tf"));
+ assertTrue(getPDFToPDF(ACCESSIBLERADIOBUTTONS).contains("/F15106079 12 Tf"));
}
private String getPDFToPDF(String pdf) throws IOException {
@@ -577,17 +580,40 @@ public class PDFBoxAdapterTestCase {
@Test
public void testRewriteOfPatternForms() throws IOException {
- Assert.assertTrue(getPDFToPDF(PATTERN).contains("/R1106079 gs\n1 1 m"));
+ assertTrue(getPDFToPDF(PATTERN).contains("/R1106079 gs\n1 1 m"));
}
@Test
public void testDCTEncryption() throws IOException {
+ defaultTestDCTEncryption(IMAGE, "/Filter /DCTDecode");
+ }
+
+ @Test
+ public void testDCTEncryptionFilterInsideArray() throws IOException {
+ //dct filters can be inside a COSArray (/Filter [/DCTDecode])
+ int outputSize = defaultTestDCTEncryption(DCT_ARRAY, "/Filter /DCTDecode");
+ assertTrue("Filter inside a COSArray cannot cause output file size to ballon", outputSize < 100000);
+ }
+
+ @Test
+ public void testDCTEncryptionMultipleFiltersInsideArray() throws IOException {
+ //multiple filters can be inside a COSArray (/Filter [/FlateDecode /DCTDecode])
+ int outputSize = defaultTestDCTEncryption(DCT_ARRAY2, "/Filter [/FlateDecode /DCTDecode]");
+ assertTrue("Filter inside a COSArray cannot cause output file size to ballon", outputSize < 100000);
+ }
+
+ private int defaultTestDCTEncryption(String filename, String expected) throws IOException {
PDFDocument pdfdoc = new PDFDocument("");
pdfdoc.setEncryption(new PDFEncryptionParams());
- loadPage(pdfdoc, IMAGE);
+ loadPage(pdfdoc, filename);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
pdfdoc.output(bos);
- Assert.assertTrue(bos.toString(StandardCharsets.UTF_8.name()).contains("/Filter /DCTDecode"));
+
+ String output = bos.toString(StandardCharsets.UTF_8.name());
+ assertTrue("Output must have the DCTDecode filter as the input uses this filter",
+ output.contains(expected));
+
+ return bos.size();
}
@Test
@@ -633,8 +659,8 @@ public class PDFBoxAdapterTestCase {
String id2 = "33 0 R";
assertEquals(getAnnotationsID(page2), id2);
String outStr = os.toString(StandardCharsets.UTF_8.name()).replaceAll("\\s\\s/", "/");
- Assert.assertTrue(outStr.contains("<< /Kids [32 0 R] /T ([Signer1) >>"));
- Assert.assertTrue(outStr.contains("<<\n"
+ assertTrue(outStr.contains("<< /Kids [32 0 R] /T ([Signer1) >>"));
+ assertTrue(outStr.contains("<<\n"
+ "/Kids [" + id1 + " " + id2 + "]\n"
+ "/Parent 34 0 R\n"
+ "/T (Fullname1)\n"
@@ -651,7 +677,7 @@ public class PDFBoxAdapterTestCase {
PDFPage page = loadPage(pdfdoc, doc, new Rectangle());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
page.getPDFResources().output(bos);
- Assert.assertTrue(bos.toString(StandardCharsets.UTF_8.name()).contains("/Properties << /S /S >>"));
+ assertTrue(bos.toString(StandardCharsets.UTF_8.name()).contains("/Properties << /S /S >>"));
}
@Test
@@ -663,7 +689,7 @@ public class PDFBoxAdapterTestCase {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
pdfdoc.output(bos);
String outStr = removeWhiteSpace(bos);
- Assert.assertTrue(outStr.contains("<<\n"
+ assertTrue(outStr.contains("<<\n"
+ "/Type /Pattern\n"
+ "/PatternType 2\n"
+ "/Shading 2 0 R\n"
@@ -682,7 +708,7 @@ public class PDFBoxAdapterTestCase {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
pdfdoc.output(bos);
String outStr = removeWhiteSpace(bos);
- Assert.assertTrue(outStr.contains("<<\n"
+ assertTrue(outStr.contains("<<\n"
+ "/Type /Pattern\n/PatternType 2\n/Shading 2 0 R\n"
+ "/Matrix [53.8858833313 0 0 -26.4968185425 72.7459411621 -120.8601837158]\n"
+ ">>"));
@@ -698,8 +724,8 @@ public class PDFBoxAdapterTestCase {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
pdfdoc.output(bos);
String outStr = removeWhiteSpace(bos);
- Assert.assertTrue(outStr.contains("/Pattern << /Pa1 12 0 R /Pa2 13 0 R >>"));
- Assert.assertTrue(outStr.contains("<<\n"
+ assertTrue(outStr.contains("/Pattern << /Pa1 12 0 R /Pa2 13 0 R >>"));
+ assertTrue(outStr.contains("<<\n"
+ "/Type /Pattern\n"
+ "/PatternType 2\n"
+ "/Shading 2 0 R\n"
@@ -748,7 +774,7 @@ public class PDFBoxAdapterTestCase {
ByteArrayOutputStream bos = pdfToPS(SOFTMASK);
String output = bos.toString(StandardCharsets.UTF_8.name());
assertEquals(output.split("BeginBitmap").length, 3);
- Assert.assertTrue(output.contains("/ImageMatrix [148 0 0 78 0 0]"));
- Assert.assertTrue(output.contains("/ImageMatrix [192 0 0 192 0 0]"));
+ assertTrue(output.contains("/ImageMatrix [148 0 0 78 0 0]"));
+ assertTrue(output.contains("/ImageMatrix [192 0 0 192 0 0]"));
}
}
diff --git a/test/resources/org/apache/fop/render/pdf/pdfbox/dctArray.pdf b/test/resources/org/apache/fop/render/pdf/pdfbox/dctArray.pdf
new file mode 100644
index 0000000..8251226
Binary files /dev/null and b/test/resources/org/apache/fop/render/pdf/pdfbox/dctArray.pdf differ
diff --git a/test/resources/org/apache/fop/render/pdf/pdfbox/dctArray2.pdf b/test/resources/org/apache/fop/render/pdf/pdfbox/dctArray2.pdf
new file mode 100644
index 0000000..921fa1c
Binary files /dev/null and b/test/resources/org/apache/fop/render/pdf/pdfbox/dctArray2.pdf differ
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: commits-help@xmlgraphics.apache.org