i2ptunnel:

- Pass Accept-Encoding header through HTTP client and server proxies,
   to allow end-to-end compression
 - Don't do transparent response compression if response
   Content-Encoding indicates it is already compressed
 - Minor encoding cleanups
EepGet:
 - Send Accept-Encoding: gzip even when proxied
 - Minor cleanups
This commit is contained in:
zzz
2015-09-18 18:15:32 +00:00
parent 64889b2bc2
commit 1a385b6dca
4 changed files with 42 additions and 28 deletions

View File

@@ -39,7 +39,10 @@ class HTTPResponseOutputStream extends FilterOutputStream {
private final byte _buf1[];
protected boolean _gzip;
protected long _dataExpected;
/** lower-case, trimmed */
protected String _contentType;
/** lower-case, trimmed */
protected String _contentEncoding;
private static final int CACHE_SIZE = 8*1024;
private static final ByteCache _cache = ByteCache.getInstance(8, CACHE_SIZE);
@@ -151,7 +154,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
responseLine = (responseLine.trim() + "\r\n");
if (_log.shouldLog(Log.INFO))
_log.info("Response: " + responseLine.trim());
out.write(responseLine.getBytes());
out.write(DataHelper.getUTF8(responseLine));
} else {
for (int j = lastEnd+1; j < i; j++) {
if (_headerBuffer.getData()[j] == ':') {
@@ -160,7 +163,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
if ( (keyLen <= 0) || (valLen < 0) )
throw new IOException("Invalid header @ " + j);
String key = DataHelper.getUTF8(_headerBuffer.getData(), lastEnd+1, keyLen);
String val = null;
String val;
if (valLen == 0)
val = "";
else
@@ -171,10 +174,10 @@ class HTTPResponseOutputStream extends FilterOutputStream {
String lcKey = key.toLowerCase(Locale.US);
if ("connection".equals(lcKey)) {
out.write("Connection: close\r\n".getBytes());
out.write(DataHelper.getASCII("Connection: close\r\n"));
connectionSent = true;
} else if ("proxy-connection".equals(lcKey)) {
out.write("Proxy-Connection: close\r\n".getBytes());
out.write(DataHelper.getASCII("Proxy-Connection: close\r\n"));
proxyConnectionSent = true;
} else if ("content-encoding".equals(lcKey) && "x-i2p-gzip".equals(val.toLowerCase(Locale.US))) {
_gzip = true;
@@ -189,7 +192,10 @@ class HTTPResponseOutputStream extends FilterOutputStream {
} catch (NumberFormatException nfe) {}
} else if ("content-type".equals(lcKey)) {
// save for compress decision on server side
_contentType = val;
_contentType = val.toLowerCase(Locale.US);
} else if ("content-encoding".equals(lcKey)) {
// save for compress decision on server side
_contentEncoding = val.toLowerCase(Locale.US);
} else if ("set-cookie".equals(lcKey)) {
String lcVal = val.toLowerCase(Locale.US);
if (lcVal.contains("domain=b32.i2p") ||
@@ -203,7 +209,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
break;
}
}
out.write((key.trim() + ": " + val.trim() + "\r\n").getBytes());
out.write(DataHelper.getUTF8(key.trim() + ": " + val + "\r\n"));
}
break;
}
@@ -214,9 +220,9 @@ class HTTPResponseOutputStream extends FilterOutputStream {
}
if (!connectionSent)
out.write("Connection: close\r\n".getBytes());
out.write(DataHelper.getASCII("Connection: close\r\n"));
if (!proxyConnectionSent)
out.write("Proxy-Connection: close\r\n".getBytes());
out.write(DataHelper.getASCII("Proxy-Connection: close\r\n"));
finishHeaders();
@@ -237,7 +243,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
protected boolean shouldCompress() { return _gzip; }
protected void finishHeaders() throws IOException {
out.write("\r\n".getBytes()); // end of the headers
out.write(DataHelper.getASCII("\r\n")); // end of the headers
}
@Override

View File

@@ -881,7 +881,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
} else if(lowercaseLine.startsWith("accept")) {
// strip the accept-blah headers, as they vary dramatically from
// browser to browser
if(!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_ACCEPT))) {
// But allow Accept-Encoding: gzip, deflate
if(!lowercaseLine.startsWith("accept-encoding: ") &&
!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_ACCEPT))) {
line = null;
continue;
}
@@ -933,8 +935,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
// according to rfc2616 s14.3, this *should* force identity, even if
// an explicit q=0 for gzip doesn't. tested against orion.i2p, and it
// seems to work.
if(!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_ACCEPT)))
newRequest.append("Accept-Encoding: \r\n");
//if (!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_ACCEPT)))
// newRequest.append("Accept-Encoding: \r\n");
if (!usingInternalOutproxy)
newRequest.append("X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n");
}

View File

@@ -419,12 +419,14 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
setEntry(headers, "Connection", "close");
// we keep the enc sent by the browser before clobbering it, since it may have
// been x-i2p-gzip
String enc = getEntryOrNull(headers, "Accept-encoding");
String altEnc = getEntryOrNull(headers, "X-Accept-encoding");
String enc = getEntryOrNull(headers, "Accept-Encoding");
String altEnc = getEntryOrNull(headers, "X-Accept-Encoding");
// according to rfc2616 s14.3, this *should* force identity, even if
// "identity;q=1, *;q=0" didn't.
setEntry(headers, "Accept-encoding", "");
// as of 0.9.23, the client passes this header through, and we do the same,
// so if the server and browser can do the compression/decompression, we don't have to
//setEntry(headers, "Accept-Encoding", "");
socket.setReadTimeout(readTimeout);
Socket s = getSocket(socket.getPeerDestination().calculateHash(), socket.getLocalPort());
@@ -432,7 +434,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
// instead of i2ptunnelrunner, use something that reads the HTTP
// request from the socket, modifies the headers, sends the request to the
// server, reads the response headers, rewriting to include Content-encoding: x-i2p-gzip
// if it was one of the Accept-encoding: values, and gzip the payload
// if it was one of the Accept-Encoding: values, and gzip the payload
boolean allowGZIP = true;
String val = opts.getProperty("i2ptunnel.gzip");
if ( (val != null) && (!Boolean.parseBoolean(val)) )
@@ -443,7 +445,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
boolean useGZIP = alt || ( (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) );
// Don't pass this on, outproxies should strip so I2P traffic isn't so obvious but they probably don't
if (alt)
headers.remove("X-Accept-encoding");
headers.remove("X-Accept-Encoding");
String modifiedHeader = formatHeaders(headers, command);
if (_log.shouldLog(Log.DEBUG))
@@ -671,6 +673,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
/**
* Don't compress small responses or images.
* Don't compress things that are already compressed.
* Compression is inline but decompression on the client side
* creates a new thread.
*/
@@ -687,7 +690,11 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
(!_contentType.equals("application/x-bzip")) &&
(!_contentType.equals("application/x-bzip2")) &&
(!_contentType.equals("application/x-gzip")) &&
(!_contentType.equals("application/zip"))));
(!_contentType.equals("application/zip")))) &&
(_contentEncoding == null ||
((!_contentEncoding.equals("gzip")) &&
(!_contentEncoding.equals("compress")) &&
(!_contentEncoding.equals("deflate"))));
}
@Override
@@ -877,9 +884,9 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
String lcName = name.toLowerCase(Locale.US);
if ("accept-encoding".equals(lcName))
name = "Accept-encoding";
name = "Accept-Encoding";
else if ("x-accept-encoding".equals(lcName))
name = "X-Accept-encoding";
name = "X-Accept-Encoding";
else if ("x-forwarded-for".equals(lcName))
name = "X-Forwarded-For";
else if ("x-forwarded-server".equals(lcName))

View File

@@ -755,6 +755,8 @@ public class EepGet {
Thread pusher = null;
_decompressException = null;
if (_isGzippedResponse) {
if (_log.shouldInfo())
_log.info("Gzipped response, starting decompressor");
PipedInputStream pi = BigPipedInputStream.getInstance();
PipedOutputStream po = new PipedOutputStream(pi);
pusher = new I2PAppThread(new Gunzipper(pi, _out), "EepGet Decompressor");
@@ -1160,17 +1162,13 @@ public class EepGet {
lookahead[1] = lookahead[2];
lookahead[2] = (byte)cur;
}
private static boolean isEndOfHeaders(byte lookahead[]) {
byte first = lookahead[0];
byte second = lookahead[1];
byte third = lookahead[2];
return (isNL(second) && isNL(third)) || // \n\n
(isNL(first) && isNL(third)); // \n\r\n
return lookahead[2] == NL &&
(lookahead[0] == NL || lookahead[1] == NL); // \n\n or \n\r\n
}
/** we ignore any potential \r, since we trim it on write anyway */
private static final byte NL = '\n';
private static boolean isNL(byte b) { return (b == NL); }
/**
* @param timeout may be null
@@ -1315,7 +1313,8 @@ public class EepGet {
buf.append("Content-length: ").append(_postData.length()).append("\r\n");
// This will be replaced if we are going through I2PTunnelHTTPClient
buf.append("Accept-Encoding: ");
if ((!_shouldProxy) &&
// as of 0.9.23, the proxy passes the Accept-Encoding header through
if ( /* (!_shouldProxy) && */
// This is kindof a hack, but if we are downloading a gzip file
// we don't want to transparently gunzip it and save it as a .gz file.
(!path.endsWith(".gz")) && (!path.endsWith(".tgz")))