Merge branch '458-gunzip' into 'master'

i2ptunnel: Fix gzip footer check in GunzipOutputStream (Gitlab #458)

Closes #458

See merge request i2p-hackers/i2p.i2p!134
This commit is contained in:
zzz
2023-10-28 15:40:55 +00:00

View File

@ -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;