diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index 0baa40c63..d523883f8 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -122,6 +122,51 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { "

This I2P website is not configured for SSL.

\n" + ""; + 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"+ + "414 Request URI Too Long\n"+ + "

414 Request URI too long

\n" + + ""; + + 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"+ + "431 Request Header Fields Too Large\n"+ + "

431 Request header fields too large

\n" + + ""; + + 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"+ + "408 Request Timeout\n"+ + "

408 Request timeout

\n" + + ""; + + 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"+ + "400 Bad Request\n"+ + "

400 Bad request

\n" + + ""; + + public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { super(host, port, privData, l, notifyThis, tunnel); setupI2PTunnelHTTPServer(spoofHost); @@ -242,8 +287,63 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { // may not be, depending on the client-side options StringBuilder command = new StringBuilder(128); - Map> headers = readHeaders(socket, null, command, - CLIENT_SKIPHEADERS, getTunnel().getContext()); + Map> 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(); @@ -709,10 +809,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 + * @throws RequestTooLongException if too long + * @throws BadRequestException on bad headers * @throws IOException on other errors in the underlying stream */ private static Map> readHeaders(I2PSocket socket, InputStream in, StringBuilder command, @@ -723,11 +824,16 @@ 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 [" + buf.toString() + "]"); } //if (_log.shouldLog(Log.DEBUG)) @@ -749,25 +855,27 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { 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 [" + buf.toString() + "]"); } 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 [" + buf.toString() + "]"); + } 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 [" + buf.toString() + "]"); String name = buf.substring(0, split).trim(); String value = null; if (buf.length() > split + 1) @@ -860,5 +968,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); + } + } }