diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java index 8adb79bb6..2c448d946 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java @@ -122,7 +122,9 @@ public class I2PTunnelClient extends I2PTunnelClientBase { int port = addr.getPort(); i2ps = createI2PSocket(clientDest, port); i2ps.setReadTimeout(readTimeout); - new I2PTunnelRunner(s, i2ps, sockLock, null, mySockets); + Thread t = new I2PTunnelRunner(s, i2ps, sockLock, null, null, mySockets, + (I2PTunnelRunner.FailCallback) null); + t.start(); } catch (Exception ex) { if (_log.shouldLog(Log.INFO)) _log.info("Error connecting", ex); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java index d56d0787c..45e530e6e 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java @@ -59,18 +59,6 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R public static final String AUTH_REALM = "I2P SSL Proxy"; - private final static byte[] ERR_DESTINATION_UNKNOWN = - ("HTTP/1.1 503 Service Unavailable\r\n"+ - "Content-Type: text/html; charset=iso-8859-1\r\n"+ - "Cache-control: no-cache\r\n"+ - "\r\n"+ - "

I2P ERROR: DESTINATION NOT FOUND

"+ - "That I2P Destination was not found. "+ - "The host (or the outproxy, if you're using one) could also "+ - "be temporarily offline. You may want to retry. "+ - "Could not find the following Destination:

") - .getBytes(); - private final static byte[] ERR_BAD_PROTOCOL = ("HTTP/1.1 405 Bad Method\r\n"+ "Content-Type: text/html; charset=iso-8859-1\r\n"+ @@ -293,9 +281,9 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R if (clientDest == null) { byte[] header; if (usingWWWProxy) - header = getErrorPage("dnfp-header.ht", ERR_DESTINATION_UNKNOWN); + header = getErrorPage("dnfp", ERR_DESTINATION_UNKNOWN); else - header = getErrorPage("dnfh-header.ht", ERR_DESTINATION_UNKNOWN); + header = getErrorPage("dnfh", ERR_DESTINATION_UNKNOWN); writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination); s.close(); return; @@ -308,89 +296,29 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R data = newRequest.toString().getBytes("ISO-8859-1"); else response = SUCCESS_RESPONSE; - Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId); - // starts itself - new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout); - } catch (SocketException ex) { - _log.info(getPrefix(requestId) + "Error trying to connect", ex); - handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); - closeSocket(s); + OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId); + Thread t = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout); + t.start(); } catch (IOException ex) { _log.info(getPrefix(requestId) + "Error trying to connect", ex); - handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); + handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); closeSocket(s); } catch (I2PException ex) { _log.info("getPrefix(requestId) + Error trying to connect", ex); - handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); + handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); closeSocket(s); } catch (OutOfMemoryError oom) { IOException ex = new IOException("OOM"); _log.info("getPrefix(requestId) + Error trying to connect", ex); - handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); + handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); closeSocket(s); } } - private static class OnTimeout implements Runnable { - private final Socket _socket; - private final OutputStream _out; - private final String _target; - private final boolean _usingProxy; - private final String _wwwProxy; - private final long _requestId; - - public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) { - _socket = s; - _out = out; - _target = target; - _usingProxy = usingProxy; - _wwwProxy = wwwProxy; - _requestId = id; - } - - public void run() { - //if (_log.shouldLog(Log.DEBUG)) - // _log.debug("Timeout occured requesting " + _target); - handleConnectClientException(new RuntimeException("Timeout"), _out, - _target, _usingProxy, _wwwProxy, _requestId); - closeSocket(_socket); - } - } - private static void writeErrorMessage(byte[] errMessage, OutputStream out) throws IOException { if (out == null) return; out.write(errMessage); - out.write("\n\n".getBytes()); - out.flush(); - } - - private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest, - boolean usingWWWProxy, String wwwProxy) throws IOException { - if (out != null) { - out.write(errMessage); - if (targetRequest != null) { - out.write(targetRequest.getBytes()); - if (usingWWWProxy) - out.write(("
WWW proxy: " + wwwProxy).getBytes()); - } - out.write("
".getBytes()); - out.write("\n\n".getBytes()); - out.flush(); - } - } - - private static void handleConnectClientException(Exception ex, OutputStream out, String targetRequest, - boolean usingWWWProxy, String wwwProxy, long requestId) { - if (out == null) - return; - byte[] header; - if (usingWWWProxy) - header = getErrorPage(I2PAppContext.getGlobalContext(), "dnfp-header.ht", ERR_DESTINATION_UNKNOWN); - else - header = getErrorPage(I2PAppContext.getGlobalContext(), "dnf-header.ht", ERR_DESTINATION_UNKNOWN); - try { - writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy); - } catch (IOException ioe) {} + writeFooter(out); } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index 8706edbae..966a9bee7 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -10,7 +10,6 @@ import java.net.Socket; import java.net.SocketException; import java.net.URI; import java.net.URISyntaxException; -import java.util.Date; import java.util.Locale; import java.util.Properties; import java.util.StringTokenizer; @@ -35,7 +34,6 @@ import net.i2p.i2ptunnel.localServer.LocalHTTPServer; import net.i2p.util.EventDispatcher; import net.i2p.util.Log; import net.i2p.util.PortMapper; -import net.i2p.util.Translate; /** * Act as a mini HTTP proxy, handling various different types of requests, @@ -93,17 +91,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn "\r\n" + "

I2P ERROR: REQUEST DENIED

" + "You attempted to connect to a non-I2P website or location.
").getBytes(); - private final static byte[] ERR_DESTINATION_UNKNOWN = - ("HTTP/1.1 503 Service Unavailable\r\n" + - "Content-Type: text/html; charset=iso-8859-1\r\n" + - "Cache-control: no-cache\r\n" + - "\r\n" + - "

I2P ERROR: DESTINATION NOT FOUND

" + - "That I2P Destination was not found. Perhaps you pasted in the " + - "wrong BASE64 I2P Destination or the link you are following is " + - "bad. The host (or the WWW proxy, if you're using one) could also " + - "be temporarily offline. You may want to retry. " + - "Could not find the following Destination:

").getBytes(); + /***** private final static byte[] ERR_TIMEOUT = ("HTTP/1.1 504 Gateway Timeout\r\n"+ @@ -642,7 +630,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn if(alias.equals("i2p")) { // bad ahelperKey byte[] header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN); - writeErrorMessage(header, out, targetRequest, false, destination, null); + writeErrorMessage(header, out, targetRequest, false, destination); } else { String trustedURL = requestURI.toASCIIString(); URI conflictURI; @@ -951,7 +939,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn // no destination, going to outproxy plugin if (usingInternalOutproxy) { Socket outSocket = outproxy.connect(host, remotePort); - Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId); + OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId); byte[] data; byte[] response; if (method.toUpperCase(Locale.US).equals("CONNECT")) { @@ -961,7 +949,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn data = newRequest.toString().getBytes("ISO-8859-1"); response = null; } - new I2PTunnelOutproxyRunner(s, outSocket, sockLock, data, response, onTimeout); + Thread t = new I2PTunnelOutproxyRunner(s, outSocket, sockLock, data, response, onTimeout); + t.start(); return; } @@ -980,7 +969,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn _log.warn(getPrefix(requestId) + "Could not find destination for " + addressHelper); } byte[] header = getErrorPage("ahelper-notfound", ERR_AHELPER_NOTFOUND); - writeErrorMessage(header, out, targetRequest, false, destination, null); + writeErrorMessage(header, out, targetRequest, false, destination); s.close(); return; } @@ -1018,7 +1007,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn } else if(ahelperPresent) { header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN); } else if(destination.length() == 60 && destination.toLowerCase(Locale.US).endsWith(".b32.i2p")) { - header = getErrorPage("dnf", ERR_DESTINATION_UNKNOWN); + header = getErrorPage("nols", ERR_DESTINATION_UNKNOWN); } else { header = getErrorPage("dnfh", ERR_DESTINATION_UNKNOWN); jumpServers = getTunnel().getClientOptions().getProperty(PROP_JUMP_SERVERS); @@ -1067,7 +1056,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn if (remotePort > 0) sktOpts.setPort(remotePort); I2PSocket i2ps = createI2PSocket(clientDest, sktOpts); - Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId); + OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId); if (method.toUpperCase(Locale.US).equals("CONNECT")) { byte[] data; byte[] response; @@ -1078,30 +1067,32 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn data = null; response = I2PTunnelConnectClient.SUCCESS_RESPONSE; } - new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout); + Thread t = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout); + t.start(); } else { byte[] data = newRequest.toString().getBytes("ISO-8859-1"); - new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout); + Thread t = new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout); + t.start(); } } catch(IOException ex) { if(_log.shouldLog(Log.INFO)) { _log.info(getPrefix(requestId) + "Error trying to connect", ex); } //l.log("Error connecting: " + ex.getMessage()); - handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); + handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); closeSocket(s); } catch(I2PException ex) { if(_log.shouldLog(Log.INFO)) { _log.info("getPrefix(requestId) + Error trying to connect", ex); } //l.log("Error connecting: " + ex.getMessage()); - handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); + handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); closeSocket(s); } catch(OutOfMemoryError oom) { IOException ex = new IOException("OOM"); _log.error("getPrefix(requestId) + Error trying to connect", oom); //l.log("Error connecting: " + ex.getMessage()); - handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); + handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId); closeSocket(s); } } @@ -1229,43 +1220,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn return Base32.encode(_dest.calculateHash().getData()) + ".b32.i2p"; } - /** - * Public only for LocalHTTPServer, not for general use - */ - public static void writeFooter(OutputStream out) throws IOException { - // the css is hiding this div for now, but we'll keep it here anyway - out.write("

I2P HTTP Proxy Server
Generated on: ".getBytes()); - out.write(new Date().toString().getBytes()); - out.write("

\n".getBytes()); - out.flush(); - } - - private static class OnTimeout implements Runnable { - - private final Socket _socket; - private final OutputStream _out; - private final String _target; - private final boolean _usingProxy; - private final String _wwwProxy; - private final long _requestId; - - public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) { - _socket = s; - _out = out; - _target = target; - _usingProxy = usingProxy; - _wwwProxy = wwwProxy; - _requestId = id; - } - - public void run() { - //if (_log.shouldLog(Log.DEBUG)) - // _log.debug("Timeout occured requesting " + _target); - handleHTTPClientException(new RuntimeException("Timeout"), _out, - _target, _usingProxy, _wwwProxy, _requestId); - closeSocket(_socket); - } - } public static final String DEFAULT_JUMP_SERVERS = "http://i2host.i2p/cgi-bin/i2hostjump?," + "http://stats.i2p/cgi-bin/jump.cgi?a=," + @@ -1273,101 +1227,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn "http://i2pjump.i2p/jump/"; //"http://i2jump.i2p/"; - /** - * @param jumpServers comma- or space-separated list, or null - */ - private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest, - boolean usingWWWProxy, String wwwProxy, String jumpServers) throws IOException { - if(out != null) { - out.write(errMessage); - if(targetRequest != null) { - String uri = targetRequest.replace("&", "&"); - out.write("".getBytes()); - out.write(uri.getBytes()); - out.write("".getBytes()); - if(usingWWWProxy) { - out.write(("

").getBytes()); - out.write(_("HTTP Outproxy").getBytes("UTF-8")); - out.write((": " + wwwProxy).getBytes()); - } - if(jumpServers != null && jumpServers.length() > 0) { - boolean first = true; - if(uri.startsWith("http://")) { - uri = uri.substring(7); - } - StringTokenizer tok = new StringTokenizer(jumpServers, ", "); - while(tok.hasMoreTokens()) { - String jurl = tok.nextToken(); - String jumphost; - try { - URI jURI = new URI(jurl); - String proto = jURI.getScheme(); - jumphost = jURI.getHost(); - if (proto == null || jumphost == null || - !proto.toLowerCase(Locale.US).equals("http")) - continue; - jumphost = jumphost.toLowerCase(Locale.US); - if (!jumphost.endsWith(".i2p")) - continue; - } catch(URISyntaxException use) { - continue; - } - // Skip jump servers we don't know - if(!jumphost.endsWith(".b32.i2p")) { - Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(jumphost); - if(dest == null) { - continue; - } - } - - if (first) { - first = false; - out.write("

".getBytes()); - out.write(_("Click a link below to look for an address helper by using a \"jump\" service:").getBytes("UTF-8")); - out.write("
\n".getBytes()); - } - out.write("
".getBytes()); - // Translators: parameter is a host name - out.write(_("{0} jump service", jumphost).getBytes()); - out.write("\n".getBytes()); - } - } - } - out.write("
".getBytes()); - writeFooter(out); - } - } - - private static void handleHTTPClientException(Exception ex, OutputStream out, String targetRequest, - boolean usingWWWProxy, String wwwProxy, long requestId) { - - // static - //if (_log.shouldLog(Log.WARN)) - // _log.warn(getPrefix(requestId) + "Error sending to " + wwwProxy + " (proxy? " + usingWWWProxy + ", request: " + targetRequest, ex); - if(out != null) { - try { - byte[] header; - if(usingWWWProxy) { - header = getErrorPage(I2PAppContext.getGlobalContext(), "dnfp", ERR_DESTINATION_UNKNOWN); - } else { - header = getErrorPage(I2PAppContext.getGlobalContext(), "dnf", ERR_DESTINATION_UNKNOWN); - } - writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy, null); - } catch(IOException ioe) { - // static - //_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe); - } - } else { - // static - //_log.warn(getPrefix(requestId) + "Client disconnected before we could say that destination " + "was unknown", ex); - } - } - /** @param host ignored */ private static boolean isSupportedAddress(String host, String protocol) { if((host == null) || (protocol == null)) { @@ -1526,22 +1385,4 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn } } ****/ - - /** these strings go in the jar, not the war */ - private static final String BUNDLE_NAME = "net.i2p.i2ptunnel.proxy.messages"; - - /** lang in routerconsole.lang property, else current locale */ - protected static String _(String key) { - return Translate.getString(key, I2PAppContext.getGlobalContext(), BUNDLE_NAME); - } - - /** {0} */ - protected static String _(String key, Object o) { - return Translate.getString(key, o, I2PAppContext.getGlobalContext(), BUNDLE_NAME); - } - - /** {0} and {1} */ - protected static String _(String key, Object o, Object o2) { - return Translate.getString(key, o, o2, I2PAppContext.getGlobalContext(), BUNDLE_NAME); - } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java index 9f4b0cf04..bf1bb0b56 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java @@ -3,14 +3,18 @@ */ package net.i2p.i2ptunnel; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.OutputStream; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; -import java.io.File; import java.util.BitSet; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -23,14 +27,18 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import net.i2p.I2PAppContext; +import net.i2p.client.streaming.I2PSocketException; import net.i2p.client.streaming.I2PSocketManager; import net.i2p.data.Base64; import net.i2p.data.DataHelper; +import net.i2p.data.Destination; +import net.i2p.data.i2cp.MessageStatusMessage; import net.i2p.util.EepGet; import net.i2p.util.EventDispatcher; import net.i2p.util.InternalSocket; import net.i2p.util.Log; import net.i2p.util.PasswordManager; +import net.i2p.util.Translate; import net.i2p.util.TranslateReader; /** @@ -73,11 +81,25 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem "HTTP outproxy configured. Please configure an outproxy in I2PTunnel") .getBytes(); + protected final static byte[] ERR_DESTINATION_UNKNOWN = + ("HTTP/1.1 503 Service Unavailable\r\n" + + "Content-Type: text/html; charset=iso-8859-1\r\n" + + "Cache-control: no-cache\r\n" + + "\r\n" + + "

I2P ERROR: DESTINATION NOT FOUND

" + + "That I2P Destination was not found. Perhaps you pasted in the " + + "wrong BASE64 I2P Destination or the link you are following is " + + "bad. The host (or the WWW proxy, if you're using one) could also " + + "be temporarily offline. You may want to retry. " + + "Could not find the following Destination:

").getBytes(); + private final byte[] _proxyNonce; private final ConcurrentHashMap _nonces; private final AtomicInteger _nonceCleanCounter = new AtomicInteger(); - protected String getPrefix(long requestId) { return "Client[" + _clientId + "/" + requestId + "]: "; } + protected String getPrefix(long requestId) { + return "HTTPClient[" + _clientId + '/' + requestId + "]: "; + } protected String selectProxy() { synchronized (_proxyList) { @@ -481,6 +503,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem } } + /** these strings go in the jar, not the war */ private static final String BUNDLE_NAME = "net.i2p.i2ptunnel.proxy.messages"; /** @@ -505,4 +528,226 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem } // we won't ever get here } + + /** + * @since 0.9.14 moved from superclasses + */ + protected class OnTimeout implements I2PTunnelRunner.FailCallback { + private final Socket _socket; + private final OutputStream _out; + private final String _target; + private final boolean _usingProxy; + private final String _wwwProxy; + private final long _requestId; + + public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) { + _socket = s; + _out = out; + _target = target; + _usingProxy = usingProxy; + _wwwProxy = wwwProxy; + _requestId = id; + } + + public void onFail(Exception ex) { + Throwable cause = ex.getCause(); + if (cause != null && cause instanceof I2PSocketException) { + I2PSocketException ise = (I2PSocketException) cause; + handleI2PSocketException(ise, _out, _target, _usingProxy, _wwwProxy); + } else { + handleClientException(ex, _out, _target, _usingProxy, _wwwProxy, _requestId); + } + closeSocket(_socket); + } + } + + /** + * @since 0.9.14 moved from superclasses + */ + protected void handleClientException(Exception ex, OutputStream out, String targetRequest, + boolean usingWWWProxy, String wwwProxy, long requestId) { + if (out == null) + return; + byte[] header; + if (usingWWWProxy) + header = getErrorPage(I2PAppContext.getGlobalContext(), "dnfp", ERR_DESTINATION_UNKNOWN); + else + header = getErrorPage(I2PAppContext.getGlobalContext(), "dnf", ERR_DESTINATION_UNKNOWN); + try { + writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy); + } catch (IOException ioe) {} + } + + /** + * Generate an error page based on the status code + * in our custom exception. + * + * @since 0.9.14 + */ + protected void handleI2PSocketException(I2PSocketException ise, OutputStream out, String targetRequest, + boolean usingWWWProxy, String wwwProxy) { + if (out == null) + return; + int status = ise.getStatus(); + String error; + //TODO MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION + if (status == MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET) { + error = usingWWWProxy ? "nolsp" : "nols"; + } else { + error = usingWWWProxy ? "dnfp" : "dnf"; + } + byte[] header = getErrorPage(error, ERR_DESTINATION_UNKNOWN); + String message = ise.getLocalizedMessage(); + try { + writeErrorMessage(header, message, out, targetRequest, usingWWWProxy, wwwProxy); + } catch(IOException ioe) {} + } + + /** + * No jump servers or extra message + * @since 0.9.14 + */ + protected void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest, + boolean usingWWWProxy, String wwwProxy) throws IOException { + writeErrorMessage(errMessage, null, out, targetRequest, usingWWWProxy, wwwProxy, null); + } + + /** + * No extra message + * @param jumpServers comma- or space-separated list, or null + * @since 0.9.14 moved from superclasses + */ + protected void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest, + boolean usingWWWProxy, String wwwProxy, String jumpServers) throws IOException { + writeErrorMessage(errMessage, null, out, targetRequest, usingWWWProxy, wwwProxy, jumpServers); + } + + /** + * No jump servers + * @param extraMessage extra message + * @since 0.9.14 + */ + protected void writeErrorMessage(byte[] errMessage, String extraMessage, + OutputStream out, String targetRequest, + boolean usingWWWProxy, String wwwProxy) throws IOException { + writeErrorMessage(errMessage, extraMessage, out, targetRequest, usingWWWProxy, wwwProxy, null); + } + + /** + * @param jumpServers comma- or space-separated list, or null + * @param msg extra message + * @since 0.9.14 + */ + protected void writeErrorMessage(byte[] errMessage, String extraMessage, + OutputStream out, String targetRequest, + boolean usingWWWProxy, String wwwProxy, + String jumpServers) throws IOException { + if (out == null) + return; + out.write(errMessage); + if (targetRequest != null) { + String uri = targetRequest.replace("&", "&"); + out.write("".getBytes()); + out.write(uri.getBytes()); + out.write("".getBytes()); + if (usingWWWProxy) { + out.write(("

").getBytes()); + out.write(_("HTTP Outproxy").getBytes("UTF-8")); + out.write((": " + wwwProxy).getBytes()); + } + if (extraMessage != null) { + out.write(("

" + extraMessage + "").getBytes()); + } + if (jumpServers != null && jumpServers.length() > 0) { + boolean first = true; + if(uri.startsWith("http://")) { + uri = uri.substring(7); + } + StringTokenizer tok = new StringTokenizer(jumpServers, ", "); + while(tok.hasMoreTokens()) { + String jurl = tok.nextToken(); + String jumphost; + try { + URI jURI = new URI(jurl); + String proto = jURI.getScheme(); + jumphost = jURI.getHost(); + if (proto == null || jumphost == null || + !proto.toLowerCase(Locale.US).equals("http")) + continue; + jumphost = jumphost.toLowerCase(Locale.US); + if (!jumphost.endsWith(".i2p")) + continue; + } catch(URISyntaxException use) { + continue; + } + // Skip jump servers we don't know + if (!jumphost.endsWith(".b32.i2p")) { + Destination dest = _context.namingService().lookup(jumphost); + if(dest == null) { + continue; + } + } + + if (first) { + first = false; + out.write("

".getBytes()); + out.write(_("Click a link below to look for an address helper by using a \"jump\" service:").getBytes("UTF-8")); + out.write("
\n".getBytes()); + } + out.write("
".getBytes()); + // Translators: parameter is a host name + out.write(_("{0} jump service", jumphost).getBytes()); + out.write("\n".getBytes()); + } + } + } + out.write("
".getBytes()); + writeFooter(out); + } + + /** + * Flushes. + * + * Public only for LocalHTTPServer, not for general use + * @since 0.9.14 moved from I2PTunnelHTTPClient + */ + public static void writeFooter(OutputStream out) throws IOException { + // The css is hiding this div for now, but we'll keep it here anyway + // Tag the strings below for translation if we unhide it. + out.write("

I2P HTTP Proxy Server
Generated on: ".getBytes()); + out.write(new Date().toString().getBytes()); + out.write("

\n".getBytes()); + out.flush(); + } + + /** + * Translate + * @since 0.9.14 moved from I2PTunnelHTTPClient + */ + protected String _(String key) { + return Translate.getString(key, _context, BUNDLE_NAME); + } + + /** + * Translate + * {0} + * @since 0.9.14 moved from I2PTunnelHTTPClient + */ + protected String _(String key, Object o) { + return Translate.getString(key, o, _context, BUNDLE_NAME); + } + + /** + * Translate + * {0} and {1} + * @since 0.9.14 moved from I2PTunnelHTTPClient + */ + protected String _(String key, Object o, Object o2) { + return Translate.getString(key, o, o2, _context, BUNDLE_NAME); + } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientRunner.java index 42c0361df..e12b839d3 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientRunner.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientRunner.java @@ -21,11 +21,16 @@ import net.i2p.util.Log; * and the server should echo it. However, both broken and malicious * servers could ignore that, potentially confusing the user. * + * Warning - not maintained as a stable API for external use. */ public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner { + + /** + * Does NOT start itself. Caller must call start(). + */ public I2PTunnelHTTPClientRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, - List sockList, Runnable onTimeout) { - super(s, i2ps, slock, initialI2PData, sockList, onTimeout); + List sockList, FailCallback onFail) { + super(s, i2ps, slock, initialI2PData, null, sockList, onFail); } @Override diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index 26ef4d68e..75f85a1cd 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -307,7 +307,9 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { Thread.currentThread().getName()+".hc"); req.start(); } else { - new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null); + Thread t = new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), + null, (I2PTunnelRunner.FailCallback) null); + t.start(); } long afterHandle = getTunnel().getContext().clock().now(); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java index 5cd691a3f..f39df586f 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java @@ -138,7 +138,9 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { modifiedRegistration = buf.toString(); } Socket s = getSocket(socket.getPeerDestination().calculateHash(), socket.getLocalPort()); - new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(), null); + Thread t = new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(), + null, (I2PTunnelRunner.FailCallback) null); + t.start(); } catch (SocketException ex) { try { // Send a response so the user doesn't just see a disconnect diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelOutproxyRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelOutproxyRunner.java index d59d84c78..1e0752b17 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelOutproxyRunner.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelOutproxyRunner.java @@ -26,6 +26,8 @@ import net.i2p.util.Log; /** * Like I2PTunnelRunner but socket-to-socket * + * Warning - not maintained as a stable API for external use. + * * @since 0.9.11 */ public class I2PTunnelOutproxyRunner extends I2PAppThread { @@ -54,22 +56,22 @@ public class I2PTunnelOutproxyRunner extends I2PAppThread { /** when the runner started up */ private final long startedOn; /** if we die before receiving any data, run this job */ - private final Runnable onTimeout; + private final I2PTunnelRunner.FailCallback onTimeout; private long totalSent; private long totalReceived; private static final AtomicLong __forwarderId = new AtomicLong(); /** - * Starts itself (fixme) + * Does NOT start itself. Caller must call start(). * * @param slock the socket lock, non-null * @param initialI2PData may be null - * @param onTimeout May be null. If non-null and no data (except initial data) was sent or received, + * @param onTimeout May be null. If non-null and no data (except initial data) was received, it will be run before closing s. */ public I2PTunnelOutproxyRunner(Socket s, Socket i2ps, Object slock, byte[] initialI2PData, - byte[] initialSocketData, Runnable onTimeout) { + byte[] initialSocketData, I2PTunnelRunner.FailCallback onTimeout) { this.s = s; this.i2ps = i2ps; this.slock = slock; @@ -83,7 +85,6 @@ public class I2PTunnelOutproxyRunner extends I2PAppThread { _log.info("OutproxyRunner started"); _runnerId = __runnerId.incrementAndGet(); setName("OutproxyRunner " + _runnerId); - start(); } /** @@ -144,6 +145,9 @@ public class I2PTunnelOutproxyRunner extends I2PAppThread { in = new BufferedInputStream(in, 2*NETWORK_BUFFER_SIZE); Thread t1 = new StreamForwarder(in, i2pout, true); Thread t2 = new StreamForwarder(i2pin, out, false); + // TODO can we run one of these inline and save a thread? + t1.start(); + t2.start(); synchronized (finishLock) { while (!finished) { finishLock.wait(); @@ -159,7 +163,7 @@ public class I2PTunnelOutproxyRunner extends I2PAppThread { + " totalSent = " + totalSent + " job = " + onTimeout); // Run even if totalSent > 0, as that's probably POST data. if (totalReceived <= 0) - onTimeout.run(); + onTimeout.onFail(null); } // now one connection is dead - kill the other as well, after making sure we flush @@ -242,6 +246,9 @@ public class I2PTunnelOutproxyRunner extends I2PAppThread { } } + /** + * Forward data in one direction + */ private class StreamForwarder extends I2PAppThread { private final InputStream in; @@ -250,6 +257,9 @@ public class I2PTunnelOutproxyRunner extends I2PAppThread { private final boolean _toI2P; private final ByteCache _cache; + /** + * Does not start itself. Caller must start() + */ private StreamForwarder(InputStream in, OutputStream out, boolean toI2P) { this.in = in; this.out = out; @@ -257,7 +267,6 @@ public class I2PTunnelOutproxyRunner extends I2PAppThread { direction = (toI2P ? "toOutproxy" : "fromOutproxy"); _cache = ByteCache.getInstance(32, NETWORK_BUFFER_SIZE); setName("OutproxyForwarder " + _runnerId + '.' + __forwarderId.incrementAndGet()); - start(); } @Override diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java index c9ca5d80e..057c6acaa 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java @@ -25,6 +25,11 @@ import net.i2p.util.I2PAppThread; import net.i2p.util.InternalSocket; import net.i2p.util.Log; +/** + * A thread that starts two more threads, one to forward traffic in each direction. + * + * Warning - not maintained as a stable API for external use. + */ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErrorListener { protected final Log _log; @@ -43,7 +48,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr private final Socket s; private final I2PSocket i2ps; private final Object slock, finishLock = new Object(); - volatile boolean finished = false; + private volatile boolean finished; private final byte[] initialI2PData; private final byte[] initialSocketData; /** when the last data was sent/received (or -1 if never) */ @@ -53,24 +58,35 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr private final List sockList; /** if we die before receiving any data, run this job */ private final Runnable onTimeout; + private final FailCallback _onFail; private long totalSent; private long totalReceived; private static final AtomicLong __forwarderId = new AtomicLong(); + /** + * For use in new constructor + * @since 0.9.14 + */ + public interface FailCallback { + /** + * @param e may be null + */ + public void onFail(Exception e); + } + + /** + * Starts itself + * + * @param slock the socket lock, non-null + * @param initialI2PData may be null + * @param sockList may be null. Caller must add i2ps to the list! It will be removed here on completion. + * Will synchronize on slock when removing. + * @deprecated use FailCallback constructor + */ public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList) { - this(s, i2ps, slock, initialI2PData, null, sockList, null); - } - - public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, - byte[] initialSocketData, List sockList) { - this(s, i2ps, slock, initialI2PData, initialSocketData, sockList, null); - } - - public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, - List sockList, Runnable onTimeout) { - this(s, i2ps, slock, initialI2PData, null, sockList, onTimeout); + this(s, i2ps, slock, initialI2PData, null, sockList, null, null, true); } /** @@ -81,11 +97,78 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr * @param initialSocketData may be null * @param sockList may be null. Caller must add i2ps to the list! It will be removed here on completion. * Will synchronize on slock when removing. - * @param onTimeout May be null. If non-null and no data (except initial data) was sent or received, - it will be run before closing s. + * @deprecated use FailCallback constructor + */ + public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, + byte[] initialSocketData, List sockList) { + this(s, i2ps, slock, initialI2PData, initialSocketData, sockList, null, null, true); + } + + /** + * Starts itself + * + * @param slock the socket lock, non-null + * @param initialI2PData may be null + * @param sockList may be null. Caller must add i2ps to the list! It will be removed here on completion. + * Will synchronize on slock when removing. + * @param onTimeout May be null. If non-null and no data (except initial data) was received, + * it will be run before closing s. + * @deprecated use FailCallback constructor + */ + public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, + List sockList, Runnable onTimeout) { + this(s, i2ps, slock, initialI2PData, null, sockList, onTimeout, null, true); + } + + /** + * Starts itself + * + * @param slock the socket lock, non-null + * @param initialI2PData may be null + * @param initialSocketData may be null + * @param sockList may be null. Caller must add i2ps to the list! It will be removed here on completion. + * Will synchronize on slock when removing. + * @param onTimeout May be null. If non-null and no data (except initial data) was received, + * it will be run before closing s. + * @deprecated use FailCallback constructor */ public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList, Runnable onTimeout) { + this(s, i2ps, slock, initialI2PData, null, sockList, onTimeout, null, true); + } + + /** + * Recommended new constructor. Does NOT start itself. Caller must call start(). + * + * @param slock the socket lock, non-null + * @param initialI2PData may be null + * @param initialSocketData may be null + * @param sockList may be null. Caller must add i2ps to the list! It will be removed here on completion. + * Will synchronize on slock when removing. + * @param onFail May be null. If non-null and no data (except initial data) was received, + * it will be run before closing s. + */ + public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, + byte[] initialSocketData, List sockList, FailCallback onFail) { + this(s, i2ps, slock, initialI2PData, null, sockList, null, onFail, false); + } + + /** + * Base constructor + * + * @param slock the socket lock, non-null + * @param initialI2PData may be null + * @param initialSocketData may be null + * @param sockList may be null. Caller must add i2ps to the list! It will be removed here on completion. + * Will synchronize on slock when removing. + * @param onTimeout May be null. If non-null and no data (except initial data) was received, + * it will be run before closing s. + * @param onFail Trumps onTimeout + * @param shouldStart should thread be started in constructor (bad, false recommended) + */ + private I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, + byte[] initialSocketData, List sockList, Runnable onTimeout, + FailCallback onFail, boolean shouldStart) { this.sockList = sockList; this.s = s; this.i2ps = i2ps; @@ -93,6 +176,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr this.initialI2PData = initialI2PData; this.initialSocketData = initialSocketData; this.onTimeout = onTimeout; + _onFail = onFail; lastActivityOn = -1; startedOn = Clock.getInstance().now(); _log = I2PAppContext.getGlobalContext().logManager().getLog(getClass()); @@ -100,7 +184,8 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr _log.info("I2PTunnelRunner started"); _runnerId = __runnerId.incrementAndGet(); setName("I2PTunnelRunner " + _runnerId); - start(); + if (shouldStart) + start(); } /** @@ -181,8 +266,11 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr + " written to the socket, starting forwarders"); if (!(s instanceof InternalSocket)) in = new BufferedInputStream(in, 2*NETWORK_BUFFER_SIZE); - Thread t1 = new StreamForwarder(in, i2pout, true); - Thread t2 = new StreamForwarder(i2pin, out, false); + StreamForwarder toI2P = new StreamForwarder(in, i2pout, true); + StreamForwarder fromI2P = new StreamForwarder(i2pin, out, false); + // TODO can we run one of these inline and save a thread? + toI2P.start(); + fromI2P.start(); synchronized (finishLock) { while (!finished) { finishLock.wait(); @@ -192,7 +280,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr _log.debug("At least one forwarder completed, closing and joining"); // this task is useful for the httpclient - if (onTimeout != null) { + if (onTimeout != null || _onFail != null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("runner has a timeout job, totalReceived = " + totalReceived + " totalSent = " + totalSent + " job = " + onTimeout); @@ -200,12 +288,20 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr // This will be run even if initialSocketData != null, it's the timeout job's // responsibility to know that and decide whether or not to write to the socket. // HTTPClient never sets initialSocketData. - if (totalReceived <= 0) - onTimeout.run(); + if (totalReceived <= 0) { + if (_onFail != null) { + Exception e = fromI2P.getFailure(); + if (e == null) + e = toI2P.getFailure(); + _onFail.onFail(e); + } else { + onTimeout.run(); + } + } } // now one connection is dead - kill the other as well, after making sure we flush - close(out, in, i2pout, i2pin, s, i2ps, t1, t2); + close(out, in, i2pout, i2pin, s, i2ps, toI2P, fromI2P); } catch (InterruptedException ex) { if (_log.shouldLog(Log.ERROR)) _log.error("Interrupted", ex); @@ -291,6 +387,9 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr t2.join(30*1000); } + /** + * Deprecated, unimplemented in streaming, never called. + */ public void errorOccurred() { synchronized (finishLock) { finished = true; @@ -306,6 +405,9 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr } } + /** + * Forward data in one direction + */ private class StreamForwarder extends I2PAppThread { private final InputStream in; @@ -313,15 +415,18 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr private final String direction; private final boolean _toI2P; private final ByteCache _cache; + private volatile Exception _failure; - private StreamForwarder(InputStream in, OutputStream out, boolean toI2P) { + /** + * Does not start itself. Caller must start() + */ + public StreamForwarder(InputStream in, OutputStream out, boolean toI2P) { this.in = in; this.out = out; _toI2P = toI2P; direction = (toI2P ? "toI2P" : "fromI2P"); _cache = ByteCache.getInstance(32, NETWORK_BUFFER_SIZE); setName("StreamForwarder " + _runnerId + '.' + __forwarderId.incrementAndGet()); - start(); } @Override @@ -377,10 +482,12 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr ex); } } + _failure = ex; } catch (InterruptedIOException ex) { if (_log.shouldLog(Log.WARN)) _log.warn(direction + ": Closing connection due to timeout (error: \"" + ex.getMessage() + "\")"); + _failure = ex; } catch (IOException ex) { if (!finished) { if (_log.shouldLog(Log.WARN)) @@ -388,6 +495,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr } //else // _log.warn("You may ignore this", ex); + _failure = ex; } finally { _cache.release(ba); if (_log.shouldLog(Log.INFO)) { @@ -408,7 +516,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr // DON'T close if we have a timeout job and we haven't received anything, // or else the timeout job can't write the error message to the stream. // close() above will close it after the timeout job is run. - if (!(onTimeout != null && (!_toI2P) && totalReceived <= 0)) + if (!((onTimeout != null || _onFail != null) && (!_toI2P) && totalReceived <= 0)) out.close(); else if (_log.shouldLog(Log.INFO)) _log.info(direction + ": not closing so we can write the error message"); @@ -423,5 +531,12 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr } } } + + /** + * @since 0.9.14 + */ + public Exception getFailure() { + return _failure; + } } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index e79bf7ce4..0d2667d60 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -314,7 +314,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { return readTimeout; } - public boolean close(boolean forced) { + public synchronized boolean close(boolean forced) { if (!open) return true; if (task != null) { task.close(forced); @@ -523,7 +523,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { socket.setReadTimeout(readTimeout); Socket s = getSocket(socket.getPeerDestination().calculateHash(), socket.getLocalPort()); afterSocket = getTunnel().getContext().clock().now(); - new I2PTunnelRunner(s, socket, slock, null, null); + Thread t = new I2PTunnelRunner(s, socket, slock, null, null, + null, (I2PTunnelRunner.FailCallback) null); + t.start(); long afterHandle = getTunnel().getContext().clock().now(); long timeToHandle = afterHandle - afterAccept; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCClient.java index 4d70d2ee2..e22662103 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCClient.java @@ -75,7 +75,8 @@ public class I2PTunnelDCCClient extends I2PTunnelClientBase { opts.setPort(_remotePort); try { i2ps = createI2PSocket(dest, opts); - new Runner(s, i2ps); + Thread t = new Runner(s, i2ps); + t.start(); } catch (Exception ex) { _log.error("Could not make DCC connection to " + _dest + ':' + _remotePort, ex); closeSocket(s); @@ -115,9 +116,11 @@ public class I2PTunnelDCCClient extends I2PTunnelClientBase { */ private class Runner extends I2PTunnelRunner { + /** + * Does NOT start itself. Caller must call start(). + */ public Runner(Socket s, I2PSocket i2ps) { - // super calls start() - super(s, i2ps, sockLock, null, mySockets); + super(s, i2ps, sockLock, null, null, mySockets, (FailCallback) null); } @Override diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCServer.java index 48e53dd57..4b1b6b8f5 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCServer.java @@ -109,7 +109,9 @@ public class I2PTunnelDCCServer extends I2PTunnelServer { try { Socket s = new Socket(local.ia, local.port); _sockList.add(socket); - new I2PTunnelRunner(s, socket, slock, null, _sockList); + Thread t = new I2PTunnelRunner(s, socket, slock, null, null, _sockList, + (I2PTunnelRunner.FailCallback) null); + t.start(); local.socket = socket; local.expire = getTunnel().getContext().clock().now() + OUTBOUND_EXPIRE; _active.put(Integer.valueOf(myPort), local); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java index f6df4a56b..89674c592 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java @@ -54,7 +54,9 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase { SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s, getTunnel().getClientOptions()); Socket clientSock = serv.getClientSocket(); I2PSocket destSock = serv.getDestinationI2PSocket(this); - new I2PTunnelRunner(clientSock, destSock, sockLock, null, mySockets); + Thread t = new I2PTunnelRunner(clientSock, destSock, sockLock, null, null, mySockets, + (I2PTunnelRunner.FailCallback) null); + t.start(); } catch (SOCKSException e) { if (_log.shouldLog(Log.WARN)) _log.warn("Error from SOCKS connection", e); diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketException.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketException.java index c657ac672..e1f9f0cb3 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketException.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketException.java @@ -2,11 +2,13 @@ package net.i2p.client.streaming; import java.net.SocketException; +import net.i2p.I2PAppContext; import net.i2p.client.SendMessageStatusListener; import net.i2p.data.i2cp.MessageStatusMessage; +import net.i2p.util.Translate; /** - * An I2P-specific IOException thrown from input and output streams. + * An I2P-specific IOException thrown from input and output streams, * with a stored status code to be used for programmatic responses. * * @since 0.9.14 @@ -15,6 +17,7 @@ public class I2PSocketException extends SocketException { private final int _status; private static final int CUSTOM = -1; + private static final String BUNDLE_NAME = "net.i2p.client.streaming.messages"; /** * Use canned message for this status code @@ -52,55 +55,55 @@ public class I2PSocketException extends SocketException { switch (_status) { case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE: case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE: - return "Message timeout"; + return _x("Message timeout"); case MessageStatusMessage.STATUS_SEND_FAILURE_LOCAL: - return "Failed delivery to local destination"; + return _x("Failed delivery to local destination"); case MessageStatusMessage.STATUS_SEND_FAILURE_ROUTER: - return "Local router failure"; + return _x("Local router failure"); case MessageStatusMessage.STATUS_SEND_FAILURE_NETWORK: - return "Local network failure"; + return _x("Local network failure"); case MessageStatusMessage.STATUS_SEND_FAILURE_BAD_SESSION: - return "Session closed"; + return _x("Session closed"); case MessageStatusMessage.STATUS_SEND_FAILURE_BAD_MESSAGE: - return "Invalid message"; + return _x("Invalid message"); case MessageStatusMessage.STATUS_SEND_FAILURE_BAD_OPTIONS: - return "Invalid message options"; + return _x("Invalid message options"); case MessageStatusMessage.STATUS_SEND_FAILURE_OVERFLOW: - return "Buffer overflow"; + return _x("Buffer overflow"); case MessageStatusMessage.STATUS_SEND_FAILURE_EXPIRED: - return "Message expired"; + return _x("Message expired"); case MessageStatusMessage.STATUS_SEND_FAILURE_LOCAL_LEASESET: - return "Local lease set invalid"; + return _x("Local lease set invalid"); case MessageStatusMessage.STATUS_SEND_FAILURE_NO_TUNNELS: - return "No local tunnels"; + return _x("No local tunnels"); case MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION: - return "Unsupported encryption options"; + return _x("Unsupported encryption options"); case MessageStatusMessage.STATUS_SEND_FAILURE_DESTINATION: - return "Invalid destination"; + return _x("Invalid destination"); case MessageStatusMessage.STATUS_SEND_FAILURE_BAD_LEASESET: - return "Local router failure"; + return _x("Local router failure"); case MessageStatusMessage.STATUS_SEND_FAILURE_EXPIRED_LEASESET: - return "Destination lease set expired"; + return _x("Destination lease set expired"); case MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET: - return "Destination lease set not found"; + return _x("Destination lease set not found"); case SendMessageStatusListener.STATUS_CANCELLED: - return "Local destination shutdown"; + return _x("Local destination shutdown"); case CUSTOM: return super.getMessage(); @@ -109,4 +112,22 @@ public class I2PSocketException extends SocketException { return "Failure code: " + _status; } } + + /** + * Translated + */ + @Override + public String getLocalizedMessage() { + String s = getMessage(); + if (s == null) + return null; + return Translate.getString(s, I2PAppContext.getGlobalContext(), BUNDLE_NAME); + } + + /** + * Tag for translation + */ + private static String _x(String s) { + return s; + } } diff --git a/installer/resources/proxy/nols-header.ht b/installer/resources/proxy/nols-header.ht new file mode 100644 index 000000000..11ddffdc3 --- /dev/null +++ b/installer/resources/proxy/nols-header.ht @@ -0,0 +1,26 @@ +HTTP/1.1 504 Gateway Timeout +Content-Type: text/html; charset=UTF-8 +Cache-control: no-cache +Connection: close +Proxy-Connection: close + + + +_("Warning: Eepsite Unreachable") + + + + + +
+

_("Warning: Eepsite Unreachable")

+

+_("The eepsite was not reachable, because its lease set was not found.") +_("The eepsite is probably down.") +_("You may want to {0}retry{1}.", "", "")

+
+

_("Could not find the following destination:") +

diff --git a/installer/resources/proxy/nolsp-header.ht b/installer/resources/proxy/nolsp-header.ht new file mode 100644 index 000000000..483a95857 --- /dev/null +++ b/installer/resources/proxy/nolsp-header.ht @@ -0,0 +1,26 @@ +HTTP/1.1 504 Gateway Timeout +Content-Type: text/html; charset=UTF-8 +Cache-control: no-cache +Connection: close +Proxy-Connection: close + + + +_("Warning: Outproxy Not Found") + + + + + +
+

_("Warning: Outproxy Not Found")

+

+_("The HTTP outproxy was not reachable, because its lease set was not found.") +_("The outproxy is probably down.") +_("You may want to {0}retry{1} as this will randomly reselect an outproxy from the pool you have defined {2}here{3} (if you have more than one configured).", "", "", "", "") +_("If you continue to have trouble you may want to edit your outproxy list {0}here{1}.", "", "") +

+

_("Could not find the following destination:")