Author: tilman
Date: Fri Jul 24 09:22:46 2026
New Revision: 1936539

Log:
PDFBOX-5660: fix readlong(), as suggested by Valery Bokov and Claude Code; 
Sonar fix; closes #488

Modified:
   
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
   
pdfbox/branches/3.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java

Modified: 
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
==============================================================================
--- 
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
 Fri Jul 24 09:18:50 2026        (r1936538)
+++ 
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
 Fri Jul 24 09:22:46 2026        (r1936539)
@@ -303,7 +303,7 @@ public final class CCITTFactory
             {
                 int tag = readshort(endianess, reader);
                 int type = readshort(endianess, reader);
-                int count = readlong(endianess, reader);
+                int count = (int) readlong(endianess, reader);
                 int val;
                 // Note that when the type is shorter than 4 bytes, the rest 
can be garbage
                 // and must be ignored. E.g. short (2 bytes) from "01 00 38 
32" (little endian)
@@ -322,7 +322,7 @@ public final class CCITTFactory
                         reader.read();
                         break;
                     default: // long and other types
-                        val = readlong(endianess, reader);
+                        val = (int) readlong(endianess, reader);
                         break;
                 }
                 switch (tag)
@@ -476,13 +476,14 @@ public final class CCITTFactory
         return (raf.read() << 8) | raf.read();
     }
 
-    private static int readlong(char endianess, RandomAccessRead raf) throws 
IOException
+    static long readlong(char endianess, RandomAccessRead raf) throws 
IOException
     {
+        // TIFF LONG is an unsigned 32-bit value; mask so it widens correctly
         if (endianess == 'I')
         {
-            return raf.read() | (raf.read() << 8) | (raf.read() << 16) | 
(raf.read() << 24);
+            return (raf.read() | (raf.read() << 8) | (raf.read() << 16) | 
(raf.read() << 24)) & 0xFFFFFFFFL;
         }
-        return (raf.read() << 24) | (raf.read() << 16) | (raf.read() << 8) | 
raf.read();
+        return ((raf.read() << 24) | (raf.read() << 16) | (raf.read() << 8) | 
raf.read()) & 0xFFFFFFFFL;
     }
 
     private static final byte[] fliptable = new byte[]

Modified: 
pdfbox/branches/3.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java
==============================================================================
--- 
pdfbox/branches/3.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java
     Fri Jul 24 09:18:50 2026        (r1936538)
+++ 
pdfbox/branches/3.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java
     Fri Jul 24 09:22:46 2026        (r1936539)
@@ -35,6 +35,8 @@ import javax.imageio.stream.ImageInputSt
 import org.apache.pdfbox.io.IOUtils;
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.io.RandomAccessRead;
+import org.apache.pdfbox.io.RandomAccessReadBuffer;
 import org.apache.pdfbox.pdmodel.PDPage;
 import org.apache.pdfbox.pdmodel.common.PDRectangle;
 import org.apache.pdfbox.pdmodel.PDPageContentStream;
@@ -318,4 +320,40 @@ class CCITTFactoryTest
             assertEquals(1, document.getNumberOfPages());
         }
     }
-}
+
+    /**
+     * Tests that CCITTFactory's private readlong() reads a TIFF LONG as an 
unsigned 32-bit
+     * value. The previous implementation returned a (possibly negative) int, 
which was then
+     * sign-extended when widened to long, corrupting IFD offsets/counts whose 
high bit is set
+     * (e.g. 0x80000000 and above).
+     */
+    @Test
+    void testReadLongIsUnsigned() throws IOException
+    {
+        // all bits set: 0xFFFFFFFF == 4294967295 as an unsigned TIFF LONG.
+        // The buggy code returned the int -1, which as a long is -1, not 
4294967295.
+        byte[] allOnes = { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF 
};
+        assertReadLongUnsigned('I', allOnes, 0xFFFFFFFFL);
+        assertReadLongUnsigned('M', allOnes, 0xFFFFFFFFL);
+
+        // only the top bit set, in each byte order: 0x80000000 == 2147483648 
unsigned.
+        // The buggy code returned the int Integer.MIN_VALUE, which 
sign-extends to a
+        // large negative long instead of 2147483648.
+        byte[] littleEndianTopBit = { 0x00, 0x00, 0x00, (byte) 0x80 };
+        assertReadLongUnsigned('I', littleEndianTopBit, 0x80000000L);
+
+        byte[] bigEndianTopBit = { (byte) 0x80, 0x00, 0x00, 0x00 };
+        assertReadLongUnsigned('M', bigEndianTopBit, 0x80000000L);
+    }
+
+    private static void assertReadLongUnsigned(char endianess, byte[] bytes,
+                                               long expected) throws 
IOException
+    {
+        try (RandomAccessRead raf = new RandomAccessReadBuffer(bytes))
+        {
+            long value = CCITTFactory.readlong(endianess, raf);
+            assertEquals(expected, value);
+            assertTrue(value >= 0, "TIFF LONG must be read as unsigned, not 
sign-extended");
+        }
+    }
+}
\ No newline at end of file

Reply via email to