Here are some small improvements when creating new BlockInputStream instances. This reduces the size of the byte[] for the block header to the actual size and replaces use of ByteArrayInputStream, which has synchronized methods, with a ByteBuffer, which provides the same functionality without synchronization.
diff --git a/src/org/tukaani/xz/BlockInputStream.java b/src/org/tukaani/xz/BlockInputStream.java index 1931bd6..94342e2 100644 --- a/src/org/tukaani/xz/BlockInputStream.java +++ b/src/org/tukaani/xz/BlockInputStream.java @@ -9,13 +9,15 @@ package org.tukaani.xz; -import java.io.InputStream; import java.io.DataInputStream; -import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; import java.util.Arrays; -import org.tukaani.xz.common.DecoderUtil; + import org.tukaani.xz.check.Check; +import org.tukaani.xz.common.DecoderUtil; class BlockInputStream extends InputStream { private final DataInputStream inData; @@ -44,17 +46,18 @@ class BlockInputStream extends InputStream { this.verifyCheck = verifyCheck; inData = new DataInputStream(in); - byte[] buf = new byte[DecoderUtil.BLOCK_HEADER_SIZE_MAX]; - // Block Header Size or Index Indicator - inData.readFully(buf, 0, 1); + int b = inData.readUnsignedByte(); // See if this begins the Index field. - if (buf[0] == 0x00) + if (b == 0x00) throw new IndexIndicatorException(); // Read the rest of the Block Header. - headerSize = 4 * ((buf[0] & 0xFF) + 1); + headerSize = ((b & 0xFF) + 1) << 2; + + final byte[] buf = new byte[headerSize]; + buf[0] = (byte) b; inData.readFully(buf, 1, headerSize - 1); // Validate the CRC32. @@ -71,11 +74,9 @@ class BlockInputStream extends InputStream { long[] filterIDs = new long[filterCount]; byte[][] filterProps = new byte[filterCount][]; - // Use a stream to parse the fields after the Block Flags field. + // Use a ByteBuffer to parse the fields after the Block Flags field. // Exclude the CRC32 field at the end. - ByteArrayInputStream bufStream = new ByteArrayInputStream( - buf, 2, headerSize - 6); - + final ByteBuffer bb = ByteBuffer.wrap(buf, 2, headerSize - 6); try { // Set the maximum valid compressed size. This is overriden // by the value from the Compressed Size field if it is present. @@ -85,7 +86,7 @@ class BlockInputStream extends InputStream { // Decode and validate Compressed Size if the relevant flag // is set in Block Flags. if ((buf[1] & 0x40) != 0x00) { - compressedSizeInHeader = DecoderUtil.decodeVLI(bufStream); + compressedSizeInHeader = DecoderUtil.decodeVLI(bb); if (compressedSizeInHeader == 0 || compressedSizeInHeader > compressedSizeLimit) @@ -97,27 +98,27 @@ class BlockInputStream extends InputStream { // Decode Uncompressed Size if the relevant flag is set // in Block Flags. if ((buf[1] & 0x80) != 0x00) - uncompressedSizeInHeader = DecoderUtil.decodeVLI(bufStream); + uncompressedSizeInHeader = DecoderUtil.decodeVLI(bb); // Decode Filter Flags. for (int i = 0; i < filterCount; ++i) { - filterIDs[i] = DecoderUtil.decodeVLI(bufStream); + filterIDs[i] = DecoderUtil.decodeVLI(bb); - long filterPropsSize = DecoderUtil.decodeVLI(bufStream); - if (filterPropsSize > bufStream.available()) + long filterPropsSize = DecoderUtil.decodeVLI(bb); + if (filterPropsSize > bb.remaining()) throw new CorruptedInputException(); filterProps[i] = new byte[(int)filterPropsSize]; - bufStream.read(filterProps[i]); + bb.get(filterProps[i]); } - } catch (IOException e) { + } catch (IOException | BufferUnderflowException e) { throw new CorruptedInputException("XZ Block Header is corrupt"); } // Check that the remaining bytes are zero. - for (int i = bufStream.available(); i > 0; --i) - if (bufStream.read() != 0x00) + for (int i = bb.remaining(); i > 0; --i) + if (bb.get() != 0x00) throw new UnsupportedOptionsException( "Unsupported options in XZ Block Header"); diff --git a/src/org/tukaani/xz/common/DecoderUtil.java b/src/org/tukaani/xz/common/DecoderUtil.java index 77ba441..c3aa21d 100644 --- a/src/org/tukaani/xz/common/DecoderUtil.java +++ b/src/org/tukaani/xz/common/DecoderUtil.java @@ -9,14 +9,17 @@ package org.tukaani.xz.common; -import java.io.InputStream; -import java.io.IOException; import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; import java.util.zip.CRC32; -import org.tukaani.xz.XZ; -import org.tukaani.xz.XZFormatException; + import org.tukaani.xz.CorruptedInputException; import org.tukaani.xz.UnsupportedOptionsException; +import org.tukaani.xz.XZ; +import org.tukaani.xz.XZFormatException; public class DecoderUtil extends Util { public static boolean isCRC32Valid(byte[] buf, int off, int len, @@ -118,4 +121,27 @@ public class DecoderUtil extends Util { return num; } + + public static long decodeVLI(ByteBuffer bb) throws IOException { + try { + int b = bb.get(); + long num = b & 0x7F; + int i = 0; + + while ((b & 0x80) != 0x00) { + if (++i >= VLI_SIZE_MAX) + throw new CorruptedInputException(); + + b = bb.get(); + if (b == 0x00) + throw new CorruptedInputException(); + + num |= (long)(b & 0x7F) << (i * 7); + } + + return num; + } catch (BufferUnderflowException e) { + throw new EOFException(); + } + } }