From 7755c4697302fbfab875978377896acdfbe11df6 Mon Sep 17 00:00:00 2001 From: Jonas Konrad Date: Fri, 25 Sep 2026 17:37:58 +0200 Subject: [PATCH] Make stream failures sticky in LZ4FrameInputStream and LZ4BlockInputStream After an IOException from reading a block or frame header, both streams could keep returning data they had already rejected, or throw unchecked exceptions (NullPointerException, IndexOutOfBoundsException). Record the first failure, wrapping RuntimeExceptions in an IOException, and make later read(), skip() and the frame stream's expected content size methods throw an IOException caused by it. available() returns 0 once the stream has failed. Fixes #101 Co-Authored-By: Claude Opus 5.5 --- .../net/jpountz/lz4/LZ4BlockInputStream.java | 28 +++++ .../net/jpountz/lz4/LZ4FrameInputStream.java | 42 ++++++- .../jpountz/lz4/LZ4BlockStreamingTest.java | 55 ++++++++ .../net/jpountz/lz4/LZ4FrameIOStreamTest.java | 117 ++++++++++++++++++ 4 files changed, 241 insertions(+), 1 deletion(-) diff --git a/src/java/net/jpountz/lz4/LZ4BlockInputStream.java b/src/java/net/jpountz/lz4/LZ4BlockInputStream.java index 12b4dbfb..8725f624 100644 --- a/src/java/net/jpountz/lz4/LZ4BlockInputStream.java +++ b/src/java/net/jpountz/lz4/LZ4BlockInputStream.java @@ -39,6 +39,8 @@ * {@link InputStream} implementation to decode data written with * {@link LZ4BlockOutputStream}. This class is not thread-safe and does not * support {@link #mark(int)}/{@link #reset()}. + * Once a read fails with an {@link IOException}, every later read or skip on + * this stream throws an {@link IOException} as well. *

Use {@link Builder#withAcceptOversizedBlocks(boolean)} only for trusted * inputs that may contain noncanonical legacy LZ4 blocks. Enabling it restores * acceptance of blocks whose compressed length is greater than or equal to the @@ -57,6 +59,8 @@ public class LZ4BlockInputStream extends FilterInputStream { private int originalLen; private int o; private boolean finished; + // set once refill() fails; the stream is unusable afterwards + private IOException failure; /** * Creates a new LZ4 input stream to read from the specified underlying InputStream. @@ -187,11 +191,15 @@ public static Builder newBuilder() { @Override public int available() throws IOException { + if (failure != null) { + return 0; + } return originalLen - o; } @Override public int read() throws IOException { + ensureNotFailed(); if (finished) { return -1; } @@ -207,6 +215,7 @@ public int read() throws IOException { @Override public int read(byte[] b, int off, int len) throws IOException { SafeUtils.checkRange(b, off, len); + ensureNotFailed(); if (finished) { return -1; } @@ -229,6 +238,7 @@ public int read(byte[] b) throws IOException { @Override public long skip(long n) throws IOException { + ensureNotFailed(); if (n <= 0 || finished) { return 0; } @@ -243,7 +253,25 @@ public long skip(long n) throws IOException { return skipped; } + private void ensureNotFailed() throws IOException { + if (failure != null) { + throw new IOException("Stream previously failed", failure); + } + } + private void refill() throws IOException { + try { + refill0(); + } catch (IOException e) { + failure = e; + throw e; + } catch (RuntimeException e) { + failure = new IOException("Stream is corrupted", e); + throw failure; + } + } + + private void refill0() throws IOException { if (!tryReadFully(compressedBuffer, HEADER_LENGTH)) { if (!stopOnEmptyBlock) { finished = true; diff --git a/src/java/net/jpountz/lz4/LZ4FrameInputStream.java b/src/java/net/jpountz/lz4/LZ4FrameInputStream.java index d01eb332..fba399b8 100644 --- a/src/java/net/jpountz/lz4/LZ4FrameInputStream.java +++ b/src/java/net/jpountz/lz4/LZ4FrameInputStream.java @@ -30,6 +30,8 @@ /** * Implementation of the v1.5.1 LZ4 Frame format. This class is NOT thread safe. + * Once a read fails with an {@link IOException}, every later read or skip on + * this stream throws an {@link IOException} as well. *

* Not Supported: