From 6f284bfc37d34c0103a2fc6dc1858fbcafa14c11 Mon Sep 17 00:00:00 2001 From: AryanRG Date: Sun, 18 Oct 2026 03:49:42 +0530 Subject: [PATCH] IMAGING-389: Fix searchQuad byte matching Use a four-byte sliding window so high-bit bytes and overlapping partial matches are found. Add focused regressions and an issue-linked change note. --- src/changes/changes.xml | 1 + .../imaging/common/BinaryFunctions.java | 19 ++++++------ .../imaging/common/BinaryFunctionsTest.java | 29 +++++++++++++++++++ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 6425dea2f..dbf5684a9 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -48,6 +48,7 @@ The type attribute can be add,update,fix,remove. + Fix BinaryFunctions.searchQuad matching for high-bit bytes and overlapping prefixes. Fix Apache RAT plugin console warnings. Fix Javadoc warnings. Fix int overflow in PCX image buffer sizing, now throws AllocationRequestException (#552). diff --git a/src/main/java/org/apache/commons/imaging/common/BinaryFunctions.java b/src/main/java/org/apache/commons/imaging/common/BinaryFunctions.java index 67a122f93..da317166c 100644 --- a/src/main/java/org/apache/commons/imaging/common/BinaryFunctions.java +++ b/src/main/java/org/apache/commons/imaging/common/BinaryFunctions.java @@ -426,17 +426,16 @@ public static byte[] remainingBytes(final String name, final byte[] bytes, final * @throws IOException Thrown if it fails to read from the given input stream. */ public static boolean searchQuad(final int quad, final InputStream in) throws IOException { - final byte[] needle = quadsToByteArray(quad); - int b = -1; - int position = 0; + // Slide a 4-byte window over the stream, so that bytes >= 0x80 and overlapping partial matches are handled. + int window = 0; + int count = 0; + int b; while ((b = in.read()) != -1) { - if (needle[position] == b) { - position++; - if (position == needle.length) { - return true; - } - } else { - position = 0; + window = window << 8 | b; + if (count < 3) { + count++; + } else if (window == quad) { + return true; } } return false; diff --git a/src/test/java/org/apache/commons/imaging/common/BinaryFunctionsTest.java b/src/test/java/org/apache/commons/imaging/common/BinaryFunctionsTest.java index 23debf9dd..796a8095d 100644 --- a/src/test/java/org/apache/commons/imaging/common/BinaryFunctionsTest.java +++ b/src/test/java/org/apache/commons/imaging/common/BinaryFunctionsTest.java @@ -64,6 +64,35 @@ public void testReadBytes() throws IOException { assertArrayEquals(new byte[] { 1, 2, 3 }, read); } + @Test + public void testSearchQuad() throws IOException { + final int quad = BinaryFunctions.charsToQuad('8', 'B', 'I', 'M'); + assertTrue(BinaryFunctions.searchQuad(quad, new ByteArrayInputStream(new byte[] { 0, '8', 'B', 'I', 'M', 1 }))); + assertFalse(BinaryFunctions.searchQuad(quad, new ByteArrayInputStream(new byte[] { '8', 'B', 'I', 'X' }))); + assertFalse(BinaryFunctions.searchQuad(quad, new ByteArrayInputStream(new byte[] { '8', 'B', 'I' }))); + } + + @Test + public void testSearchQuadAfterPartialMatch() throws IOException { + // The byte that breaks a partial match may itself start the quad. + final int quad = BinaryFunctions.charsToQuad('8', 'B', 'I', 'M'); + final InputStream is = new ByteArrayInputStream(new byte[] { '8', '8', 'B', 'I', 'M', 1 }); + assertTrue(BinaryFunctions.searchQuad(quad, is)); + // The stream is left positioned right after the quad. + assertEquals(1, is.read()); + // Overlapping prefix: "AAAB" inside "AAAAB". + final int aaab = BinaryFunctions.charsToQuad('A', 'A', 'A', 'B'); + assertTrue(BinaryFunctions.searchQuad(aaab, new ByteArrayInputStream(new byte[] { 'A', 'A', 'A', 'A', 'B' }))); + } + + @Test + public void testSearchQuadWithHighBitBytes() throws IOException { + // Bytes >= 0x80 must match, for example a JPEG SOI + APP0 marker. + final InputStream is = new ByteArrayInputStream(new byte[] { 0, (byte) 0xFF, (byte) 0xD8, (byte) 0xFF, (byte) 0xE0, 7 }); + assertTrue(BinaryFunctions.searchQuad(0xFFD8FFE0, is)); + assertEquals(7, is.read()); + } + @Test public void testStartsWith() { final byte[] haystack = { 1, 2, 3, 4, 5 };