diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index a0e9405787..05a41860a6 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -29,6 +29,7 @@ import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.i2ptunnel.localServer.LocalHTTPServer; +import net.i2p.outproxy.Outproxy; import net.i2p.util.EventDispatcher; import net.i2p.util.Log; import net.i2p.util.PortMapper; @@ -320,6 +321,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn public static final String PROP_VIA = "i2ptunnel.httpclient.sendVia"; public static final String PROP_JUMP_SERVERS = "i2ptunnel.httpclient.jumpServers"; public static final String PROP_DISABLE_HELPER = "i2ptunnel.httpclient.disableAddressHelper"; + public static final String PROP_OUTPROXY = "i2ptunnel.useLocalOutproxy"; protected void clientConnectionRun(Socket s) { OutputStream out = null; @@ -330,6 +332,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn String targetRequest = null; boolean usingWWWProxy = false; + boolean usingOutproxy = false; + Outproxy outproxy = null; boolean usingInternalServer = false; String internalPath = null; String internalRawQuery = null; @@ -677,34 +681,47 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn s.close(); return; } else if(host.contains(".") || host.startsWith("[")) { - if(port >= 0) { - host = host + ':' + port; - } - // The request must be forwarded to a WWW proxy - if(_log.shouldLog(Log.DEBUG)) { - _log.debug("Before selecting outproxy for " + host); - } - currentProxy = selectProxy(); - if(_log.shouldLog(Log.DEBUG)) { - _log.debug("After selecting outproxy for " + host + ": " + currentProxy); - } - if(currentProxy == null) { - if(_log.shouldLog(Log.WARN)) { - _log.warn(getPrefix(requestId) + "Host wants to be outproxied, but we dont have any!"); + if (Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_OUTPROXY)) && + (outproxy = _context.outproxy()) != null) { + int rPort = requestURI.getPort(); + if (rPort > 0) + remotePort = rPort; + else + remotePort = 80; + usingOutproxy = true; + targetRequest = requestURI.toASCIIString(); + if(_log.shouldLog(Log.DEBUG)) + _log.debug(getPrefix(requestId) + " [" + host + "]: outproxy!"); + } else { + if(port >= 0) { + host = host + ':' + port; } - l.log("No HTTP outproxy found for the request."); - if(out != null) { - out.write(getErrorPage("noproxy", _ERR_NO_OUTPROXY)); - writeFooter(out); + // The request must be forwarded to a WWW proxy + if(_log.shouldLog(Log.DEBUG)) { + _log.debug("Before selecting outproxy for " + host); + } + currentProxy = selectProxy(); + if(_log.shouldLog(Log.DEBUG)) { + _log.debug("After selecting outproxy for " + host + ": " + currentProxy); + } + if(currentProxy == null) { + if(_log.shouldLog(Log.WARN)) { + _log.warn(getPrefix(requestId) + "Host wants to be outproxied, but we dont have any!"); + } + l.log("No HTTP outproxy found for the request."); + if(out != null) { + out.write(getErrorPage("noproxy", _ERR_NO_OUTPROXY)); + writeFooter(out); + } + s.close(); + return; + } + destination = currentProxy; + usingWWWProxy = true; + targetRequest = requestURI.toASCIIString(); + if(_log.shouldLog(Log.DEBUG)) { + _log.debug(getPrefix(requestId) + " [" + host + "]: wwwProxy!"); } - s.close(); - return; - } - destination = currentProxy; - usingWWWProxy = true; - targetRequest = requestURI.toASCIIString(); - if(_log.shouldLog(Log.DEBUG)) { - _log.debug(getPrefix(requestId) + " [" + host + "]: wwwProxy!"); } } else { // what is left for here? a hostname with no dots, and != "i2p" @@ -722,7 +739,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn return; } // end host name processing - boolean isValid = usingWWWProxy || usingInternalServer || isSupportedAddress(host, protocol); + boolean isValid = usingOutproxy || usingWWWProxy || + usingInternalServer || isSupportedAddress(host, protocol); if(!isValid) { if(_log.shouldLog(Log.INFO)) { _log.info(getPrefix(requestId) + "notValid(" + host + ")"); @@ -743,7 +761,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn // end first line processing } else { - if(lowercaseLine.startsWith("host: ") && !usingWWWProxy) { + if(lowercaseLine.startsWith("host: ") && !usingWWWProxy && !usingOutproxy) { // Note that we only pass the original Host: line through to the outproxy // But we don't create a Host: line if it wasn't sent to us line = "Host: " + host; @@ -815,7 +833,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn if(!shout) { if(!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT))) { // let's not advertise to external sites that we are from I2P - if(usingWWWProxy) { + if(usingWWWProxy || usingOutproxy) { newRequest.append("User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6\r\n"); } else { newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n"); @@ -848,7 +866,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn _log.debug(getPrefix(requestId) + "NewRequest header: [" + newRequest.toString() + "]"); } - if(method == null || destination == null) { + if(method == null || (destination == null && !usingOutproxy)) { //l.log("No HTTP method found in the request."); if(out != null) { if(protocol != null && "http".equals(protocol.toLowerCase(Locale.US))) { @@ -896,6 +914,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn return; } + // no destination, going to outproxy plugin + if (usingOutproxy) { + Socket outSocket = outproxy.connect(host, remotePort); + byte[] data = newRequest.toString().getBytes("ISO-8859-1"); + Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId); + new I2PTunnelOutproxyRunner(s, outSocket, sockLock, data, onTimeout); + return; + } + // LOOKUP // If the host is "i2p", the getHostName() lookup failed, don't try to // look it up again as the naming service does not do negative caching diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index 136079fd02..9aaf4c3b27 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -25,6 +25,7 @@ import net.i2p.crypto.TransientSessionKeyManager; import net.i2p.data.Base64; import net.i2p.data.RoutingKeyGenerator; import net.i2p.internal.InternalClientManager; +import net.i2p.outproxy.Outproxy; import net.i2p.stat.StatManager; import net.i2p.update.UpdateManager; import net.i2p.util.Clock; @@ -94,6 +95,7 @@ public class I2PAppContext { private SimpleTimer _simpleTimer; private SimpleTimer2 _simpleTimer2; private final PortMapper _portMapper; + private Outproxy _outproxy; private volatile boolean _statManagerInitialized; private volatile boolean _sessionKeyManagerInitialized; private volatile boolean _namingServiceInitialized; @@ -127,7 +129,8 @@ public class I2PAppContext { _lock5 = new Object(), _lock6 = new Object(), _lock7 = new Object(), _lock8 = new Object(), _lock9 = new Object(), _lock10 = new Object(), _lock11 = new Object(), _lock12 = new Object(), _lock13 = new Object(), _lock14 = new Object(), _lock15 = new Object(), _lock16 = new Object(), - _lock17 = new Object(), _lock18 = new Object(), _lock19 = new Object(), _lock20 = new Object(); + _lock17 = new Object(), _lock18 = new Object(), _lock19 = new Object(), _lock20 = new Object(), + _lock21 = new Object(); /** * Pull the default context, creating a new one if necessary, else using @@ -1023,4 +1026,39 @@ public class I2PAppContext { public UpdateManager updateManager() { return null; } + + /** + * A local outproxy + * @return The outproxy if it is registered, else null + * @since 0.9.11 + */ + public Outproxy outproxy() { + return _outproxy; + } + + /** + * Register as the outproxy. For now, only one. + * @throws IllegalStateException if one was already registered + * @since 0.9.11 + */ + public void registerOutproxy(Outproxy oproxy) { + synchronized(_lock21) { + if (_outproxy != null) + throw new IllegalStateException(); + _outproxy = oproxy; + } + } + + /** + * Unregister the outproxy. + * @throws IllegalStateException if it was not registered + * @since 0.9.11 + */ + public void unregisterOutproxy(Outproxy oproxy) { + synchronized(_lock21) { + if (_outproxy != oproxy) + throw new IllegalStateException(); + _outproxy = null; + } + } } diff --git a/core/java/src/net/i2p/outproxy/Outproxy.java b/core/java/src/net/i2p/outproxy/Outproxy.java new file mode 100644 index 0000000000..a18066acdd --- /dev/null +++ b/core/java/src/net/i2p/outproxy/Outproxy.java @@ -0,0 +1,17 @@ +package net.i2p.outproxy; + +import java.io.IOException; +import java.net.Socket; + +/** + * + * @since 0.9.11 + */ +public interface Outproxy { + + /** + * + */ + public Socket connect(String host, int port) throws IOException; + +}