This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new 02f40d5d00 TIKA-5033 follow-on: PSD thumbnail tests check decoded
dimensions and malformed resources (#3548)
02f40d5d00 is described below
commit 02f40d5d00d03eec08529a3df198cfbba09d2084
Author: Tim Allison <[email protected]>
AuthorDate: Thu Nov 5 13:37:26 2026 -0500
TIKA-5033 follow-on: PSD thumbnail tests check decoded dimensions and
malformed resources (#3548)
---
.../apache/tika/parser/image/PSDParserTest.java | 115 +++++++++++++++++++--
1 file changed, 107 insertions(+), 8 deletions(-)
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/PSDParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/PSDParserTest.java
index a3d88ac086..a61cdac661 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/PSDParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/PSDParserTest.java
@@ -20,9 +20,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.List;
+import javax.imageio.ImageIO;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.xml.sax.helpers.DefaultHandler;
import org.apache.tika.TikaTest;
@@ -94,18 +102,109 @@ public class PSDParserTest extends TikaTest {
});
}
+ @ParameterizedTest
+ @ValueSource(strings = {"testPSD2.psd:69", "testPSD_xmp.psd:160"})
+ public void testThumbnail(String fileAndWidth) throws Exception {
+ String[] parts = fileAndWidth.split(":");
+ List<Metadata> metadataList = getRecursiveMetadata(parts[0]);
+ assertEquals(2, metadataList.size());
+ Metadata thumbnail = metadataList.get(1);
+
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
+ thumbnail.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
+ assertEquals("image/jpeg", thumbnail.get(HttpHeaders.CONTENT_TYPE));
+ //only a correctly sliced JPEG decodes to the thumbnail's dimensions
+ assertEquals(parts[1], thumbnail.get(TIFF.IMAGE_WIDTH));
+ }
+
@Test
- public void testThumbnail() throws Exception {
- for (String file : new String[]{"testPSD2.psd", "testPSD_xmp.psd"}) {
- List<Metadata> metadataList = getRecursiveMetadata(file);
- assertEquals(2, metadataList.size(), file);
- Metadata thumbnail = metadataList.get(1);
-
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
- thumbnail.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE),
file);
- assertEquals("image/jpeg",
thumbnail.get(HttpHeaders.CONTENT_TYPE), file);
+ public void testSyntheticThumbnail() throws Exception {
+ byte[] jpeg = jpeg(8, 4);
+ List<Metadata> metadataList = parseSynthetic(thumbnailResource(1,
jpeg.length, jpeg));
+ assertEquals(2, metadataList.size());
+ assertEquals("8", metadataList.get(1).get(TIFF.IMAGE_WIDTH));
+ assertEquals("4", metadataList.get(1).get(TIFF.IMAGE_LENGTH));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"notJpegFormat", "sizePastResource", "sizeHuge",
"sizeOne", "noSoi"})
+ public void testMalformedThumbnailIsIgnored(String variant) throws
Exception {
+ byte[] jpeg = jpeg(8, 4);
+ byte[] resource;
+ switch (variant) {
+ case "notJpegFormat":
+ resource = thumbnailResource(0, jpeg.length, jpeg);
+ break;
+ case "sizePastResource":
+ //+2: an odd resource is padded by one byte that the size may
cover
+ resource = thumbnailResource(1, jpeg.length + 2L, jpeg);
+ break;
+ case "sizeHuge":
+ resource = thumbnailResource(1, 0xFFFFFFF0L, jpeg);
+ break;
+ case "sizeOne":
+ resource = thumbnailResource(1, 1, jpeg);
+ break;
+ default:
+ jpeg[1] = 0;
+ resource = thumbnailResource(1, jpeg.length, jpeg);
+ break;
+ }
+ List<Metadata> metadataList = parseSynthetic(resource);
+ assertEquals(1, metadataList.size(), variant);
+ assertEquals("1", metadataList.get(0).get(TIFF.IMAGE_WIDTH), variant);
+ }
+
+ private List<Metadata> parseSynthetic(byte[] resourceData) throws
Exception {
+ try (TikaInputStream tis =
TikaInputStream.get(psdWithThumbnail(resourceData))) {
+ return getRecursiveMetadata(tis, new Metadata(), new
ParseContext(), false);
}
}
+ private static byte[] jpeg(int width, int height) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ImageIO.write(new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB), "jpg", bos);
+ return bos.toByteArray();
+ }
+
+ private static byte[] thumbnailResource(int format, long compressedSize,
byte[] jpeg)
+ throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(bos);
+ out.writeInt(format);
+ out.writeInt(8); //width
+ out.writeInt(4); //height
+ out.writeInt(24); //widthbytes
+ out.writeInt(96); //total size
+ out.writeInt((int) compressedSize);
+ out.writeShort(24); //bits per pixel
+ out.writeShort(1); //planes
+ out.write(jpeg);
+ return bos.toByteArray();
+ }
+
+ private static byte[] psdWithThumbnail(byte[] resourceData) throws
IOException {
+ int padded = resourceData.length + (resourceData.length % 2);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(bos);
+ out.write("8BPS".getBytes(StandardCharsets.US_ASCII));
+ out.writeShort(1); //version
+ out.write(new byte[6]); //reserved
+ out.writeShort(3); //channels
+ out.writeInt(1); //height
+ out.writeInt(1); //width
+ out.writeShort(8); //depth
+ out.writeShort(3); //RGB
+ out.writeInt(0); //color mode section
+ out.writeInt(4 + 2 + 2 + 4 + padded); //image resources section
+ out.write("8BIM".getBytes(StandardCharsets.US_ASCII));
+ out.writeShort(0x040C);
+ out.writeShort(0); //empty name, padded to even
+ out.writeInt(resourceData.length);
+ out.write(resourceData);
+ out.write(new byte[padded - resourceData.length]);
+ return bos.toByteArray();
+ }
+
@Test
public void testNoThumbnail() throws Exception {
assertEquals(1, getRecursiveMetadata("testPSD.psd").size());