This is an automated email from the ASF dual-hosted git repository. markt-asf pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit a2ee8e625faa7b704fb0e76c6c75c5ce40423a25 Author: Mark Thomas <[email protected]> AuthorDate: Tue Jun 2 21:16:26 2026 +0100 Test cases co-authored with CoPilot/GPT-5.4 --- .../apache/tomcat/util/buf/TestB2CConverter.java | 269 +++++++++++++++++++++ 1 file changed, 269 insertions(+) diff --git a/test/org/apache/tomcat/util/buf/TestB2CConverter.java b/test/org/apache/tomcat/util/buf/TestB2CConverter.java index 8c30591f44..a5e83fcfa6 100644 --- a/test/org/apache/tomcat/util/buf/TestB2CConverter.java +++ b/test/org/apache/tomcat/util/buf/TestB2CConverter.java @@ -16,6 +16,9 @@ */ package org.apache.tomcat.util.buf; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.MalformedInputException; import java.nio.charset.StandardCharsets; @@ -24,6 +27,8 @@ import java.util.Locale; import org.junit.Assert; import org.junit.Test; +import org.apache.catalina.connector.InputBuffer; + public class TestB2CConverter { private static final byte[] UTF16_MESSAGE = @@ -140,4 +145,268 @@ public class TestB2CConverter { } Assert.assertNotNull(e); } + + + @Test + public void testLeftoverChunk() throws Exception { + // E2 8C A8 is the "keyboard" character + B2CConverter conv = new B2CConverter(StandardCharsets.UTF_8); + ByteChunk bc = new ByteChunk(); + CharChunk cc = new CharChunk(); + cc.allocate(1, -1); + + bc.append((byte) 0x41); + + bc.append((byte) 0xE2); + conv.convert(bc, cc, false); + Assert.assertEquals(1, cc.getLength()); + + bc.append((byte) 0x8C); + conv.convert(bc, cc, false); + Assert.assertEquals(1, cc.getLength()); + + bc.append((byte) 0xA8); + conv.convert(bc, cc, true); + // Expect overflow so while all 3 bytes are present, they aren't converted + Assert.assertEquals(1, cc.getLength()); + + cc.setLimit(2); + cc.makeSpace(2); + conv.convert(bc, cc, true); + Assert.assertEquals("A\u2328", cc.toString()); + } + + + @Test + public void testLeftoverBuffer() throws Exception { + // E2 8C A8 is the "keyboard" character + B2CConverter conv = new B2CConverter(StandardCharsets.UTF_8); + ByteBuffer bb = ByteBuffer.allocate(8); + CharBuffer cb = newCharBuffer(1); + TesterInputBuffer ib = new TesterInputBuffer(bb); + + bb.put((byte) 0x41); + bb.flip(); + + conv.convert(bb, cb, ib, false); + assertCharBufferEquals("A", cb); + + // Will trigger overflow if there is a complete character + bb.clear(); + bb.put((byte) 0xE2); + bb.flip(); + conv.convert(bb, cb, ib, false); + assertCharBufferEquals("A", cb); + + // NO-OP (underflow) + conv.convert(bb, cb, ib, false); + assertCharBufferEquals("A", cb); + + // Adds second byte of an incomplete 3 byte character to leftovers + ib.setNextRealBytes(new byte[] { (byte) 0x8C }); + conv.convert(bb, cb, ib, false); + assertCharBufferEquals("A", cb); + + // Adds final byte of 3 byte character to leftovers. Not converted as output will overflow + ib.setNextRealBytes(new byte[] { (byte) 0xA8 }); + conv.convert(bb, cb, ib, false); + assertCharBufferEquals("A", cb); + + cb = newCharBuffer(2); + cb.limit(cb.capacity()); + cb.put('A'); + cb.limit(1); + conv.convert(bb, cb, ib, false); + assertCharBufferEquals("A\u2328", cb); + } + + + @Test + public void testLeftoverChunkWithTrailingBytes() throws Exception { + B2CConverter conv = new B2CConverter(StandardCharsets.UTF_8); + ByteChunk bc = new ByteChunk(); + CharChunk cc = new CharChunk(); + cc.allocate(4, -1); + + bc.append((byte) 0x41); + bc.append((byte) 0xE2); + conv.convert(bc, cc, false); + + bc.append((byte) 0x8C); + conv.convert(bc, cc, false); + + bc.append((byte) 0xA8); + bc.append((byte) 0x42); + conv.convert(bc, cc, true); + + Assert.assertEquals("A\u2328B", cc.toString()); + } + + + @Test + public void testLeftoverChunkMalformed() throws Exception { + B2CConverter conv = new B2CConverter(StandardCharsets.UTF_8, true); + ByteChunk bc = new ByteChunk(); + CharChunk cc = new CharChunk(); + cc.allocate(4, -1); + + bc.append((byte) 0x41); + bc.append((byte) 0xE2); + conv.convert(bc, cc, false); + + bc.append((byte) 0x42); + conv.convert(bc, cc, true); + + Assert.assertEquals("A\ufffdB", cc.toString()); + } + + + @Test + public void testLeftoverChunkMalformedAtEnd() throws Exception { + B2CConverter conv = new B2CConverter(StandardCharsets.UTF_8, true); + ByteChunk bc = new ByteChunk(); + CharChunk cc = new CharChunk(); + cc.allocate(4, -1); + + bc.append((byte) 0x41); + bc.append((byte) 0xE2); + conv.convert(bc, cc, true); + + Assert.assertEquals("A\ufffd", cc.toString()); + } + + + @Test + public void testBug54602d() throws Exception { + B2CConverter conv = new B2CConverter(StandardCharsets.UTF_8); + ByteBuffer bb = ByteBuffer.allocate(4); + CharBuffer cb = newCharBuffer(4); + TesterInputBuffer ib = new TesterInputBuffer(bb); + + bb.put(UTF8_PARTIAL); + bb.flip(); + conv.convert(bb, cb, ib, false); + assertCharBufferEquals("", cb); + + conv.convert(bb, cb, ib, false); + assertCharBufferEquals("", cb); + + Exception e = null; + try { + conv.convert(bb, cb, ib, true); + } catch (MalformedInputException mie) { + e = mie; + } + Assert.assertNotNull(e); + } + + + @Test(timeout = 1000) + public void testLeftoverBufferWithBufferedContinuationBytes() throws Exception { + B2CConverter conv = new B2CConverter(StandardCharsets.UTF_8); + ByteBuffer bb = ByteBuffer.wrap(new byte[] { (byte) 0xE2 }); + CharBuffer cb = newCharBuffer(4); + TesterInputBuffer ib = new TesterInputBuffer(bb); + + conv.convert(bb, cb, ib, false); + + bb = ByteBuffer.wrap(new byte[] { (byte) 0x8C, (byte) 0xA8, (byte) 0x42 }); + ib.setByteBuffer(bb); + conv.convert(bb, cb, ib, true); + + assertCharBufferEquals("\u2328B", cb); + } + + + @Test + public void testLeftoverBufferRefillWithTrailingBytes() throws Exception { + B2CConverter conv = new B2CConverter(StandardCharsets.UTF_8); + ByteBuffer bb = ByteBuffer.allocate(8); + CharBuffer cb = newCharBuffer(4); + TesterInputBuffer ib = new TesterInputBuffer(bb); + + bb.put((byte) 0xE2); + bb.flip(); + conv.convert(bb, cb, ib, false); + + ib.setNextRealBytes(new byte[] { (byte) 0x8C, (byte) 0xA8, (byte) 0x42 }); + conv.convert(bb, cb, ib, true); + + assertCharBufferEquals("\u2328B", cb); + } + + + @Test + public void testLeftoverBufferRefillWithReplacementBuffer() throws Exception { + B2CConverter conv = new B2CConverter(StandardCharsets.UTF_8); + ByteBuffer bb = ByteBuffer.allocate(8); + CharBuffer cb = newCharBuffer(4); + TesterInputBuffer ib = new TesterInputBuffer(bb); + + bb.put((byte) 0xE2); + bb.flip(); + conv.convert(bb, cb, ib, false); + + ib.replaceNextByteBuffer(new byte[] { (byte) 0x8C, (byte) 0xA8, (byte) 0x42 }); + conv.convert(bb, cb, ib, true); + + assertCharBufferEquals("\u2328B", cb); + } + + + private static class TesterInputBuffer extends InputBuffer { + + private byte[] nextRealBytes = null; + private boolean replaceByteBuffer = false; + + TesterInputBuffer(ByteBuffer byteBuffer) { + super(null); + setByteBuffer(byteBuffer); + } + + @Override + public int realReadBytes() throws IOException { + if (nextRealBytes == null) { + return -1; + } else { + ByteBuffer byteBuffer; + if (replaceByteBuffer) { + byteBuffer = ByteBuffer.allocate(Math.max(getByteBuffer().capacity(), nextRealBytes.length)); + setByteBuffer(byteBuffer); + } else { + byteBuffer = getByteBuffer(); + } + byteBuffer.clear(); + byteBuffer.put(nextRealBytes); + byteBuffer.flip(); + int result = nextRealBytes.length; + nextRealBytes = null; + replaceByteBuffer = false; + return result; + } + } + + public void setNextRealBytes(byte[] nextRealBytes) { + this.nextRealBytes = nextRealBytes; + } + + public void replaceNextByteBuffer(byte[] nextRealBytes) { + this.nextRealBytes = nextRealBytes; + replaceByteBuffer = true; + } + } + + + private static CharBuffer newCharBuffer(int capacity) { + CharBuffer charBuffer = CharBuffer.allocate(capacity); + charBuffer.limit(0); + return charBuffer; + } + + + private static void assertCharBufferEquals(String expected, CharBuffer actual) { + CharBuffer actualCopy = actual.asReadOnlyBuffer(); + actualCopy.position(0); + Assert.assertEquals(expected, actualCopy.toString()); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
