Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 34 additions & 13 deletions src/java/net/jpountz/lz4/LZ4DecompressorWithLength.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,22 @@ private static void checkDestinationLength(int decompressedLength, int maxDestin
}
}

private static int checkWritten(int written, int declared) {
if (written != declared) {
throw new LZ4Exception("Decompressed " + written
+ " bytes, but length prefix declared " + declared);
}
return written;
}

/**
* Convenience method, equivalent to calling
* {@link #decompress(byte[], int, byte[], int) decompress(src, 0, dest, 0)}.
*
* @param src the compressed data
* @param dest the destination buffer to store the decompressed data
* @return the number of bytes read to restore the original input
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes, which always equals the declared decompressed length (when {@link LZ4SafeDecompressor} is used)
* @throws LZ4Exception if the compressed data is malformed or does not decompress to exactly the declared decompressed length
*/
public int decompress(byte[] src, byte[] dest) {
return decompress(src, 0, dest, 0);
Expand All @@ -204,7 +213,8 @@ public int decompress(byte[] src, byte[] dest) {
* @param srcOff the start offset in src
* @param dest the destination buffer to store the decompressed data
* @param destOff the start offset in dest
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes (when {@link LZ4SafeDecompressor} is used)
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes, which always equals the declared decompressed length (when {@link LZ4SafeDecompressor} is used)
* @throws LZ4Exception if the compressed data is malformed or does not decompress to exactly the declared decompressed length
*/
public int decompress(byte[] src, int srcOff, byte[] dest, int destOff) {
if (safeDecompressor != null) {
Expand All @@ -229,7 +239,8 @@ public int decompress(byte[] src, int srcOff, byte[] dest, int destOff) {
* @param srcLen the exact size of the compressed data (ignored when {@link LZ4FastDecompressor} is used)
* @param dest the destination buffer to store the decompressed data
* @param destOff the start offset in dest
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes (when {@link LZ4SafeDecompressor} is used)
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes, which always equals the declared decompressed length (when {@link LZ4SafeDecompressor} is used)
* @throws LZ4Exception if the compressed data is malformed or does not decompress to exactly the declared decompressed length
*/
public int decompress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff) {
if (safeDecompressor == null) {
Expand All @@ -238,7 +249,7 @@ public int decompress(byte[] src, int srcOff, int srcLen, byte[] dest, int destO
final int destLen = getDecompressedLength(src, srcOff);
SafeUtils.checkRange(dest, destOff, 0);
checkDestinationLength(destLen, dest.length - destOff);
return safeDecompressor.decompress(src, srcOff + 4, srcLen - 4, dest, destOff, destLen);
return checkWritten(safeDecompressor.decompress(src, srcOff + 4, srcLen - 4, dest, destOff, destLen), destLen);
}

/**
Expand All @@ -247,7 +258,8 @@ public int decompress(byte[] src, int srcOff, int srcLen, byte[] dest, int destO
*
* @param src the compressed data
* @return the decompressed data
* @throws LZ4Exception if the declared decompressed length is invalid or exceeds the configured maximum
* @throws LZ4Exception if the declared decompressed length is invalid or exceeds the configured maximum,
* or if the compressed data does not decompress to exactly the declared decompressed length
*/
public byte[] decompress(byte[] src) {
return decompress(src, 0);
Expand All @@ -265,7 +277,8 @@ public byte[] decompress(byte[] src) {
* @param src the compressed data
* @param srcOff the start offset in src
* @return the decompressed data
* @throws LZ4Exception if the declared decompressed length is invalid or exceeds the configured maximum
* @throws LZ4Exception if the declared decompressed length is invalid or exceeds the configured maximum,
* or if the compressed data does not decompress to exactly the declared decompressed length
*/
public byte[] decompress(byte[] src, int srcOff) {
if (safeDecompressor != null) {
Expand All @@ -289,7 +302,8 @@ public byte[] decompress(byte[] src, int srcOff) {
* @param srcOff the start offset in src
* @param srcLen the exact size of the compressed data (ignored when {@link LZ4FastDecompressor} is used)
* @return the decompressed data
* @throws LZ4Exception if the declared decompressed length is invalid or exceeds the configured maximum
* @throws LZ4Exception if the declared decompressed length is invalid or exceeds the configured maximum,
* or if the compressed data does not decompress to exactly the declared decompressed length
*/
public byte[] decompress(byte[] src, int srcOff, int srcLen) {
if (safeDecompressor == null) {
Expand All @@ -298,7 +312,9 @@ public byte[] decompress(byte[] src, int srcOff, int srcLen) {
SafeUtils.checkRange(src, srcOff, srcLen);
final int destLen = getDecompressedLength(src, srcOff);
checkDecompressedLength(destLen, srcLen - 4);
return safeDecompressor.decompress(src, srcOff + 4, srcLen - 4, destLen);
final byte[] dest = new byte[destLen];
checkWritten(safeDecompressor.decompress(src, srcOff + 4, srcLen - 4, dest, 0, destLen), destLen);
return dest;
}

/**
Expand All @@ -309,6 +325,9 @@ public byte[] decompress(byte[] src, int srcOff, int srcLen) {
*
* @param src the compressed data
* @param dest the destination buffer to store the decompressed data
* @throws LZ4Exception if the compressed data is malformed or does not decompress to exactly the declared
* decompressed length; the positions of the buffers are then left unchanged, but the content
* of <code>dest</code> after its position may have been partially overwritten
*/
public void decompress(ByteBuffer src, ByteBuffer dest) {
final int destLen = getDecompressedLength(src, src.position());
Expand All @@ -318,9 +337,9 @@ public void decompress(ByteBuffer src, ByteBuffer dest) {
src.position(src.position() + 4 + read);
dest.position(dest.position() + destLen);
} else {
final int written = safeDecompressor.decompress(src, src.position() + 4, src.remaining() - 4, dest, dest.position(), destLen);
checkWritten(safeDecompressor.decompress(src, src.position() + 4, src.remaining() - 4, dest, dest.position(), destLen), destLen);
src.position(src.limit());
dest.position(dest.position() + written);
dest.position(dest.position() + destLen);
}
}

Expand All @@ -337,7 +356,8 @@ public void decompress(ByteBuffer src, ByteBuffer dest) {
* @param srcOff the start offset in src
* @param dest the destination buffer to store the decompressed data
* @param destOff the start offset in dest
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes (when {@link LZ4SafeDecompressor} is used)
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes, which always equals the declared decompressed length (when {@link LZ4SafeDecompressor} is used)
* @throws LZ4Exception if the compressed data is malformed or does not decompress to exactly the declared decompressed length
*/
public int decompress(ByteBuffer src, int srcOff, ByteBuffer dest, int destOff) {
if (safeDecompressor != null) {
Expand All @@ -363,7 +383,8 @@ public int decompress(ByteBuffer src, int srcOff, ByteBuffer dest, int destOff)
* @param srcLen the exact size of the compressed data (ignored when {@link LZ4FastDecompressor} is used)
* @param dest the destination buffer to store the decompressed data
* @param destOff the start offset in dest
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes (when {@link LZ4SafeDecompressor} is used)
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes, which always equals the declared decompressed length (when {@link LZ4SafeDecompressor} is used)
* @throws LZ4Exception if the compressed data is malformed or does not decompress to exactly the declared decompressed length
*/
public int decompress(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, int destOff) {
if (safeDecompressor == null) {
Expand All @@ -372,6 +393,6 @@ public int decompress(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, i
final int destLen = getDecompressedLength(src, srcOff);
ByteBufferUtils.checkRange(dest, destOff, 0);
checkDestinationLength(destLen, dest.capacity() - destOff);
return safeDecompressor.decompress(src, srcOff + 4, srcLen - 4, dest, destOff, destLen);
return checkWritten(safeDecompressor.decompress(src, srcOff + 4, srcLen - 4, dest, destOff, destLen), destLen);
}
}
66 changes: 66 additions & 0 deletions src/test/net/jpountz/lz4/LZ4Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,72 @@ public void testWriteToReadOnlyBuffer() {
}
}

@Test
public void testDecompressorWithLengthRejectsShortOutput() {
final byte[] data = new byte[16];
for (int i = 0; i < data.length; ++i) {
data[i] = (byte) i;
}
final byte[] compressed = new LZ4CompressorWithLength(COMPRESSORS[0]).compress(data);
// rewrite the little-endian length prefix to declare more bytes than the data holds
compressed[0] = 20;
compressed[1] = compressed[2] = compressed[3] = 0;
assertEquals(20, LZ4DecompressorWithLength.getDecompressedLength(compressed));

for (LZ4SafeDecompressor safeDecompressor : SAFE_DECOMPRESSORS) {
final LZ4DecompressorWithLength decompressor = new LZ4DecompressorWithLength(safeDecompressor);
try {
decompressor.decompress(compressed);
fail();
} catch (LZ4Exception e) {
assertTrue(e.getMessage(), e.getMessage().contains("length prefix declared"));
}
try {
decompressor.decompress(compressed, 0, compressed.length);
fail();
} catch (LZ4Exception e) {
assertTrue(e.getMessage(), e.getMessage().contains("length prefix declared"));
}
try {
decompressor.decompress(compressed, new byte[20]);
fail();
} catch (LZ4Exception e) {
assertTrue(e.getMessage(), e.getMessage().contains("length prefix declared"));
}
try {
decompressor.decompress(compressed, 0, compressed.length, new byte[20], 0);
fail();
} catch (LZ4Exception e) {
assertTrue(e.getMessage(), e.getMessage().contains("length prefix declared"));
}
for (boolean direct : new boolean[] {false, true}) {
final ByteBuffer src = direct ? ByteBuffer.allocateDirect(compressed.length) : ByteBuffer.allocate(compressed.length);
src.put(compressed).flip();
final ByteBuffer dest = direct ? ByteBuffer.allocateDirect(20) : ByteBuffer.allocate(20);
try {
decompressor.decompress(src, 0, dest, 0);
fail();
} catch (LZ4Exception e) {
assertTrue(e.getMessage(), e.getMessage().contains("length prefix declared"));
}
try {
decompressor.decompress(src, 0, compressed.length, dest, 0);
fail();
} catch (LZ4Exception e) {
assertTrue(e.getMessage(), e.getMessage().contains("length prefix declared"));
}
try {
decompressor.decompress(src, dest);
fail();
} catch (LZ4Exception e) {
assertTrue(e.getMessage(), e.getMessage().contains("length prefix declared"));
}
assertEquals(0, src.position());
assertEquals(0, dest.position());
}
}
}

@Test
@Repeat(iterations = 5)
public void testAllEqual() {
Expand Down
Loading