diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/GunzipOutputStream.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/GunzipOutputStream.java index 55bb4d384af1e1d9827acecf34e17c2089bbb1ad..3905007f51096e48a7ae4f755651a63a37834c06 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/GunzipOutputStream.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/GunzipOutputStream.java @@ -1,5 +1,6 @@ package net.i2p.i2ptunnel; +import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.zip.CRC32; @@ -16,42 +17,40 @@ import net.i2p.data.DataHelper; * Note that the underlying InflaterOutputStream cannot be reused after close(), * so we don't have a Reusable version of this. * + * Sets up GunzipOutputStream -- InflaterOutputStream -- CRC32OutputStream -- uncompressedStream + * + * Not a public API, subject to change, not for external use. + * * Modified from net.i2p.util.ResettableGZIPInputStream to use Java 6 InflaterOutputstream * @since 0.9.21, public since 0.9.50 for LocalHTTPServer */ public class GunzipOutputStream extends InflaterOutputStream { private static final int FOOTER_SIZE = 8; // CRC32 + ISIZE - private final CRC32 _crc32; private final byte _buf1[] = new byte[1]; private boolean _complete; + private boolean _validated; private final byte _footer[] = new byte[FOOTER_SIZE]; private long _bytesReceived; private long _bytesReceivedAtCompletion; - + private enum HeaderState { MB1, MB2, CF, MT0, MT1, MT2, MT3, EF, OS, FLAGS, EH1, EH2, EHDATA, NAME, COMMENT, CRC1, CRC2, DONE } private HeaderState _state = HeaderState.MB1; private int _flags; private int _extHdrToRead; - + /** * Build a new Gunzip stream */ public GunzipOutputStream(OutputStream uncompressedStream) throws IOException { - super(uncompressedStream, new Inflater(true)); - _crc32 = new CRC32(); + super(new CRC32OutputStream(uncompressedStream), new Inflater(true)); } - + @Override public void write(int b) throws IOException { _buf1[0] = (byte) b; write(_buf1, 0, 1); } - - @Override - public void write(byte buf[]) throws IOException { - write(buf, 0, buf.length); - } @Override public void write(byte buf[], int off, int len) throws IOException { @@ -71,20 +70,21 @@ public class GunzipOutputStream extends InflaterOutputStream { super.write(buf, i, 1); if (inf.finished()) { isFinished = true; - _bytesReceivedAtCompletion = _bytesReceived; + _bytesReceivedAtCompletion = _bytesReceived + 1; } } _footer[(int) (_bytesReceived++ % FOOTER_SIZE)] = buf[i]; if (isFinished) { - long footerSize = _bytesReceivedAtCompletion - _bytesReceived; + long footerSize = _bytesReceived - _bytesReceivedAtCompletion; // could be at 7 or 8... // we write the first byte of the footer to the Inflater if necessary... // see comments in ResettableGZIPInputStream for details if (footerSize >= FOOTER_SIZE - 1) { + flush(); try { verifyFooter(); - inf.reset(); // so it doesn't bitch about missing data... _complete = true; + _validated = true; return; } catch (IOException ioe) { // failed at 7, retry at 8 @@ -97,7 +97,7 @@ public class GunzipOutputStream extends InflaterOutputStream { } } } - + /** * Inflater statistic */ @@ -154,21 +154,23 @@ public class GunzipOutputStream extends InflaterOutputStream { @Override public String toString() { - return "GOS read: " + getTotalRead() + " expanded: " + getTotalExpanded() + " remaining: " + getRemaining() + " finished: " + getFinished(); + return "GunzipOutputStream read: " + getTotalRead() + " expanded: " + getTotalExpanded() + + " remaining: " + getRemaining() + " finished: " + getFinished() + + " footer complete: " + _complete + " validated: " + _validated; } /** * @throws IOException on CRC or length check fail */ private void verifyFooter() throws IOException { - int idx = (int) (_bytesReceivedAtCompletion % FOOTER_SIZE); + int idx = (int) (_bytesReceived % FOOTER_SIZE); byte[] footer; if (idx == 0) { footer = _footer; } else { footer = new byte[FOOTER_SIZE]; for (int i = 0; i < FOOTER_SIZE; i++) { - footer[i] = _footer[(int) ((_bytesReceivedAtCompletion + i) % FOOTER_SIZE)]; + footer[i] = _footer[(int) ((_bytesReceived + i) % FOOTER_SIZE)]; } } @@ -176,12 +178,12 @@ public class GunzipOutputStream extends InflaterOutputStream { long expectedSize = DataHelper.fromLongLE(footer, 4, 4); if (expectedSize != actualSize) throw new IOException("gunzip expected " + expectedSize + " bytes, got " + actualSize); - - long actualCRC = _crc32.getValue(); + + long actualCRC = ((CRC32OutputStream) out).getValue(); long expectedCRC = DataHelper.fromLongLE(footer, 0, 4); if (expectedCRC != actualCRC) throw new IOException("gunzip CRC fail expected 0x" + Long.toHexString(expectedCRC) + - " bytes, got 0x" + Long.toHexString(actualCRC)); + ", got 0x" + Long.toHexString(actualCRC)); } /** @@ -323,6 +325,36 @@ public class GunzipOutputStream extends InflaterOutputStream { } } + /** + * Calculate CRC32 along the way + * @since 0.9.60 + */ + private static class CRC32OutputStream extends FilterOutputStream { + + private final CRC32 _crc32; + + public CRC32OutputStream(OutputStream out) { + super(out); + _crc32 = new CRC32(); + } + + @Override + public void write(int c) throws IOException { + _crc32.update(c); + super.write(c); + } + + @Override + public void write(byte buf[], int off, int len) throws IOException { + _crc32.update(buf, off, len); + out.write(buf, off, len); + } + + public long getValue() { + return _crc32.getValue(); + } + } + /**** public static void main(String args[]) { java.util.Random r = new java.util.Random(); @@ -331,7 +363,7 @@ public class GunzipOutputStream extends InflaterOutputStream { r.nextBytes(b); if (!test(b)) return; } - for (int i = 1; i < 64*1024; i+= 29) { + for (int i = 1050; i < 64*1024; i+= 529) { byte[] b = new byte[i]; r.nextBytes(b); if (!test(b)) return;