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());

Reply via email to