Compare commits

...

7 Commits

8 changed files with 635 additions and 38 deletions

View File

@ -6,12 +6,12 @@
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2, or (at
* your option) any later version.
*
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
@ -196,7 +196,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
Properties p = _context.getProperties();
_clientOptions = p;
_sessions = new CopyOnWriteArraySet<I2PSession>();
addConnectionEventListener(lsnr);
boolean gui = true;
boolean checkRunByE = true;
@ -359,13 +359,13 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
public List<I2PSession> getSessions() {
if (_sessions.isEmpty())
return Collections.emptyList();
return new ArrayList<I2PSession>(_sessions);
return new ArrayList<I2PSession>(_sessions);
}
/**
* @param session null ok
*/
void addSession(I2PSession session) {
void addSession(I2PSession session) {
if (session == null) return;
boolean added = _sessions.add(session);
if (added && _log.shouldLog(Log.INFO))
@ -375,27 +375,27 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
/**
* @param session null ok
*/
void removeSession(I2PSession session) {
void removeSession(I2PSession session) {
if (session == null) return;
boolean removed = _sessions.remove(session);
if (removed && _log.shouldLog(Log.INFO))
_log.info(getPrefix() + " session removed: " + session, new Exception());
}
/**
* Generic options used for clients and servers.
* NOT a copy, Do NOT modify for per-connection options, make a copy.
* @return non-null, NOT a copy, do NOT modify for per-connection options
*/
public Properties getClientOptions() { return _clientOptions; }
/**
* TunnelController that constructed this, or null.
* @return controller or null
* @since 0.9.48
*/
TunnelController getController() { return _controller; }
private void addtask(I2PTunnelTask tsk) {
tsk.setTunnel(this);
if (tsk.isOpen()) {
@ -445,6 +445,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
runClient(args, l);
} else if ("httpclient".equals(cmdname)) {
runHttpClient(args, l);
} else if ("browserclient".equals(cmdname)) {
runBrowserClient(args, l);
} else if ("ircclient".equals(cmdname)) {
runIrcClient(args, l);
} else if ("sockstunnel".equals(cmdname)) {
@ -528,11 +530,11 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
" streamrserver <port> <privkeyfile>\n" +
" textserver <host> <port> <privkey>\n");
}
/**
* Configure the extra I2CP options to use in any subsequent I2CP sessions.
* Generic options used for clients and servers
* Usage: "clientoptions[ key=value]*" .
* Usage: "clientoptions[ key=value]*" .
*
* Sets the event "clientoptions_onResult" = "ok" after completion.
*
@ -755,7 +757,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[1]);
String spoofedHost = args[2];
privKeyFile = new File(args[3]);
if (!privKeyFile.isAbsolute())
privKeyFile = new File(_context.getConfigDir(), args[3]);
@ -773,8 +775,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
return;
} else {
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>\n" +
" creates an HTTP server that sends all incoming data\n"
+ " of its destination to host:port., filtering the HTTP\n"
" creates an HTTP server that sends all incoming data\n"
+ " of its destination to host:port., filtering the HTTP\n"
+ " headers so it looks like the request is to the spoofed host.");
notifyEvent("serverTaskId", Integer.valueOf(-1));
}
@ -968,7 +970,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
}
/**
* Run an HTTP client on the given port number
* Run an HTTP client on the given port number
*
* Sets the event "httpclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
* Also sets "httpclientStatus" = "ok" or "error" after the client tunnel has started.
@ -990,7 +992,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
}
if (clientPort <= 0)
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
String proxy = "";
boolean isShared = true;
if (args.length > 1) {
@ -1041,7 +1043,76 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
}
/**
* Run a CONNECT client on the given port number
* Run an BROWSER client on the given port number
*
* Sets the event "httpbrowserclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
* Also sets "httpbrowserclientStatus" = "ok" or "error" after the client tunnel has started.
* Mutually exclusive with "Shared Clients"
*
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
* @param l logger to receive events and output
* @throws IllegalArgumentException on config problem
*/
public void runBrowserClient(String args[], Logging l) {
if (args.length >= 1 && args.length <= 3) {
int clientPort = -1;
try {
clientPort = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
notifyEvent("httpbrowserclientTaskId", Integer.valueOf(-1));
}
if (clientPort <= 0)
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
String proxy = "";
if (args.length > 1) {
if (Boolean.parseBoolean(args[1].trim())) {
if (args.length == 3)
proxy = args[2];
} else if ("false".equalsIgnoreCase(args[1].trim())) {
if (args.length == 3)
proxy = args[2];
} else if (args.length == 3) {
proxy = args[2];
} else {
proxy = args[1];
}
}
// isShared not specified, default to false
boolean isShared = false;
ownDest = !isShared;
try {
I2PTunnelClientBase task = new I2PTunnelHTTPBrowserClient(clientPort, l, ownDest, proxy, this, this);
task.startRunning();
addtask(task);
notifyEvent("httpbrowserclientTaskId", Integer.valueOf(task.getId()));
} catch (IllegalArgumentException iae) {
String msg = "Invalid I2PTunnel configuration to create an Browser Proxy connecting to the router at " + host + ':'+ port +
" and listening on " + listenHost + ':' + clientPort;
_log.error(getPrefix() + msg, iae);
l.log(msg);
notifyEvent("httpbrowserclientTaskId", Integer.valueOf(-1));
// Since nothing listens to TaskID events, use this to propagate the error to TunnelController
// Otherwise, the tunnel stays up even though the port is down
// This doesn't work for CLI though... and the tunnel doesn't close itself after error,
// so this probably leaves the tunnel open if called from the CLI
throw iae;
}
} else {
l.log("browserclient <port> [<sharedClient>] [<proxy>]\n" +
" Creates a HTTP client proxy on the specified port.\n" +
" <sharedClient> (always false) Indicates if this client shares tunnels with other clients (true or false)\n" +
" <proxy> (optional) Indicates a proxy server to be used\n" +
" when trying to access an address out of the .i2p domain");
notifyEvent("httpbrowserclientTaskId", Integer.valueOf(-1));
}
}
/**
* Run a CONNECT client on the given port number
*
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
* @param l logger to receive events and output
@ -1057,7 +1128,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
}
if (_port <= 0)
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
String proxy = "";
boolean isShared = true;
if (args.length > 1) {
@ -1105,7 +1176,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
}
/**
* Run an IRC client on the given port number
* Run an IRC client on the given port number
*
* Sets the event "ircclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
* Also sets "ircclientStatus" = "ok" or "error" after the client tunnel has started.
@ -1127,7 +1198,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
}
if (_port <= 0)
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
boolean isShared = true;
if (args.length > 2) {
if (Boolean.parseBoolean(args[2].trim())) {
@ -1168,9 +1239,9 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
notifyEvent("ircclientTaskId", Integer.valueOf(-1));
}
}
/**
* Run an SOCKS tunnel on the given port number
* Run an SOCKS tunnel on the given port number
*
* Sets the event "sockstunnelTaskId" = Integer(taskId) after the
* tunnel has been started (or -1 on error). Also sets
@ -1222,9 +1293,9 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
}
}
/**
* Run an SOCKS IRC tunnel on the given port number
* Run an SOCKS IRC tunnel on the given port number
* @param args {portNumber [, sharedClient]} or (portNumber, ignored (false), privKeyFile)
* @throws IllegalArgumentException on config problem
* @since 0.7.12
@ -1362,7 +1433,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
}
/**
* Specify the i2cp host and port
* Specify the i2cp host and port
* Deprecated - only used by CLI
*
* Sets the event "configResult" = "ok" or "error" after the configuration has been specified
@ -1677,7 +1748,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
* Perform a lookup of the name specified
* Deprecated - only used by CLI
*
* Sets the event "lookupResult" = base64 of the destination, or an error message
* Sets the event "lookupResult" = base64 of the destination, or an error message
*
* @param args {name}
* @param l logger to receive events and output
@ -1844,7 +1915,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
}
/**
* Helper method to actually close the given task number (optionally forcing
* Helper method to actually close the given task number (optionally forcing
* closure)
*
*/
@ -1916,7 +1987,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
}
/**
* Create a new destination, storing the destination and its private keys where
* Create a new destination, storing the destination and its private keys where
* instructed.
* Does NOT support non-default sig types.
* Deprecated - only used by CLI
@ -1980,8 +2051,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
/**
* Generates a Destination from a name. Now only supports base64
* names - may support naming servers later. "file:&lt;filename&gt;" is
* also supported, where filename is a file that either contains a
* binary Destination structure or the Base64 encoding of that
* also supported, where filename is a file that either contains a
* binary Destination structure or the Base64 encoding of that
* structure.
*
* Since file:&lt;filename&gt; isn't really used, this method is deprecated,
@ -2008,7 +2079,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
Log log = ctx.logManager().getLog(I2PTunnel.class);
if (name.startsWith("file:")) {
Destination result = new Destination();
byte content[] = null;
@ -2032,14 +2103,14 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
result.fromByteArray(content);
return result;
} catch (RuntimeException ex) {
if (log.shouldLog(Log.INFO))
if (log.shouldLog(Log.INFO))
log.info("File is not a binary destination - trying base64");
try {
byte decoded[] = Base64.decode(new String(content));
result.fromByteArray(decoded);
return result;
} catch (DataFormatException dfe) {
if (log.shouldLog(Log.WARN))
if (log.shouldLog(Log.WARN))
log.warn("File is not a base64 destination either - failing!");
return null;
}
@ -2078,7 +2149,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
session.connect();
d = session.lookupDest(name);
} catch (I2PSessionException ise) {
if (log.shouldLog(Log.WARN))
if (log.shouldLog(Log.WARN))
log.warn("Lookup via router failed", ise);
} finally {
if (session != null) {
@ -2098,9 +2169,9 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
if (lsnr == null) return;
listeners.remove(lsnr);
}
private String getPrefix() { return "[" + _tunnelId + "]: "; }
public I2PAppContext getContext() { return _context; }
/**

View File

@ -0,0 +1,502 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.app.ClientApp;
import net.i2p.app.ClientAppManager;
import net.i2p.app.Outproxy;
import net.i2p.client.I2PSession;
import net.i2p.client.LookupResult;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.crypto.Blinding;
import net.i2p.crypto.SHA256Generator;
import net.i2p.data.Base32;
import net.i2p.data.Base64;
import net.i2p.data.BlindData;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.i2ptunnel.localServer.LocalHTTPServer;
import net.i2p.util.ConvertToHash;
import net.i2p.util.DNSOverHTTPS;
import net.i2p.util.EventDispatcher;
import net.i2p.util.InternalSocket;
import net.i2p.util.Log;
import net.i2p.util.PortMapper;
/**
* Act as a multiplexer of I2PTunnelHTTPClients with different ports on a
* single port. Dynamically creates a new I2PTunnelHTTPClient on a per-host
* basis. For each new host, it creates a new I2PTunnelHTTPClient. Each
* I2PTunnelHTTPClient is used for requests from a single specific origin.
*
*/
public class I2PTunnelHTTPBrowserClient extends I2PTunnelHTTPClient {
HashMap<Destination, I2PTunnelHTTPClient> clients =
new HashMap<Destination, I2PTunnelHTTPClient>();
private InternalSocketRunner isr;
private static final boolean DEFAULT_KEEPALIVE_BROWSER = true;
/**
*
*/
public I2PTunnelHTTPBrowserClient(int localPort, Logging l,
I2PSocketManager sockMgr, I2PTunnel tunnel,
EventDispatcher notifyThis, long clientId) {
super(localPort, l, sockMgr, tunnel, notifyThis, clientId);
setName("HTTP Proxy on " + tunnel.listenHost + ':' + localPort);
notifyEvent("openHTTPClientResult", "ok");
}
/**
*
*/
public I2PTunnelHTTPBrowserClient(int localPort, Logging l, boolean ownDest,
String wwwProxy, EventDispatcher notifyThis,
I2PTunnel tunnel)
throws IllegalArgumentException {
super(localPort, l, ownDest, wwwProxy, notifyThis, tunnel);
setName("HTTP Proxy on " + tunnel.listenHost + ':' + localPort);
notifyEvent("openHTTPClientResult", "ok");
}
/**
* Create the default options (using the default timeout, etc).
* Warning, this does not make a copy of I2PTunnel's client options,
* it modifies them directly.
* unused?
*
* This will throw IAE on tunnel build failure
*/
@Override
protected I2PSocketOptions getDefaultOptions() {
Properties defaultOpts = getTunnel().getClientOptions();
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT)) {
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT,
"" + I2PTunnelHTTPClient.DEFAULT_READ_TIMEOUT);
}
// if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
// defaultOpts.setProperty("i2p.streaming.inactivityTimeout",
// ""+DEFAULT_READ_TIMEOUT);
// delayed start
verifySocketManager();
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT)) {
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
}
return opts;
}
/**
* Create the default options (using the default timeout, etc).
* Warning, this does not make a copy of I2PTunnel's client options,
* it modifies them directly.
* Do not use overrides for per-socket options.
*
* This will throw IAE on tunnel build failure
*/
@Override
protected I2PSocketOptions getDefaultOptions(Properties overrides) {
Properties defaultOpts = getTunnel().getClientOptions();
defaultOpts.putAll(overrides);
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT)) {
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT,
"" + I2PTunnelHTTPClient.DEFAULT_READ_TIMEOUT);
}
if (!defaultOpts.contains("i2p.streaming.inactivityTimeout")) {
defaultOpts.setProperty("i2p.streaming.inactivityTimeout",
"" + I2PTunnelHTTPClient.DEFAULT_READ_TIMEOUT);
}
// delayed start
verifySocketManager();
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT)) {
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
}
return opts;
}
/**
* Actually start working on incoming connections.
* Overridden to start an internal socket too.
*
*/
@Override
public void startRunning() {
super.startRunning();
if (open) {
this.isr = new InternalSocketRunner(this);
this.isr.start();
int port = getLocalPort();
_context.portMapper().register(PortMapper.SVC_HTTP_PROXY_TABBED,
getTunnel().listenHost, port);
_context.portMapper().register(PortMapper.SVC_HTTPS_PROXY_TABBED,
getTunnel().listenHost, port);
}
}
/**
* Overridden to close internal socket too.
*/
@Override
public boolean close(boolean forced) {
int port = getLocalPort();
int reg = _context.portMapper().getPort(PortMapper.SVC_HTTP_PROXY_TABBED);
if (reg == port) {
_context.portMapper().unregister(PortMapper.SVC_HTTP_PROXY_TABBED);
}
reg = _context.portMapper().getPort(PortMapper.SVC_HTTPS_PROXY_TABBED);
if (reg == port) {
_context.portMapper().unregister(PortMapper.SVC_HTTPS_PROXY_TABBED);
}
boolean rv = super.close(forced);
if (this.isr != null) {
this.isr.stopRunning();
}
return rv;
}
private void mapPort(String hostname, int port) {
_context.portMapper().register(PortMapper.SVC_HTTP_PROXY_TABBED + "@" +
hostname,
getTunnel().listenHost, port);
_context.portMapper().register(PortMapper.SVC_HTTPS_PROXY_TABBED + "@" +
hostname,
getTunnel().listenHost, port);
}
private void unmapPort(String hostname) {
_context.portMapper().unregister(PortMapper.SVC_HTTP_PROXY_TABBED + "@" +
hostname);
_context.portMapper().unregister(PortMapper.SVC_HTTPS_PROXY_TABBED + "@" +
hostname);
}
private int findRandomOpenPort() throws IOException {
try (ServerSocket socket = new ServerSocket(0);) {
return socket.getLocalPort();
}
}
public I2PTunnelHTTPClient getI2PTunnelHTTPClient(URI uri) {
String hostname = uri.getHost();
return getI2PTunnelHTTPClient(hostname);
}
public I2PTunnelHTTPClient getI2PTunnelHTTPClient(String hostname) {
if (hostname == null)
return null;
return clients.get(hostname);
}
protected boolean mapNewClient(URI uri) {
String hostname = uri.getHost();
if (hostname == null)
return false;
Destination destination = _context.namingService().lookup(hostname);
if (destination == null)
return false;
try {
int port = findRandomOpenPort();
I2PTunnelHTTPClient client = new I2PTunnelHTTPClient(
port, l, _ownDest, hostname, getEventDispatcher(), getTunnel());
clients.put(destination, client);
getI2PTunnelHTTPClient(hostname).startRunning();
mapPort(hostname, port);
} catch (IOException e) {
throw new RuntimeException("Failed to find a random open port", e);
}
return true;
}
protected boolean unmapClient(URI uri) {
String hostname = uri.getHost();
if (hostname == null)
return false;
getI2PTunnelHTTPClient(hostname).close(true);
unmapPort(hostname);
return true;
}
@Override
protected void clientConnectionRun(Socket s) {
OutputStream out = null;
/**
* The URL after fixup, always starting with http:// or https://
*/
String targetRequest = null;
// in-net outproxy
boolean usingWWWProxy = false;
// local outproxy plugin
boolean usingInternalOutproxy = false;
Outproxy outproxy = null;
boolean usingInternalServer = false;
String internalPath = null;
String internalRawQuery = null;
String currentProxy = null;
long requestId = __requestId.incrementAndGet();
boolean shout = false;
boolean isConnect = false;
boolean isHead = false;
I2PSocket i2ps = null;
try {
s.setSoTimeout(INITIAL_SO_TIMEOUT);
out = s.getOutputStream();
InputReader reader = new InputReader(s.getInputStream());
int requestCount = 0;
// HTTP Persistent Connections (RFC 2616)
// for the local browser-to-client-proxy socket.
// Keep it very simple.
// Will be set to false for non-GET/HEAD, non-HTTP/1.1,
// Connection: close, InternalSocket,
// or after analysis of the response headers in
// HTTPResponseOutputStream, or on errors in I2PTunnelRunner.
boolean keepalive =
getBooleanOption(OPT_KEEPALIVE_BROWSER, DEFAULT_KEEPALIVE_BROWSER) &&
!(s instanceof InternalSocket);
// indent
do { // while (keepalive)
// indent
if (requestCount > 0) {
try {
s.setSoTimeout(BROWSER_KEEPALIVE_TIMEOUT);
} catch (IOException ioe) {
if (_log.shouldInfo())
_log.info("Socket closed before request #" + requestCount);
return;
}
if (_log.shouldInfo())
_log.info("Keepalive, awaiting request #" + requestCount);
}
String line, method = null, protocol = null, host = null,
destination = null;
String hostLowerCase = null;
StringBuilder newRequest = new StringBuilder();
boolean ahelperPresent = false;
boolean ahelperNew = false;
String ahelperKey = null;
String userAgent = null;
String authorization = null;
int remotePort = 0;
String referer = null;
URI origRequestURI = null;
boolean preserveConnectionHeader = false;
boolean allowGzip = false;
while ((line = reader.readLine(method)) != null) {
line = line.trim();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
}
String lowercaseLine = line.toLowerCase(Locale.US);
if (method == null) {
// first line GET/POST/etc.
if (_log.shouldInfo())
_log.info(getPrefix(requestId) + "req #" + requestCount +
" first line [" + line + "]");
String[] params = DataHelper.split(line, " ", 3);
if (params.length != 3) {
break;
}
String request = params[1];
// various obscure fixups
if (request.startsWith("/") &&
getTunnel().getClientOptions().getProperty(
"i2ptunnel.noproxy") != null) {
// what is this for ???
request = "http://i2p" + request;
} else if (request.startsWith("/eepproxy/")) {
// Deprecated
// /eepproxy/foo.i2p/bar/baz.html
String subRequest = request.substring("/eepproxy/".length());
if (subRequest.indexOf('/') == -1) {
subRequest += '/';
}
request = "http://" + subRequest;
/****
} else if
(request.toLowerCase(Locale.US).startsWith("http://i2p/"))
{
// http://i2p/b64key/bar/baz.html
// we can't do this now by setting the URI host to the
b64key, as
// it probably contains '=' and '~' which are illegal,
// and a host may not include escaped octets
// This will get undone below.
String subRequest =
request.substring("http://i2p/".length()); if
(subRequest.indexOf("/") == -1) subRequest += "/";
"http://" + "b64key/bar/baz.html"
request = "http://" + subRequest;
} else if
(request.toLowerCase(Locale.US).startsWith("http://"))
{
// Unsupported
// http://$b64key/...
// This probably used to work, rewrite it so that
// we can create a URI without illegal characters
// This will get undone below.
String oldPath = request.substring(7);
int slash = oldPath.indexOf("/");
if (slash < 0)
slash = oldPath.length();
if (slash >= 516 && !oldPath.substring(0,
slash).contains(".")) request = "http://i2p/" +
oldPath;
****/
}
method = params[0].toUpperCase(Locale.US);
if (method.equals("HEAD")) {
isHead = true;
} else if (method.equals("CONNECT")) {
// this makes things easier later, by spoofing a
// protocol so the URI parser find the host and port
// For in-net outproxy, will be fixed up below
request = "https://" + request + '/';
isConnect = true;
keepalive = false;
} else if (!method.equals("GET")) {
// POST, PUT, ...
keepalive = false;
}
// Now use the Java URI parser
// This will be the incoming URI but will then get modified
// to be the outgoing URI (with http:// if going to
// outproxy, otherwise without)
URI requestURI = null;
try {
try {
requestURI = new URI(request);
} catch (URISyntaxException use) {
// fixup []| in path/query not escaped by browsers,
// see ticket #2130
boolean error = true;
// find 3rd /
int idx = 0;
for (int i = 0; i < 2; i++) {
idx = request.indexOf('/', idx);
if (idx < 0)
break;
idx++;
}
if (idx > 0) {
String schemeHostPort = request.substring(0, idx);
String rest = request.substring(idx);
// not escaped by all browsers, may be specific
// to query, see ticket #2130
rest = rest.replace("[", "%5B");
rest = rest.replace("]", "%5D");
rest = rest.replace("|", "%7C");
rest = rest.replace("{", "%7B");
rest = rest.replace("}", "%7D");
String testRequest = schemeHostPort + rest;
if (!testRequest.equals(request)) {
try {
requestURI = new URI(testRequest);
request = testRequest;
error = false;
} catch (URISyntaxException use2) {
// didn't work, give up
}
}
}
// guess it wasn't []|
if (error)
throw use;
}
origRequestURI = requestURI;
String hostName = requestURI.getHost();
if (hostName != null) {
host = hostName;
hostLowerCase = host.toLowerCase(Locale.US);
}
/**
* This is where the host-specific logic happens
*/
} catch (URISyntaxException use) {
if (_log.shouldLog(Log.WARN)) {
_log.warn(getPrefix(requestId) + "Bad request [" + request +
"]",
use);
}
try {
out.write(
getErrorPage("baduri", ERR_BAD_URI).getBytes("UTF-8"));
String msg = use.getLocalizedMessage();
if (msg != null) {
out.write(DataHelper.getASCII("<p>\n"));
out.write(DataHelper.getUTF8(DataHelper.escapeHTML(msg)));
out.write(DataHelper.getASCII("</p>\n"));
}
out.write(DataHelper.getASCII("</div>\n"));
writeFooter(out);
reader.drain();
} catch (IOException ioe) {
// ignore
}
return;
}
}
}
} while (keepalive);
} catch (IOException ex) {
// This is normal for keepalive when the browser closed the socket,
// or a SocketTimeoutException if we gave up first
if (_log.shouldLog(Log.INFO)) {
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
}
handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy,
requestId);
/*} catch(I2PException ex) {
if(_log.shouldLog(Log.INFO)) {
_log.info(getPrefix(requestId) + "Error trying to connect",
ex);
}
handleClientException(ex, out, targetRequest, usingWWWProxy,
currentProxy, requestId);*/
/*} catch(OutOfMemoryError oom) {
IOException ex = new IOException("OOM");
_log.error(getPrefix(requestId) + "Error trying to connect", oom);
handleClientException(ex, out, targetRequest, usingWWWProxy,
currentProxy, requestId);*/
} finally {
// only because we are running it inline
closeSocket(s);
if (i2ps != null)
try {
i2ps.close();
} catch (IOException ioe) {
}
}
}
}

View File

@ -192,7 +192,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
"The request uses a bad protocol. " +
"The I2P HTTP Proxy supports HTTP and HTTPS requests only. Other protocols such as FTP are not allowed.<BR>";
private final static String ERR_BAD_URI =
protected final static String ERR_BAD_URI =
"HTTP/1.1 403 Bad URI\r\n" +
"Content-Type: text/html; charset=iso-8859-1\r\n" +
"Cache-Control: no-cache\r\n" +
@ -1687,7 +1687,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
* Warning - DataHelper limits line length, BufferedReader does not
* Todo: Limit line length for buffered reads, or go back to unbuffered for all
*/
private static class InputReader {
protected static class InputReader {
InputStream _s;
public InputReader(InputStream s) {

View File

@ -155,6 +155,7 @@ public class TunnelController implements Logging {
public static final String TYPE_CONNECT = "connectclient";
public static final String TYPE_HTTP_BIDIR_SERVER = "httpbidirserver";
public static final String TYPE_HTTP_CLIENT = "httpclient";
public static final String TYPE_BROWSER_HTTP_CLIENT = "browserclient";
public static final String TYPE_HTTP_SERVER = "httpserver";
public static final String TYPE_IRC_CLIENT = "ircclient";
public static final String TYPE_IRC_SERVER = "ircserver";
@ -470,6 +471,8 @@ public class TunnelController implements Logging {
setSessionOptions();
if (TYPE_HTTP_CLIENT.equals(type)) {
startHttpClient();
} else if (TYPE_BROWSER_HTTP_CLIENT.equals(type)) {
startBrowserClient();
} else if(TYPE_IRC_CLIENT.equals(type)) {
startIrcClient();
} else if(TYPE_SOCKS.equals(type)) {
@ -519,6 +522,17 @@ public class TunnelController implements Logging {
_tunnel.runHttpClient(new String[] { listenPort, sharedClient, proxyList }, this);
}
private void startBrowserClient() {
setListenOn();
String listenPort = getListenPort();
String proxyList = getProxyList();
String sharedClient = getSharedClient();
if (proxyList == null)
_tunnel.runBrowserClient(new String[] { listenPort, sharedClient }, this);
else
_tunnel.runBrowserClient(new String[] { listenPort, sharedClient, proxyList }, this);
}
private void startConnectClient() {
setListenOn();
String listenPort = getListenPort();
@ -1072,6 +1086,7 @@ public class TunnelController implements Logging {
public static boolean isClient(String type) {
return TYPE_STD_CLIENT.equals(type) ||
TYPE_HTTP_CLIENT.equals(type) ||
TYPE_BROWSER_HTTP_CLIENT.equals(type) ||
TYPE_SOCKS.equals(type) ||
TYPE_SOCKS_IRC.equals(type) ||
TYPE_CONNECT.equals(type) ||

View File

@ -493,6 +493,7 @@ public class IndexBean {
public String getTypeName(String internalType) {
if (TunnelController.TYPE_STD_CLIENT.equals(internalType)) return _t("Standard client");
else if (TunnelController.TYPE_HTTP_CLIENT.equals(internalType)) return _t("HTTP/HTTPS client");
else if (TunnelController.TYPE_BROWSER_HTTP_CLIENT.equals(internalType)) return _t("HTTP/HTTPS client");
else if (TunnelController.TYPE_IRC_CLIENT.equals(internalType)) return _t("IRC client");
else if (TunnelController.TYPE_STD_SERVER.equals(internalType)) return _t("Standard server");
else if (TunnelController.TYPE_HTTP_SERVER.equals(internalType)) return _t("HTTP server");

View File

@ -375,6 +375,7 @@
<select name="type">
<option value="client"><%=intl._t("Standard")%></option>
<option value="httpclient">HTTP/CONNECT</option>
<option value="browserclient">Tabbed HTTP/CONNECT</option>
<option value="ircclient">IRC</option>
<option value="sockstunnel">SOCKS 4/4a/5</option>
<option value="socksirctunnel">SOCKS IRC</option>

View File

@ -185,6 +185,11 @@
<%=intl._t("Set your browser to use this tunnel as an http proxy, or set your \"http_proxy\" environment variable for command-line applications in GNU/Linux.")%>
<%=intl._t("Websites outside I2P can also be reached if an HTTP proxy within I2P is known.")%>
</td></tr>
<tr><td><%=intl._t("Tabbed")%> HTTP/HTTPS</td><td>
<%=intl._t("Like the HTTP/HTTPS tunnel, but designed to isolate each new origin under a new destination.")%>
<%=intl._t("Especially useful when combined with a browser which displays multiple tabs.")%>
<%=intl._t("Set your browser to use this tunnel as an http proxy, or set your \"http_proxy\" environment variable for command-line applications in GNU/Linux.")%>
</td></tr>
<tr><td>IRC</td><td>
<%=intl._t("Customized client tunnel specific for IRC connections.")%>
<%=intl._t("With this tunnel type, your IRC client will be able to connect to an IRC network inside I2P.")%>

View File

@ -29,6 +29,8 @@ public class PortMapper {
public static final String SVC_HTTPS_CONSOLE = "https_console";
public static final String SVC_HTTP_PROXY = "HTTP";
public static final String SVC_HTTPS_PROXY = "HTTPS";
public static final String SVC_HTTP_PROXY_TABBED = "HTTP_TABBED";
public static final String SVC_HTTPS_PROXY_TABBED = "HTTPS_TABBED";
public static final String SVC_EEPSITE = "eepsite";
/** @since 0.9.34 */
public static final String SVC_HTTPS_EEPSITE = "https_eepsite";