forked from I2P_Developers/i2p.i2p
Merge branch 'i2ptunnel-keepalive-util' into 'master'
i2ptunnel: Prep for keepalive See merge request i2p-hackers/i2p.i2p!166
This commit is contained in:
@@ -16,6 +16,7 @@ import java.util.Locale;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.i2ptunnel.util.GunzipOutputStream;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.i2ptunnel.GunzipOutputStream;
|
||||
import net.i2p.i2ptunnel.util.GunzipOutputStream;
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.PortMapper;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package net.i2p.i2ptunnel.util;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* An OutputStream that limits how many bytes are written
|
||||
*
|
||||
* @since 0.9.62
|
||||
*/
|
||||
public class ByteLimitOutputStream extends LimitOutputStream {
|
||||
|
||||
private final long _limit;
|
||||
private long _count;
|
||||
|
||||
/**
|
||||
* @param limit greater than zero
|
||||
*/
|
||||
public ByteLimitOutputStream(OutputStream out, DoneCallback done, long limit) {
|
||||
super(out, done);
|
||||
if (limit <= 0)
|
||||
throw new IllegalArgumentException();
|
||||
_limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte src[], int off, int len) throws IOException {
|
||||
if (len == 0)
|
||||
return;
|
||||
if (_isDone)
|
||||
throw new EOFException("done");
|
||||
long togo = _limit - _count;
|
||||
boolean last = len >= togo;
|
||||
if (last)
|
||||
len = (int) togo;
|
||||
super.write(src, off, len);
|
||||
_count += len;
|
||||
if (last)
|
||||
setDone();
|
||||
}
|
||||
|
||||
/*
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length != 1) {
|
||||
System.err.println("Usage: ByteLimitOutputStream length < in > out");
|
||||
System.exit(1);
|
||||
}
|
||||
Test test = new Test();
|
||||
long limit = Long.parseLong(args[0]);
|
||||
test.test(limit);
|
||||
}
|
||||
|
||||
static class Test implements DoneCallback {
|
||||
private boolean run = true;
|
||||
|
||||
public void test(long limit) throws Exception {
|
||||
LimitOutputStream lout = new ByteLimitOutputStream(System.out, this, limit);
|
||||
final byte buf[] = new byte[4096];
|
||||
try {
|
||||
int read;
|
||||
while (run && (read = System.in.read(buf)) != -1) {
|
||||
lout.write(buf, 0, read);
|
||||
}
|
||||
} finally {
|
||||
lout.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void streamDone() {
|
||||
System.err.println("Done");
|
||||
run = false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package net.i2p.i2ptunnel.util;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* Simple stream for checking and optionally removing RFC2616 chunked encoding to the output.
|
||||
*
|
||||
* @since 0.9.62
|
||||
*/
|
||||
public class DechunkedOutputStream extends LimitOutputStream {
|
||||
private final boolean _strip;
|
||||
private State _state = State.LEN;
|
||||
// During main part, how much is remaining in the chunk
|
||||
// During the trailer, counts the trailer header size
|
||||
private int _remaining;
|
||||
|
||||
private static final byte[] CRLF = DataHelper.getASCII("\r\n");
|
||||
|
||||
private enum State { LEN, CR, LF, DATA, TRAILER, DONE }
|
||||
|
||||
public DechunkedOutputStream(OutputStream raw, DoneCallback callback, boolean strip) {
|
||||
super(raw, callback);
|
||||
_strip = strip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte buf[], int off, int len) throws IOException {
|
||||
if (len <= 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
// _state is what we are expecting next
|
||||
//System.err.println("State: " + _state + " i=" + i + " len=" + len + " remaining=" + _remaining + " char=0x" + Integer.toHexString(buf[off + i] & 0xff));
|
||||
switch (_state) {
|
||||
// collect chunk len and possible ';' then wait for extension if any and CRLF
|
||||
case LEN: {
|
||||
int c = buf[off + i] & 0xff;
|
||||
if (c >= '0' && c <= '9') {
|
||||
if (_remaining >= 0x8000000)
|
||||
throw new IOException("Chunk length too big");
|
||||
_remaining <<= 4;
|
||||
_remaining |= c - '0';
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
if (_remaining >= 0x800000)
|
||||
throw new IOException("Chunk length too big");
|
||||
_remaining <<= 4;
|
||||
_remaining |= 10 + c - 'a';
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
if (_remaining >= 0x800000)
|
||||
throw new IOException("Chunk length too big");
|
||||
_remaining <<= 4;
|
||||
_remaining |= 10 + c - 'A';
|
||||
} else if (c == ';') {
|
||||
_state = State.CR;
|
||||
} else if (c == '\r') {
|
||||
_state = State.LF;
|
||||
} else if (c == '\n') {
|
||||
if (_remaining > 0)
|
||||
_state = State.DATA;
|
||||
else
|
||||
_state = State.TRAILER;
|
||||
} else {
|
||||
throw new IOException("Unexpected length char 0x" + Integer.toHexString(c));
|
||||
}
|
||||
if (!_strip)
|
||||
out.write(buf, off + i, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// collect any chunk extension and CR then wait for LF
|
||||
case CR: {
|
||||
int c = buf[off + i] & 0xff;
|
||||
if (c == '\r') {
|
||||
_state = State.LF;
|
||||
} else if (c == '\n') {
|
||||
if (_remaining > 0)
|
||||
_state = State.DATA;
|
||||
else
|
||||
_state = State.TRAILER;
|
||||
} else {
|
||||
// chunk extension between the ';' and the CR
|
||||
}
|
||||
if (!_strip)
|
||||
out.write(buf, off + i, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// collect LF then wait for DATA
|
||||
case LF: {
|
||||
int c = buf[off + i] & 0xff;
|
||||
if (c == '\n') {
|
||||
if (_remaining > 0)
|
||||
_state = State.DATA;
|
||||
else
|
||||
_state = State.TRAILER;
|
||||
} else {
|
||||
throw new IOException("no LF after CR");
|
||||
}
|
||||
if (!_strip)
|
||||
out.write(buf, off + i, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// collect DATA then wait for LEN
|
||||
case DATA: {
|
||||
int towrite = Math.min(_remaining, len - i);
|
||||
out.write(buf, off + i, towrite);
|
||||
// loop will increment
|
||||
i += towrite - 1;
|
||||
_remaining -= towrite;
|
||||
if (_remaining <= 0)
|
||||
_state = State.LEN;
|
||||
break;
|
||||
}
|
||||
|
||||
// swallow and discard the Trailer headers until we find a plain CRLF
|
||||
// we reuse _remaining here to count the size of the header
|
||||
case TRAILER: {
|
||||
int c = buf[off + i] & 0xff;
|
||||
if (c == '\r') {
|
||||
// stay here
|
||||
} else if (c == '\n') {
|
||||
if (_remaining <= 0) {
|
||||
// that's it!
|
||||
if (!_strip)
|
||||
out.write(buf, off + i, 1);
|
||||
_state = State.DONE;
|
||||
setDone();
|
||||
return;
|
||||
} else {
|
||||
// stay here
|
||||
_remaining = 0;
|
||||
}
|
||||
} else {
|
||||
_remaining++;
|
||||
}
|
||||
if (!_strip)
|
||||
out.write(buf, off + i, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case DONE: {
|
||||
throw new EOFException((len - i) + " extra bytes written after chunking done");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length != 1) {
|
||||
System.err.println("Usage: DechunkedOutputStream true/false < in > out");
|
||||
System.exit(1);
|
||||
}
|
||||
Test test = new Test();
|
||||
boolean strip = Boolean.parseBoolean(args[0]);
|
||||
test.test(strip);
|
||||
}
|
||||
|
||||
static class Test implements DoneCallback {
|
||||
private boolean run = true;
|
||||
|
||||
public void test(boolean strip) throws Exception {
|
||||
LimitOutputStream cout = new DechunkedOutputStream(System.out, this, strip);
|
||||
final byte buf[] = new byte[4096];
|
||||
try {
|
||||
int read;
|
||||
while (run && (read = System.in.read(buf)) != -1) {
|
||||
cout.write(buf, 0, read);
|
||||
}
|
||||
} finally {
|
||||
cout.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void streamDone() {
|
||||
System.err.println("Done");
|
||||
run = false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package net.i2p.i2ptunnel.util;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Write to nowhere
|
||||
*
|
||||
* @since 0.9.62 copied from susimail
|
||||
*/
|
||||
public class DummyOutputStream extends OutputStream {
|
||||
|
||||
public void write(int val) {}
|
||||
|
||||
@Override
|
||||
public void write(byte src[]) {}
|
||||
|
||||
@Override
|
||||
public void write(byte src[], int off, int len) {}
|
||||
|
||||
@Override
|
||||
public void flush() {}
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
package net.i2p.i2ptunnel.util;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -7,7 +8,10 @@ import java.util.zip.CRC32;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterOutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.i2ptunnel.util.LimitOutputStream.DoneCallback;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Gunzip implementation per
|
||||
@@ -22,7 +26,7 @@ import net.i2p.data.DataHelper;
|
||||
* 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
|
||||
* @since 0.9.21, public since 0.9.50 for LocalHTTPServer, moved to util in 0.9.62
|
||||
*/
|
||||
public class GunzipOutputStream extends InflaterOutputStream {
|
||||
private static final int FOOTER_SIZE = 8; // CRC32 + ISIZE
|
||||
@@ -38,12 +42,28 @@ public class GunzipOutputStream extends InflaterOutputStream {
|
||||
private HeaderState _state = HeaderState.MB1;
|
||||
private int _flags;
|
||||
private int _extHdrToRead;
|
||||
private final DoneCallback _callback;
|
||||
private final Log _log;
|
||||
|
||||
private static final OutputStream DUMMY_OUT = new DummyOutputStream();
|
||||
|
||||
/**
|
||||
* Build a new Gunzip stream
|
||||
*/
|
||||
public GunzipOutputStream(OutputStream uncompressedStream) throws IOException {
|
||||
this(uncompressedStream, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* With a callback when done
|
||||
*
|
||||
* @param cb may be null
|
||||
* @since 0.9.62
|
||||
*/
|
||||
public GunzipOutputStream(OutputStream uncompressedStream, DoneCallback cb) throws IOException {
|
||||
super(new CRC32OutputStream(uncompressedStream), new Inflater(true));
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(GunzipOutputStream.class);
|
||||
_callback = cb;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,7 +77,10 @@ public class GunzipOutputStream extends InflaterOutputStream {
|
||||
if (_complete) {
|
||||
// shortcircuit so the inflater doesn't try to refill
|
||||
// with the footer's data (which would fail, causing ZLIB err)
|
||||
return;
|
||||
IOException ioe = new EOFException("Extra data written to gunzipper");
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("EOF", ioe);
|
||||
throw ioe;
|
||||
}
|
||||
boolean isFinished = inf.finished();
|
||||
for (int i = off; i < off + len; i++) {
|
||||
@@ -85,6 +108,8 @@ public class GunzipOutputStream extends InflaterOutputStream {
|
||||
verifyFooter();
|
||||
_complete = true;
|
||||
_validated = true;
|
||||
if (_callback != null)
|
||||
_callback.streamDone();
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
// failed at 7, retry at 8
|
||||
@@ -147,6 +172,8 @@ public class GunzipOutputStream extends InflaterOutputStream {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Closing " + this);
|
||||
_complete = true;
|
||||
_state = HeaderState.DONE;
|
||||
super.close();
|
||||
@@ -0,0 +1,58 @@
|
||||
package net.i2p.i2ptunnel.util;
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Base class for limiting writes and calling a callback when finished
|
||||
*
|
||||
* @since 0.9.62
|
||||
*/
|
||||
public abstract class LimitOutputStream extends FilterOutputStream {
|
||||
|
||||
private final byte _buf1[];
|
||||
protected final DoneCallback _callback;
|
||||
protected boolean _isDone;
|
||||
|
||||
public interface DoneCallback { public void streamDone(); }
|
||||
|
||||
/**
|
||||
* @param done non-null
|
||||
*/
|
||||
public LimitOutputStream(OutputStream out, DoneCallback done) {
|
||||
super(out);
|
||||
_callback = done;
|
||||
_buf1 = new byte[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int c) throws IOException {
|
||||
_buf1[0] = (byte)c;
|
||||
write(_buf1, 0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses MUST override the following method
|
||||
* such that it calls done() when finished
|
||||
* and throws EOFException if called again
|
||||
*/
|
||||
@Override
|
||||
public void write(byte buf[], int off, int len) throws IOException {
|
||||
out.write(buf, off, len);
|
||||
}
|
||||
|
||||
|
||||
protected boolean isDone() { return _isDone; }
|
||||
|
||||
/**
|
||||
* flush(), call the callback, and set _isDone
|
||||
*/
|
||||
protected void setDone() throws IOException {
|
||||
if (_isDone)
|
||||
throw new IllegalStateException("already done");
|
||||
flush();
|
||||
_callback.streamDone();
|
||||
_isDone = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
HTTP utilities.
|
||||
Not for external use; not maintained as a stable API.
|
||||
Since 0.9.62
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user