Author: bodewig Date: Mon Jan 20 13:26:08 2014 New Revision: 1559687 URL: http://svn.apache.org/r1559687 Log: no longer try to read one byte ahead in BZip2CompressorInputStream - COMPRESS-253
Added: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/compressors/bzip2/PythonTruncatedBzip2Test.java (with props) Modified: commons/proper/compress/trunk/src/changes/changes.xml commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java Modified: commons/proper/compress/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/changes/changes.xml?rev=1559687&r1=1559686&r2=1559687&view=diff ============================================================================== --- commons/proper/compress/trunk/src/changes/changes.xml (original) +++ commons/proper/compress/trunk/src/changes/changes.xml Mon Jan 20 13:26:08 2014 @@ -44,6 +44,10 @@ The <action> type attribute can be add,u <body> <release version="1.8" date="not released, yet" description="Release 1.8"> + <action issue="COMPRESS-253" type="fix" date="2014-01-20"> + BZip2CompressorInputStream read fewer bytes than possible from + a truncated stream. + </action> </release> <release version="1.7" date="2014-01-20" description="Release 1.7"> Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java?rev=1559687&r1=1559686&r2=1559687&view=diff ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java (original) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java Mon Jan 20 13:26:08 2014 @@ -64,8 +64,6 @@ public class BZip2CompressorInputStream private InputStream in; private final boolean decompressConcatenated; - private int currentChar = -1; - private static final int EOF = 0; private static final int START_BLOCK_STATE = 1; private static final int RAND_PART_A_STATE = 2; @@ -133,7 +131,6 @@ public class BZip2CompressorInputStream init(true); initBlock(); - setupBlock(); } @Override @@ -171,12 +168,13 @@ public class BZip2CompressorInputStream final int hi = offs + len; int destOffs = offs; - for (int b; (destOffs < hi) && ((b = read0()) >= 0);) { + int b; + while (destOffs < hi && ((b = read0()) >= 0)) { dest[destOffs++] = (byte) b; + count(1); } int c = (destOffs == offs) ? -1 : (destOffs - offs); - count(c); return c; } @@ -196,42 +194,34 @@ public class BZip2CompressorInputStream } private int read0() throws IOException { - final int retChar = this.currentChar; - - switch (this.currentState) { + switch (currentState) { case EOF: return -1; case START_BLOCK_STATE: - throw new IllegalStateException(); + return setupBlock(); case RAND_PART_A_STATE: throw new IllegalStateException(); case RAND_PART_B_STATE: - setupRandPartB(); - break; + return setupRandPartB(); case RAND_PART_C_STATE: - setupRandPartC(); - break; + return setupRandPartC(); case NO_RAND_PART_A_STATE: throw new IllegalStateException(); case NO_RAND_PART_B_STATE: - setupNoRandPartB(); - break; + return setupNoRandPartB(); case NO_RAND_PART_C_STATE: - setupNoRandPartC(); - break; + return setupNoRandPartC(); default: throw new IllegalStateException(); } - - return retChar; } private boolean init(boolean isFirstStream) throws IOException { @@ -800,9 +790,9 @@ public class BZip2CompressorInputStream return dataShadow.perm[zt][zvec - dataShadow.base[zt][zn]]; } - private void setupBlock() throws IOException { - if (this.data == null) { - return; + private int setupBlock() throws IOException { + if (currentState == EOF || this.data == null) { + return -1; } final int[] cftab = this.data.cftab; @@ -832,13 +822,12 @@ public class BZip2CompressorInputStream if (this.blockRandomised) { this.su_rNToGo = 0; this.su_rTPos = 0; - setupRandPartA(); - } else { - setupNoRandPartA(); + return setupRandPartA(); } + return setupNoRandPartA(); } - private void setupRandPartA() throws IOException { + private int setupRandPartA() throws IOException { if (this.su_i2 <= this.last) { this.su_chPrev = this.su_ch2; int su_ch2Shadow = this.data.ll8[this.su_tPos] & 0xff; @@ -853,39 +842,39 @@ public class BZip2CompressorInputStream } this.su_ch2 = su_ch2Shadow ^= (this.su_rNToGo == 1) ? 1 : 0; this.su_i2++; - this.currentChar = su_ch2Shadow; this.currentState = RAND_PART_B_STATE; this.crc.updateCRC(su_ch2Shadow); + return su_ch2Shadow; } else { endBlock(); initBlock(); - setupBlock(); + return setupBlock(); } } - private void setupNoRandPartA() throws IOException { + private int setupNoRandPartA() throws IOException { if (this.su_i2 <= this.last) { this.su_chPrev = this.su_ch2; int su_ch2Shadow = this.data.ll8[this.su_tPos] & 0xff; this.su_ch2 = su_ch2Shadow; this.su_tPos = this.data.tt[this.su_tPos]; this.su_i2++; - this.currentChar = su_ch2Shadow; this.currentState = NO_RAND_PART_B_STATE; this.crc.updateCRC(su_ch2Shadow); + return su_ch2Shadow; } else { this.currentState = NO_RAND_PART_A_STATE; endBlock(); initBlock(); - setupBlock(); + return setupBlock(); } } - private void setupRandPartB() throws IOException { + private int setupRandPartB() throws IOException { if (this.su_ch2 != this.su_chPrev) { this.currentState = RAND_PART_A_STATE; this.su_count = 1; - setupRandPartA(); + return setupRandPartA(); } else if (++this.su_count >= 4) { this.su_z = (char) (this.data.ll8[this.su_tPos] & 0xff); this.su_tPos = this.data.tt[this.su_tPos]; @@ -902,51 +891,51 @@ public class BZip2CompressorInputStream if (this.su_rNToGo == 1) { this.su_z ^= 1; } - setupRandPartC(); + return setupRandPartC(); } else { this.currentState = RAND_PART_A_STATE; - setupRandPartA(); + return setupRandPartA(); } } - private void setupRandPartC() throws IOException { + private int setupRandPartC() throws IOException { if (this.su_j2 < this.su_z) { - this.currentChar = this.su_ch2; this.crc.updateCRC(this.su_ch2); this.su_j2++; + return this.su_ch2; } else { this.currentState = RAND_PART_A_STATE; this.su_i2++; this.su_count = 0; - setupRandPartA(); + return setupRandPartA(); } } - private void setupNoRandPartB() throws IOException { + private int setupNoRandPartB() throws IOException { if (this.su_ch2 != this.su_chPrev) { this.su_count = 1; - setupNoRandPartA(); + return setupNoRandPartA(); } else if (++this.su_count >= 4) { this.su_z = (char) (this.data.ll8[this.su_tPos] & 0xff); this.su_tPos = this.data.tt[this.su_tPos]; this.su_j2 = 0; - setupNoRandPartC(); + return setupNoRandPartC(); } else { - setupNoRandPartA(); + return setupNoRandPartA(); } } - private void setupNoRandPartC() throws IOException { + private int setupNoRandPartC() throws IOException { if (this.su_j2 < this.su_z) { int su_ch2Shadow = this.su_ch2; - this.currentChar = su_ch2Shadow; this.crc.updateCRC(su_ch2Shadow); this.su_j2++; this.currentState = NO_RAND_PART_C_STATE; + return su_ch2Shadow; } else { this.su_i2++; this.su_count = 0; - setupNoRandPartA(); + return setupNoRandPartA(); } } Added: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/compressors/bzip2/PythonTruncatedBzip2Test.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/compressors/bzip2/PythonTruncatedBzip2Test.java?rev=1559687&view=auto ============================================================================== --- commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/compressors/bzip2/PythonTruncatedBzip2Test.java (added) +++ commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/compressors/bzip2/PythonTruncatedBzip2Test.java Mon Jan 20 13:26:08 2014 @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.commons.compress.compressors.bzip2; + +import static org.junit.Assert.assertArrayEquals; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.Arrays; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Testcase porting a test from Python's testsuite. + * @see "https://issues.apache.org/jira/browse/COMPRESS-253" + */ +public class PythonTruncatedBzip2Test { + + private static String TEXT = "root:x:0:0:root:/root:/bin/bash\nbin:x:1:1:bin:/bin:\ndaemon:x:2:2:daemon:/sbin:\nadm:x:3:4:adm:/var/adm:\nlp:x:4:7:lp:/var/spool/lpd:\nsync:x:5:0:sync:/sbin:/bin/sync\nshutdown:x:6:0:shutdown:/sbin:/sbin/shutdown\nhalt:x:7:0:halt:/sbin:/sbin/halt\nmail:x:8:12:mail:/var/spool/mail:\nnews:x:9:13:news:/var/spool/news:\nuucp:x:10:14:uucp:/var/spool/uucp:\noperator:x:11:0:operator:/root:\ngames:x:12:100:games:/usr/games:\ngopher:x:13:30:gopher:/usr/lib/gopher-data:\nftp:x:14:50:FTP User:/var/ftp:/bin/bash\nnobody:x:65534:65534:Nobody:/home:\npostfix:x:100:101:postfix:/var/spool/postfix:\nniemeyer:x:500:500::/home/niemeyer:/bin/bash\npostgres:x:101:102:PostgreSQL Server:/var/lib/pgsql:/bin/bash\nmysql:x:102:103:MySQL server:/var/lib/mysql:/bin/bash\nwww:x:103:104::/var/www:/bin/false\n"; + + private static byte[] DATA; + private static byte[] TRUNCATED_DATA; + private ReadableByteChannel bz2Channel; + + @BeforeClass + public static void initializeTestData() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + BZip2CompressorOutputStream bz2out = new BZip2CompressorOutputStream(out); + bz2out.write(TEXT.getBytes(), 0, TEXT.getBytes().length); + bz2out.close(); + DATA = out.toByteArray(); + + // Drop the eos_magic field (6 bytes) and CRC (4 bytes). + TRUNCATED_DATA = Arrays.copyOfRange(DATA, 0, DATA.length - 10); + } + + @Before + public void initializeChannel() throws IOException { + InputStream source = new ByteArrayInputStream(TRUNCATED_DATA); + this.bz2Channel = makeBZ2C(source); + } + + @After + public void closeChannel() throws IOException { + bz2Channel.close(); + bz2Channel = null; + } + + @Test(expected = IOException.class) + public void testTruncatedData() throws IOException { + //with BZ2File(self.filename) as f: + // self.assertRaises(EOFError, f.read) + System.out.println("Attempt to read the whole thing in, should throw ..."); + ByteBuffer buffer = ByteBuffer.allocate(8192); + bz2Channel.read(buffer); + } + + @Test + public void testPartialReadTruncatedData() throws IOException { + //with BZ2File(self.filename) as f: + // self.assertEqual(f.read(len(self.TEXT)), self.TEXT) + // self.assertRaises(EOFError, f.read, 1) + + final int length = TEXT.length(); + ByteBuffer buffer = ByteBuffer.allocate(length); + bz2Channel.read(buffer); + + assertArrayEquals(Arrays.copyOfRange(TEXT.getBytes(), 0, length), + buffer.array()); + + // subsequent read should throw + buffer = ByteBuffer.allocate(1); + try { + bz2Channel.read(buffer); + Assert.fail("The read should have thrown."); + } catch (IOException e) { + // pass + } + } + + private static ReadableByteChannel makeBZ2C(InputStream source) throws IOException { + BufferedInputStream bin = new BufferedInputStream(source); + BZip2CompressorInputStream bZin = new BZip2CompressorInputStream(bin, true); + + return Channels.newChannel(bZin); + } +} Propchange: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/compressors/bzip2/PythonTruncatedBzip2Test.java ------------------------------------------------------------------------------ svn:eol-style = native