Author: tilman
Date: Fri Jul 24 09:22:50 2026
New Revision: 1936540
Log:
PDFBOX-5660: fix readlong(), as suggested by Valery Bokov and Claude Code;
Sonar fix; closes #488
Modified:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java
Modified:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
==============================================================================
---
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
Fri Jul 24 09:22:46 2026 (r1936539)
+++
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
Fri Jul 24 09:22:50 2026 (r1936540)
@@ -306,7 +306,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)
@@ -325,7 +325,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)
@@ -475,13 +475,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/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java
==============================================================================
---
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java
Fri Jul 24 09:22:46 2026 (r1936539)
+++
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java
Fri Jul 24 09:22:50 2026 (r1936540)
@@ -34,6 +34,8 @@ import javax.imageio.stream.ImageInputSt
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;
@@ -316,4 +318,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