propagate from branch 'i2p.i2p.zzz.test2' (head b6de226d1664089488ab2b438fe7457e9fb8e563)

to branch 'i2p.i2p' (head 0cf35c87b68a5360bd35257e36dfe7f740e53693)
This commit is contained in:
zzz
2015-04-17 13:18:22 +00:00
18 changed files with 775 additions and 371 deletions

View File

@@ -71,11 +71,16 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
private static final long TOTAL_HEADER_TIMEOUT = 2 * HEADER_TIMEOUT;
private static final long START_INTERVAL = (60 * 1000) * 3;
private static final int MAX_LINE_LENGTH = 8*1024;
/** ridiculously long, just to prevent OOM DOS @since 0.7.13 */
private static final int MAX_HEADERS = 60;
/** Includes request, just to prevent OOM DOS @since 0.9.20 */
private static final int MAX_TOTAL_HEADER_SIZE = 32*1024;
private long _startedOn = 0L;
private ConnThrottler _postThrottler;
private final static byte[] ERR_UNAVAILABLE =
("HTTP/1.1 503 Service Unavailable\r\n"+
private final static String ERR_UNAVAILABLE =
"HTTP/1.1 503 Service Unavailable\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"Connection: close\r\n"+
@@ -84,11 +89,10 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
"<html><head><title>503 Service Unavailable</title></head>\n"+
"<body><h2>503 Service Unavailable</h2>\n" +
"<p>This I2P website is unavailable. It may be down or undergoing maintenance.</p>\n" +
"</body></html>")
.getBytes();
"</body></html>";
private final static byte[] ERR_DENIED =
("HTTP/1.1 403 Denied\r\n"+
private final static String ERR_DENIED =
"HTTP/1.1 403 Denied\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"Connection: close\r\n"+
@@ -97,11 +101,10 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
"<html><head><title>403 Denied</title></head>\n"+
"<body><h2>403 Denied</h2>\n" +
"<p>Denied due to excessive requests. Please try again later.</p>\n" +
"</body></html>")
.getBytes();
"</body></html>";
private final static byte[] ERR_INPROXY =
("HTTP/1.1 403 Denied\r\n"+
private final static String ERR_INPROXY =
"HTTP/1.1 403 Denied\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"Connection: close\r\n"+
@@ -110,8 +113,64 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
"<html><head><title>403 Denied</title></head>\n"+
"<body><h2>403 Denied</h2>\n" +
"<p>Inproxy access denied. You must run <a href=\"https://geti2p.net/\">I2P</a> to access this site.</p>\n" +
"</body></html>")
.getBytes();
"</body></html>";
private final static String ERR_SSL =
"HTTP/1.1 503 Service Unavailable\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"Connection: close\r\n"+
"Proxy-Connection: close\r\n"+
"\r\n"+
"<html><head><title>503 Service Unavailable</title></head>\n"+
"<body><h2>503 Service Unavailable</h2>\n" +
"<p>This I2P website is not configured for SSL.</p>\n" +
"</body></html>";
private final static String ERR_REQUEST_URI_TOO_LONG =
"HTTP/1.1 414 Request URI too long\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"Connection: close\r\n"+
"Proxy-Connection: close\r\n"+
"\r\n"+
"<html><head><title>414 Request URI Too Long</title></head>\n"+
"<body><h2>414 Request URI too long</h2>\n" +
"</body></html>";
private final static String ERR_HEADERS_TOO_LARGE =
"HTTP/1.1 431 Request header fields too large\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"Connection: close\r\n"+
"Proxy-Connection: close\r\n"+
"\r\n"+
"<html><head><title>431 Request Header Fields Too Large</title></head>\n"+
"<body><h2>431 Request header fields too large</h2>\n" +
"</body></html>";
private final static String ERR_REQUEST_TIMEOUT =
"HTTP/1.1 408 Request timeout\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"Connection: close\r\n"+
"Proxy-Connection: close\r\n"+
"\r\n"+
"<html><head><title>408 Request Timeout</title></head>\n"+
"<body><h2>408 Request timeout</h2>\n" +
"</body></html>";
private final static String ERR_BAD_REQUEST =
"HTTP/1.1 400 Bad Request\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"Connection: close\r\n"+
"Proxy-Connection: close\r\n"+
"\r\n"+
"<html><head><title>400 Bad Request</title></head>\n"+
"<body><h2>400 Bad request</h2>\n" +
"</body></html>";
public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privData, l, notifyThis, tunnel);
@@ -131,7 +190,6 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
private void setupI2PTunnelHTTPServer(String spoofHost) {
_spoofHost = (spoofHost != null && spoofHost.trim().length() > 0) ? spoofHost.trim() : null;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000 });
}
@Override
@@ -208,13 +266,88 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
//local is fast, so synchronously. Does not need that many
//threads.
try {
if (socket.getLocalPort() == 443) {
if (getTunnel().getClientOptions().getProperty("targetForPort.443") == null) {
try {
socket.getOutputStream().write(ERR_SSL.getBytes("UTF-8"));
} catch (IOException ioe) {
} finally {
try {
socket.close();
} catch (IOException ioe) {}
}
return;
}
Socket s = getSocket(socket.getPeerDestination().calculateHash(), 443);
Runnable t = new I2PTunnelRunner(s, socket, slock, null, null,
null, (I2PTunnelRunner.FailCallback) null);
_clientExecutor.execute(t);
return;
}
long afterAccept = getTunnel().getContext().clock().now();
// The headers _should_ be in the first packet, but
// may not be, depending on the client-side options
StringBuilder command = new StringBuilder(128);
Map<String, List<String>> headers = readHeaders(socket, null, command,
CLIENT_SKIPHEADERS, getTunnel().getContext());
Map<String, List<String>> headers;
try {
// catch specific exceptions thrown, to return a good
// error to the client
headers = readHeaders(socket, null, command,
CLIENT_SKIPHEADERS, getTunnel().getContext());
} catch (SocketTimeoutException ste) {
try {
socket.getOutputStream().write(ERR_REQUEST_TIMEOUT.getBytes("UTF-8"));
} catch (IOException ioe) {
} finally {
try { socket.close(); } catch (IOException ioe) {}
}
if (_log.shouldLog(Log.WARN))
_log.warn("Error while receiving the new HTTP request", ste);
return;
} catch (EOFException eofe) {
try {
socket.getOutputStream().write(ERR_BAD_REQUEST.getBytes("UTF-8"));
} catch (IOException ioe) {
} finally {
try { socket.close(); } catch (IOException ioe) {}
}
if (_log.shouldLog(Log.WARN))
_log.warn("Error while receiving the new HTTP request", eofe);
return;
} catch (LineTooLongException ltle) {
try {
socket.getOutputStream().write(ERR_HEADERS_TOO_LARGE.getBytes("UTF-8"));
} catch (IOException ioe) {
} finally {
try { socket.close(); } catch (IOException ioe) {}
}
if (_log.shouldLog(Log.WARN))
_log.warn("Error while receiving the new HTTP request", ltle);
return;
} catch (RequestTooLongException rtle) {
try {
socket.getOutputStream().write(ERR_REQUEST_URI_TOO_LONG.getBytes("UTF-8"));
} catch (IOException ioe) {
} finally {
try { socket.close(); } catch (IOException ioe) {}
}
if (_log.shouldLog(Log.WARN))
_log.warn("Error while receiving the new HTTP request", rtle);
return;
} catch (BadRequestException bre) {
try {
socket.getOutputStream().write(ERR_BAD_REQUEST.getBytes("UTF-8"));
} catch (IOException ioe) {
} finally {
try { socket.close(); } catch (IOException ioe) {}
}
if (_log.shouldLog(Log.WARN))
_log.warn("Error while receiving the new HTTP request", bre);
return;
}
long afterHeaders = getTunnel().getContext().clock().now();
Properties opts = getTunnel().getClientOptions();
@@ -239,7 +372,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
try {
// Send a 403, so the user doesn't get an HTTP Proxy error message
// and blame his router or the network.
socket.getOutputStream().write(ERR_INPROXY);
socket.getOutputStream().write(ERR_INPROXY.getBytes("UTF-8"));
} catch (IOException ioe) {}
try {
socket.close();
@@ -256,7 +389,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
try {
// Send a 403, so the user doesn't get an HTTP Proxy error message
// and blame his router or the network.
socket.getOutputStream().write(ERR_DENIED);
socket.getOutputStream().write(ERR_DENIED.getBytes("UTF-8"));
} catch (IOException ioe) {}
try {
socket.close();
@@ -341,7 +474,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
try {
// Send a 503, so the user doesn't get an HTTP Proxy error message
// and blame his router or the network.
socket.getOutputStream().write(ERR_UNAVAILABLE);
socket.getOutputStream().write(ERR_UNAVAILABLE.getBytes("UTF-8"));
} catch (IOException ioe) {}
try {
socket.close();
@@ -362,7 +495,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
try {
// Send a 503, so the user doesn't get an HTTP Proxy error message
// and blame his router or the network.
socket.getOutputStream().write(ERR_UNAVAILABLE);
socket.getOutputStream().write(ERR_UNAVAILABLE.getBytes("UTF-8"));
} catch (IOException ioe) {}
try {
socket.close();
@@ -453,7 +586,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
try {
if (browserout == null)
browserout = _browser.getOutputStream();
browserout.write(ERR_UNAVAILABLE);
browserout.write(ERR_UNAVAILABLE.getBytes("UTF-8"));
} catch (IOException ioe) {}
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
@@ -633,9 +766,6 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
return buf.toString();
}
/** ridiculously long, just to prevent OOM DOS @since 0.7.13 */
private static final int MAX_HEADERS = 60;
/**
* Add an entry to the multimap.
*/
@@ -680,10 +810,11 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
* @param socket if null, use in as InputStream
* @param in if null, use socket.getInputStream() as InputStream
* @param command out parameter, first line
* @param command out parameter, first line
* @throws SocketTimeoutException if timeout is reached before newline
* @throws EOFException if EOF is reached before newline
* @throws LineTooLongException if too long
* @throws LineTooLongException if one header too long, or too many headers, or total size too big
* @throws RequestTooLongException if too long
* @throws BadRequestException on bad headers
* @throws IOException on other errors in the underlying stream
*/
private static Map<String, List<String>> readHeaders(I2PSocket socket, InputStream in, StringBuilder command,
@@ -694,51 +825,49 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
// slowloris / darkloris
long expire = ctx.clock().now() + TOTAL_HEADER_TIMEOUT;
if (socket != null) {
readLine(socket, command, HEADER_TIMEOUT);
try {
readLine(socket, command, HEADER_TIMEOUT);
} catch (LineTooLongException ltle) {
// convert for first line
throw new RequestTooLongException("Request too long - max " + MAX_LINE_LENGTH);
}
} else {
boolean ok = DataHelper.readLine(in, command);
if (!ok)
throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
throw new EOFException("EOF reached before the end of the headers");
}
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Read the http command [" + command.toString() + "]");
// FIXME we probably don't need or want this in the outgoing direction
int trimmed = 0;
if (command.length() > 0) {
for (int i = 0; i < command.length(); i++) {
if (command.charAt(i) == 0) {
command = command.deleteCharAt(i);
i--;
trimmed++;
}
}
}
if (trimmed > 0)
ctx.statManager().addRateData("i2ptunnel.httpNullWorkaround", trimmed);
int totalSize = command.length();
int i = 0;
while (true) {
if (++i > MAX_HEADERS)
throw new IOException("Too many header lines - max " + MAX_HEADERS);
if (++i > MAX_HEADERS) {
throw new LineTooLongException("Too many header lines - max " + MAX_HEADERS);
}
buf.setLength(0);
if (socket != null) {
readLine(socket, buf, expire - ctx.clock().now());
} else {
boolean ok = DataHelper.readLine(in, buf);
if (!ok)
throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
throw new BadRequestException("EOF reached before the end of the headers");
}
if ( (buf.length() == 0) ||
((buf.charAt(0) == '\n') || (buf.charAt(0) == '\r')) ) {
// end of headers reached
return headers;
} else {
if (ctx.clock().now() >= expire)
throw new IOException("Headers took too long [" + buf.toString() + "]");
if (ctx.clock().now() > expire) {
throw new SocketTimeoutException("Headers took too long");
}
int split = buf.indexOf(":");
if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]");
if (split <= 0)
throw new BadRequestException("Invalid HTTP header, missing colon");
totalSize += buf.length();
if (totalSize > MAX_TOTAL_HEADER_SIZE)
throw new LineTooLongException("Req+headers too big");
String name = buf.substring(0, split).trim();
String value = null;
if (buf.length() > split + 1)
@@ -831,5 +960,23 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
super(s);
}
}
/**
* @since 0.9.20
*/
private static class RequestTooLongException extends IOException {
public RequestTooLongException(String s) {
super(s);
}
}
/**
* @since 0.9.20
*/
private static class BadRequestException extends IOException {
public BadRequestException(String s) {
super(s);
}
}
}