forked from I2P_Developers/i2p.i2p
- Refactoring to use Jave URI parser to better handle
escapes, IPv6 addresses, ports - Rewrite i2paddresshelper scanning/removal intermediate checkin, bug fixes to follow
This commit is contained in:
@ -13,6 +13,8 @@ import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@ -61,6 +63,10 @@ import net.i2p.util.Translate;
|
||||
* in browsers or other user-visible applications, as relative links will not
|
||||
* resolve correctly, cookies won't work, etc.
|
||||
*
|
||||
* Note that http://$b64key/... and http://$b64key.i2p/... are NOT supported, as
|
||||
* a b64 key may contain '=' and '~', both of which are illegal host name characters.
|
||||
* Rewrite as http://i2p/$b64key/...
|
||||
*
|
||||
* If the $site resolves with the I2P naming service, then it is directed towards
|
||||
* that eepsite, otherwise it is directed towards this client's outproxy (typically
|
||||
* "squid.i2p"). Only HTTP is supported (no HTTPS, ftp, mailto, etc). Both GET
|
||||
@ -309,6 +315,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static final String HELPER_PARAM = "i2paddresshelper";
|
||||
public static final String LOCAL_SERVER = "proxy.i2p";
|
||||
private static final boolean DEFAULT_GZIP = true;
|
||||
/** all default to false */
|
||||
@ -321,11 +328,19 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
|
||||
/**
|
||||
* The URL after fixup, always starting with http://
|
||||
*/
|
||||
String targetRequest = null;
|
||||
|
||||
boolean usingWWWProxy = false;
|
||||
boolean usingInternalServer = false;
|
||||
String internalPath = null;
|
||||
String internalRawQuery = null;
|
||||
String currentProxy = null;
|
||||
long requestId = ++__requestId;
|
||||
|
||||
try {
|
||||
out = s.getOutputStream();
|
||||
InputReader reader = new InputReader(s.getInputStream());
|
||||
@ -351,79 +366,84 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "First line [" + line + "]");
|
||||
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1) break;
|
||||
method = line.substring(0, pos);
|
||||
// TODO use Java URL class to make all this simpler and more robust
|
||||
// That will also fix IPV6 [a:b:c]
|
||||
String request = line.substring(pos + 1);
|
||||
String[] params = line.split(" ", 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/")) {
|
||||
// /eepproxy/foo.i2p/bar/baz.html HTTP/1.0
|
||||
// Deprecated
|
||||
// /eepproxy/foo.i2p/bar/baz.html
|
||||
String subRequest = request.substring("/eepproxy/".length());
|
||||
int protopos = subRequest.indexOf(" ");
|
||||
String uri = subRequest.substring(0, protopos);
|
||||
if (uri.indexOf("/") == -1) {
|
||||
uri = uri + "/";
|
||||
}
|
||||
// "http://" + "foo.i2p/bar/baz.html" + " HTTP/1.0"
|
||||
request = "http://" + uri + subRequest.substring(protopos);
|
||||
if (subRequest.indexOf("/") == -1)
|
||||
subRequest += "/";
|
||||
request = "http://" + subRequest;
|
||||
/****
|
||||
} else if (request.toLowerCase(Locale.US).startsWith("http://i2p/")) {
|
||||
// http://i2p/b64key/bar/baz.html HTTP/1.0
|
||||
// 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());
|
||||
int protopos = subRequest.indexOf(" ");
|
||||
String uri = subRequest.substring(0, protopos);
|
||||
if (uri.indexOf("/") == -1) {
|
||||
uri = uri + "/";
|
||||
}
|
||||
// "http://" + "b64key/bar/baz.html" + " HTTP/1.0"
|
||||
request = "http://" + uri + subRequest.substring(protopos);
|
||||
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;
|
||||
****/
|
||||
}
|
||||
|
||||
pos = request.indexOf("//");
|
||||
if (pos == -1) {
|
||||
// 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;
|
||||
try {
|
||||
requestURI = new URI(request);
|
||||
if (requestURI.getRawUserInfo() != null || requestURI.getRawFragment() != null) {
|
||||
// these should never be sent to the proxy in the request line
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Removing userinfo or fragment [" + request + "]");
|
||||
requestURI = changeURI(requestURI, null, 0, null);
|
||||
}
|
||||
if (requestURI.getPath() == null || requestURI.getPath().length() <= 0) {
|
||||
// Add a path
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Adding / path to [" + request + "]");
|
||||
requestURI = changeURI(requestURI, null, 0, "/");
|
||||
}
|
||||
} catch (URISyntaxException use) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Bad request [" + request + "]", use);
|
||||
break;
|
||||
}
|
||||
method = params[0];
|
||||
String protocolVersion = params[2];
|
||||
|
||||
protocol = requestURI.getScheme();
|
||||
host = requestURI.getHost();
|
||||
if (protocol == null || host == null) {
|
||||
_log.warn(request);
|
||||
method = null;
|
||||
break;
|
||||
}
|
||||
protocol = request.substring(0, pos + 2);
|
||||
request = request.substring(pos + 2);
|
||||
|
||||
// "foo.i2p/bar/baz HTTP/1.1", with any i2paddresshelper parameter removed
|
||||
targetRequest = request;
|
||||
|
||||
// pos is the start of the path
|
||||
pos = request.indexOf("/");
|
||||
if (pos == -1) {
|
||||
//pos = request.length();
|
||||
method = null;
|
||||
break;
|
||||
}
|
||||
host = request.substring(0, pos);
|
||||
|
||||
// parse port
|
||||
int posPort = host.indexOf(":");
|
||||
int port = 80;
|
||||
if(posPort != -1) {
|
||||
String[] parts = host.split(":");
|
||||
try {
|
||||
host = parts[0];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
if (out != null) {
|
||||
out.write(getErrorPage("denied", ERR_REQUEST_DENIED));
|
||||
writeFooter(out);
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
|
||||
}
|
||||
try {
|
||||
port = Integer.parseInt(parts[1]);
|
||||
} catch(Exception exc) {
|
||||
// TODO: log this
|
||||
}
|
||||
}
|
||||
int port = requestURI.getPort();
|
||||
|
||||
// Go through the various types of host names, set
|
||||
// the host and destination variables accordingly,
|
||||
@ -433,115 +453,141 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
// in our addressbook (all naming is local),
|
||||
// and it is removed from the request line.
|
||||
|
||||
if (host.length() >= 516 && host.indexOf(".") < 0) {
|
||||
// http://b64key/bar/baz.html
|
||||
destination = host;
|
||||
host = getHostName(destination);
|
||||
line = method + ' ' + request.substring(pos);
|
||||
} else if (host.toLowerCase(Locale.US).equals(LOCAL_SERVER)) {
|
||||
String hostLowerCase = host.toLowerCase(Locale.US);
|
||||
if (hostLowerCase.equals(LOCAL_SERVER)) {
|
||||
// so we don't do any naming service lookups
|
||||
destination = host;
|
||||
usingInternalServer = true;
|
||||
} else if (host.toLowerCase(Locale.US).endsWith(".i2p")) {
|
||||
internalPath = requestURI.getPath();
|
||||
internalRawQuery = requestURI.getRawQuery();
|
||||
} else if (hostLowerCase.equals("i2p")) {
|
||||
// pull the b64 dest out of the first path element
|
||||
String oldPath = requestURI.getPath().substring(1);
|
||||
int slash = oldPath.indexOf("/");
|
||||
if (slash < 0) {
|
||||
slash = oldPath.length();
|
||||
oldPath += "/";
|
||||
}
|
||||
String dest = oldPath.substring(0, slash);
|
||||
if (slash >= 516 && !dest.contains(".")) {
|
||||
// possible alternative:
|
||||
// redirect to b32
|
||||
destination = dest;
|
||||
host = getHostName(destination);
|
||||
targetRequest = requestURI.toASCIIString();
|
||||
String newPath = dest.substring(slash);
|
||||
String newURI = requestURI.getRawPath();
|
||||
String query = requestURI.getRawQuery();
|
||||
if (query != null)
|
||||
newURI += '?' + query;
|
||||
try {
|
||||
requestURI = new URI(newURI);
|
||||
} catch (URISyntaxException use) {
|
||||
// shouldnt happen
|
||||
_log.warn(request, use);
|
||||
method = null;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
_log.warn(request);
|
||||
host = null;
|
||||
break;
|
||||
}
|
||||
} else if (hostLowerCase.endsWith(".i2p")) {
|
||||
// Destination gets the host name
|
||||
destination = host;
|
||||
// Host becomes the destination's "{b32}.b32.i2p" string, or "i2p" on lookup failure
|
||||
host = getHostName(destination);
|
||||
|
||||
int pos2;
|
||||
if ((pos2 = request.indexOf("?")) != -1) {
|
||||
// Try to find an address helper in the fragments
|
||||
// and split the request into it's component parts for rebuilding later
|
||||
if (requestURI.getPort() >= 0) {
|
||||
// TODO support I2P ports someday
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Removing port from [" + request + "]");
|
||||
try {
|
||||
requestURI = changeURI(requestURI, null, -1, null);
|
||||
} catch (URISyntaxException use) {
|
||||
_log.warn(request, use);
|
||||
method = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String query = requestURI.getRawQuery();
|
||||
if (query != null) {
|
||||
boolean ahelperConflict = false;
|
||||
|
||||
String fragments = request.substring(pos2 + 1);
|
||||
String uriPath = request.substring(0, pos2);
|
||||
pos2 = fragments.indexOf(" ");
|
||||
String protocolVersion = fragments.substring(pos2 + 1);
|
||||
String urlEncoding = "";
|
||||
fragments = fragments.substring(0, pos2);
|
||||
String initialFragments = fragments;
|
||||
// FIXME split on ';' also
|
||||
fragments = fragments + "&";
|
||||
String fragment;
|
||||
while(fragments.length() > 0) {
|
||||
pos2 = fragments.indexOf("&");
|
||||
fragment = fragments.substring(0, pos2);
|
||||
fragments = fragments.substring(pos2 + 1);
|
||||
// Try to find an address helper in the query
|
||||
String[] helperStrings = removeHelper(query);
|
||||
if (helperStrings != null &&
|
||||
!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) {
|
||||
query = helperStrings[0];
|
||||
if (query.equals(""))
|
||||
query = null;
|
||||
try {
|
||||
requestURI = replaceQuery(requestURI, query);
|
||||
} catch (URISyntaxException use) {
|
||||
// shouldn't happen
|
||||
_log.warn(request, use);
|
||||
method = null;
|
||||
break;
|
||||
}
|
||||
ahelperKey = helperStrings[1];
|
||||
// Key contains data, lets not ignore it
|
||||
if (ahelperKey.length() > 0) {
|
||||
if(ahelperKey.endsWith(".i2p")) {
|
||||
// allow i2paddresshelper=<b32>.b32.i2p syntax.
|
||||
/*
|
||||
also i2paddresshelper=name.i2p for aliases
|
||||
i.e. on your eepsite put
|
||||
<a href="?i2paddresshelper=name.i2p">This is the name I want to be called.</a>
|
||||
*/
|
||||
Destination dest = _context.namingService().lookup(ahelperKey);
|
||||
if(dest==null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Could not find destination for "+ahelperKey);
|
||||
byte[] header = getErrorPage("ahelper-notfound", ERR_AHELPER_NOTFOUND);
|
||||
out.write(header);
|
||||
out.write(("<p>" + _("This seems to be a bad destination:") + " " + ahelperKey + " " + _("i2paddresshelper cannot help you with a destination like that!") + "</p>").getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
// XXX: should closeSocket(s) be in a finally block?
|
||||
closeSocket(s);
|
||||
return;
|
||||
}
|
||||
ahelperKey = dest.toBase64();
|
||||
}
|
||||
|
||||
// Fragment looks like addresshelper key
|
||||
if (fragment.startsWith("i2paddresshelper=") &&
|
||||
!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) {
|
||||
pos2 = fragment.indexOf("=");
|
||||
ahelperKey = fragment.substring(pos2 + 1);
|
||||
// Key contains data, lets not ignore it
|
||||
if (ahelperKey != null) {
|
||||
if(ahelperKey.endsWith(".i2p")) {
|
||||
// allow i2paddresshelper=<b32>.b32.i2p syntax.
|
||||
/*
|
||||
also i2paddresshelper=name.i2p for aliases
|
||||
i.e. on your eepsite put
|
||||
<a href="?i2paddresshelper=name.i2p">This is the name I want to be called.</a>
|
||||
*/
|
||||
Destination dest = _context.namingService().lookup(ahelperKey);
|
||||
if(dest==null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Could not find destination for "+ahelperKey);
|
||||
byte[] header = getErrorPage("ahelper-notfound", ERR_AHELPER_NOTFOUND);
|
||||
out.write(header);
|
||||
out.write(("<p>" + _("This seems to be a bad destination:") + " " + ahelperKey + " " + _("i2paddresshelper cannot help you with a destination like that!") + "</p>").getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
// XXX: should closeSocket(s) be in a finally block?
|
||||
closeSocket(s);
|
||||
return;
|
||||
}
|
||||
ahelperKey = dest.toBase64();
|
||||
}
|
||||
|
||||
ahelperPresent = true;
|
||||
// ahelperKey will be validated later
|
||||
if (host == null || "i2p".equals(host)) {
|
||||
// Host lookup failed - resolvable only with addresshelper
|
||||
// Store in local HashMap unless there is conflict
|
||||
String old = addressHelpers.putIfAbsent(destination.toLowerCase(Locale.US), ahelperKey);
|
||||
ahelperNew = old == null;
|
||||
if ((!ahelperNew) && !old.equals(ahelperKey)) {
|
||||
ahelperPresent = true;
|
||||
// ahelperKey will be validated later
|
||||
if (host == null || "i2p".equals(host)) {
|
||||
// Host lookup failed - resolvable only with addresshelper
|
||||
// Store in local HashMap unless there is conflict
|
||||
String old = addressHelpers.putIfAbsent(destination.toLowerCase(Locale.US), ahelperKey);
|
||||
ahelperNew = old == null;
|
||||
if ((!ahelperNew) && !old.equals(ahelperKey)) {
|
||||
// Conflict: handle when URL reconstruction done
|
||||
ahelperConflict = true;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination +
|
||||
"], trusted key [" + old + "], specified key [" + ahelperKey + "].");
|
||||
}
|
||||
} else {
|
||||
// If the host is resolvable from database, verify addresshelper key
|
||||
// Silently bypass correct keys, otherwise alert
|
||||
Destination hostDest = _context.namingService().lookup(destination);
|
||||
if (hostDest != null) {
|
||||
String destB64 = hostDest.toBase64();
|
||||
if (destB64 != null && !destB64.equals(ahelperKey)) {
|
||||
// Conflict: handle when URL reconstruction done
|
||||
ahelperConflict = true;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination +
|
||||
"], trusted key [" + old + "], specified key [" + ahelperKey + "].");
|
||||
}
|
||||
} else {
|
||||
// If the host is resolvable from database, verify addresshelper key
|
||||
// Silently bypass correct keys, otherwise alert
|
||||
Destination hostDest = _context.namingService().lookup(destination);
|
||||
if (hostDest != null) {
|
||||
String destB64 = hostDest.toBase64();
|
||||
if (destB64 != null && !destB64.equals(ahelperKey)) {
|
||||
// Conflict: handle when URL reconstruction done
|
||||
ahelperConflict = true;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination +
|
||||
"], trusted key [" + destB64 + "], specified key [" + ahelperKey + "].");
|
||||
|
||||
}
|
||||
"], trusted key [" + destB64 + "], specified key [" + ahelperKey + "].");
|
||||
|
||||
}
|
||||
}
|
||||
} // ahelperKey
|
||||
} else {
|
||||
// Other fragments, just pass along
|
||||
// Append each fragment to urlEncoding
|
||||
if ("".equals(urlEncoding)) {
|
||||
urlEncoding = "?" + fragment;
|
||||
} else {
|
||||
urlEncoding = urlEncoding + "&" + fragment;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reconstruct the request minus the i2paddresshelper GET var
|
||||
request = uriPath + urlEncoding + " " + protocolVersion;
|
||||
targetRequest = request;
|
||||
} // ahelperKey
|
||||
} // helperstrings
|
||||
|
||||
// Did addresshelper key conflict?
|
||||
if (ahelperConflict) {
|
||||
@ -553,9 +599,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
byte[] header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN);
|
||||
writeErrorMessage(header, out, targetRequest, false, destination, null);
|
||||
} else {
|
||||
String trustedURL = protocol + uriPath + urlEncoding;
|
||||
// Fixme - any path is lost
|
||||
String conflictURL = protocol + alias + '/' + urlEncoding;
|
||||
String trustedURL = requestURI.toASCIIString();
|
||||
URI conflictURI;
|
||||
try {
|
||||
conflictURI = changeURI(requestURI, alias, 0, null);
|
||||
} catch (URISyntaxException use) {
|
||||
// shouldn't happen
|
||||
_log.warn(request, use);
|
||||
method = null;
|
||||
break;
|
||||
}
|
||||
String conflictURL = conflictURI.toASCIIString();
|
||||
byte[] header = getErrorPage("ahelper-conflict", ERR_AHELPER_CONFLICT);
|
||||
out.write(header);
|
||||
out.write(_("To visit the destination in your host database, click <a href=\"{0}\">here</a>. To visit the conflicting addresshelper destination, click <a href=\"{1}\">here</a>.", trustedURL, conflictURL).getBytes("UTF-8"));
|
||||
@ -572,11 +626,24 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
if (addressHelper != null)
|
||||
host = getHostName(addressHelper);
|
||||
|
||||
line = method + " " + request.substring(pos);
|
||||
// now strip everything but path and query from URI
|
||||
targetRequest = requestURI.toASCIIString();
|
||||
String newURI = requestURI.getRawPath();
|
||||
if (query != null)
|
||||
newURI += '?' + query;
|
||||
try {
|
||||
requestURI = new URI(newURI);
|
||||
} catch (URISyntaxException use) {
|
||||
// shouldnt happen
|
||||
_log.warn(request, use);
|
||||
method = null;
|
||||
break;
|
||||
}
|
||||
|
||||
// end of (host endsWith(".i2p"))
|
||||
|
||||
} else if (host.toLowerCase(Locale.US).equals("localhost") || host.equals("127.0.0.1") ||
|
||||
host.startsWith("192.168.")) {
|
||||
} else if (hostLowerCase.equals("localhost") || host.equals("127.0.0.1") ||
|
||||
host.startsWith("192.168.") || host.equals("[::1]")) {
|
||||
// if somebody is trying to get to 192.168.example.com, oh well
|
||||
if (out != null) {
|
||||
out.write(getErrorPage("localhost", ERR_LOCALHOST));
|
||||
@ -585,7 +652,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
s.close();
|
||||
return;
|
||||
} else if (host.indexOf(".") != -1) {
|
||||
// rebuild host
|
||||
host = host + ":" + port;
|
||||
// The request must be forwarded to a WWW proxy
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -606,37 +672,21 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
}
|
||||
destination = currentProxy;
|
||||
usingWWWProxy = true;
|
||||
targetRequest = requestURI.toASCIIString();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "Host doesnt end with .i2p and it contains a period [" + host + "]: wwwProxy!");
|
||||
_log.debug(getPrefix(requestId) + " [" + host + "]: wwwProxy!");
|
||||
} else {
|
||||
// what is left for here? a hostname with no dots, and != "i2p"
|
||||
// and not a destination ???
|
||||
// Perhaps something in privatehosts.txt ...
|
||||
request = request.substring(pos + 1);
|
||||
pos = request.indexOf("/");
|
||||
if (pos < 0) {
|
||||
l.log("Invalid request url [" + request + "]");
|
||||
if (out != null) {
|
||||
out.write(getErrorPage("denied", ERR_REQUEST_DENIED));
|
||||
writeFooter(out);
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
destination = request.substring(0, pos);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("NODOTS, NOI2P: " + request);
|
||||
destination = requestURI.getHost();
|
||||
host = getHostName(destination);
|
||||
line = method + " " + request.substring(pos);
|
||||
targetRequest = requestURI.toASCIIString();
|
||||
// FIXME treat as I2P or not???
|
||||
} // end host name processing
|
||||
|
||||
if (port != 80 && !usingWWWProxy) {
|
||||
if (out != null) {
|
||||
out.write(getErrorPage("denied", ERR_REQUEST_DENIED));
|
||||
writeFooter(out);
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isValid = usingWWWProxy || usingInternalServer || isSupportedAddress(host, protocol);
|
||||
if (!isValid) {
|
||||
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix(requestId) + "notValid(" + host + ")");
|
||||
@ -645,18 +695,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
break;
|
||||
}
|
||||
|
||||
// don't do this, it forces yet another hostname lookup,
|
||||
// and in all cases host was already set above
|
||||
//if ((!usingWWWProxy) && (!usingInternalServer)) {
|
||||
// String oldhost = host;
|
||||
// host = getHostName(destination); // hide original host
|
||||
// if (_log.shouldLog(Log.INFO))
|
||||
// _log.info(getPrefix(requestId) + " oldhost " + oldhost + " newhost " + host + " dest " + destination);
|
||||
//}
|
||||
line = method + ' ' + requestURI.toASCIIString() + ' ' + protocolVersion;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getPrefix(requestId) + "METHOD: \"" + method + "\"");
|
||||
_log.debug(getPrefix(requestId) + "PROTOC: \"" + protocol + "\"");
|
||||
_log.debug(getPrefix(requestId) + "NEWREQ: \"" + line + "\"");
|
||||
_log.debug(getPrefix(requestId) + "HOST : \"" + host + "\"");
|
||||
_log.debug(getPrefix(requestId) + "DEST : \"" + destination + "\"");
|
||||
}
|
||||
@ -763,7 +805,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
if (method == null || destination == null) {
|
||||
//l.log("No HTTP method found in the request.");
|
||||
if (out != null) {
|
||||
if (protocol != null && "http://".equals(protocol.toLowerCase(Locale.US)))
|
||||
if (protocol != null && "http".equals(protocol.toLowerCase(Locale.US)))
|
||||
out.write(getErrorPage("denied", ERR_REQUEST_DENIED));
|
||||
else
|
||||
out.write(getErrorPage("protocol", ERR_BAD_PROTOCOL));
|
||||
@ -794,11 +836,11 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
// Ignore all the headers
|
||||
if (usingInternalServer) {
|
||||
// disable the add form if address helper is disabled
|
||||
if (targetRequest.startsWith(LOCAL_SERVER + "/add?") &&
|
||||
if (internalPath.equals("/add") &&
|
||||
Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) {
|
||||
out.write(ERR_HELPER_DISABLED);
|
||||
} else {
|
||||
LocalHTTPServer.serveLocalFile(out, method, targetRequest, _proxyNonce);
|
||||
LocalHTTPServer.serveLocalFile(out, method, internalPath, internalRawQuery, _proxyNonce);
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
@ -865,7 +907,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
if (ahelperNew && "GET".equals(method) &&
|
||||
(userAgent == null || !userAgent.startsWith("Wget")) &&
|
||||
!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) {
|
||||
writeHelperSaveForm(out, destination, ahelperKey, protocol + targetRequest);
|
||||
writeHelperSaveForm(out, destination, ahelperKey, targetRequest);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
@ -875,10 +917,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
// This also prevents the not-found error page from looking bad
|
||||
// Syndie can't handle a redirect of a POST
|
||||
if (ahelperPresent && !"POST".equals(method)) {
|
||||
String uri = protocol + targetRequest;
|
||||
int spc = uri.indexOf(" ");
|
||||
if (spc >= 0)
|
||||
uri = uri.substring(0, spc);
|
||||
String uri = targetRequest;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Auto redirecting to " + uri);
|
||||
out.write(("HTTP/1.1 301 Address Helper Accepted\r\n"+
|
||||
@ -928,10 +967,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
private void writeHelperSaveForm(OutputStream out, String destination, String ahelperKey, String targetRequest) throws IOException {
|
||||
if (out == null)
|
||||
return;
|
||||
// strip HTTP/1.1
|
||||
int protopos = targetRequest.indexOf(" ");
|
||||
if (protopos >= 0)
|
||||
targetRequest = targetRequest.substring(0, protopos);
|
||||
byte[] header = getErrorPage("ahelper-new", ERR_AHELPER_NEW);
|
||||
out.write(header);
|
||||
out.write(("<table><tr><td class=\"mediumtags\" align=\"right\">" + _("Host") + "</td><td class=\"mediumtags\">" + destination + "</td></tr>\n" +
|
||||
@ -939,6 +974,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"<textarea rows=\"1\" style=\"height: 4em; min-width: 0; min-height: 0;\" cols=\"70\" wrap=\"off\" readonly=\"readonly\" >" +
|
||||
ahelperKey + "</textarea></td></tr></table>\n" +
|
||||
"<hr><div class=\"formaction\">"+
|
||||
// FIXME if there is a query remaining it is lost
|
||||
"<form method=\"GET\" action=\"" + targetRequest + "\">" +
|
||||
"<button type=\"submit\" class=\"go\">" + _("Continue to {0} without saving", destination) + "</button>" +
|
||||
"</form>\n<form method=\"GET\" action=\"http://" + LOCAL_SERVER + "/add\">" +
|
||||
@ -1094,15 +1130,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
if (out != null) {
|
||||
out.write(errMessage);
|
||||
if (targetRequest != null) {
|
||||
int protopos = targetRequest.indexOf(" ");
|
||||
String uri;
|
||||
if (protopos >= 0)
|
||||
uri = targetRequest.substring(0, protopos);
|
||||
else
|
||||
uri = targetRequest;
|
||||
out.write("<a href=\"http://".getBytes());
|
||||
String uri = targetRequest;
|
||||
out.write("<a href=\"".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("\">http://".getBytes());
|
||||
out.write("\">".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("</a>".getBytes());
|
||||
if (usingWWWProxy) {
|
||||
@ -1196,7 +1227,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
}
|
||||
}
|
||||
****/
|
||||
return protocol.toLowerCase(Locale.US).equals("http://");
|
||||
return protocol.toLowerCase(Locale.US).equals("http");
|
||||
}
|
||||
|
||||
private final static byte[] ERR_HELPER_DISABLED =
|
||||
@ -1206,6 +1237,128 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"Address helpers disabled")
|
||||
.getBytes();
|
||||
|
||||
/**
|
||||
* Change various parts of the URI.
|
||||
* String parameters are all non-encoded.
|
||||
*
|
||||
* Scheme always preserved.
|
||||
* Userinfo always cleared.
|
||||
* Host changed if non-null.
|
||||
* Port changed if non-zero.
|
||||
* Path changed if non-null.
|
||||
* Query always preserved.
|
||||
* Fragment always cleared.
|
||||
*
|
||||
* @since 0.9
|
||||
*/
|
||||
private static URI changeURI(URI uri, String host, int port, String path) throws URISyntaxException {
|
||||
return new URI(uri.getScheme(),
|
||||
null,
|
||||
host != null ? host : uri.getHost(),
|
||||
port != 0 ? port : uri.getPort(),
|
||||
path != null ? path : uri.getPath(),
|
||||
// FIXME this breaks encoded =, &
|
||||
uri.getQuery(),
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace query in the URI.
|
||||
* Userinfo cleared if uri contained a query.
|
||||
* Fragment cleared if uri contained a query.
|
||||
*
|
||||
* @param query an ENCODED query, removed if null
|
||||
* @since 0.9
|
||||
*/
|
||||
private static URI replaceQuery(URI uri, String query) throws URISyntaxException {
|
||||
URI rv = uri;
|
||||
if (rv.getRawQuery() != null) {
|
||||
rv = new URI(rv.getScheme(),
|
||||
null,
|
||||
uri.getHost(),
|
||||
uri.getPort(),
|
||||
uri.getPath(),
|
||||
null,
|
||||
null);
|
||||
}
|
||||
if (query != null) {
|
||||
String newURI = rv.toASCIIString() + '?' + query;
|
||||
rv = new URI(newURI);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the address helper from an encoded query.
|
||||
*
|
||||
* @param query an ENCODED query, removed if null
|
||||
* @return rv[0] is ENCODED query with helper removed, non-null but possibly empty;
|
||||
* rv[1] is DECODED helper value, non-null but possibly empty;
|
||||
* rv null if no helper present
|
||||
* @since 0.9
|
||||
*/
|
||||
private static String[] removeHelper(String query) {
|
||||
int keystart = 0;
|
||||
int valstart = -1;
|
||||
String key = null;
|
||||
for (int i = 0; i <= query.length(); i++) {
|
||||
char c = i < query.length() ? query.charAt(i) : '&';
|
||||
if (c == ';' || c == '&') {
|
||||
// end of key or value
|
||||
if (valstart < 0)
|
||||
key = query.substring(keystart, i);
|
||||
String decodedKey = LocalHTTPServer.decode(key);
|
||||
if (decodedKey.equals(HELPER_PARAM)) {
|
||||
String newQuery = keystart > 0 ? query.substring(0, keystart - 1) : "";
|
||||
if (i < query.length() - 1) {
|
||||
if (keystart > 0)
|
||||
newQuery += query.substring(i);
|
||||
else
|
||||
newQuery += query.substring(i + 1);
|
||||
}
|
||||
String value = valstart >= 0 ? query.substring(valstart, i) : "";
|
||||
String helperValue = LocalHTTPServer.decode(value);
|
||||
return new String[] { newQuery, helperValue };
|
||||
}
|
||||
keystart = i + 1;
|
||||
valstart = -1;
|
||||
} else if (c == '=') {
|
||||
// end of key
|
||||
key = query.substring(keystart, i);
|
||||
valstart = i + 1;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/****
|
||||
private static String[] tests = {
|
||||
"", "foo", "foo=bar", "&", "&=&", "===", "&&",
|
||||
"i2paddresshelper=foo",
|
||||
"i2paddresshelpe=foo",
|
||||
"2paddresshelper=foo",
|
||||
"i2paddresshelper=%66oo",
|
||||
"%692paddresshelper=foo",
|
||||
"i2paddresshelper=foo&a=b",
|
||||
"a=b&i2paddresshelper=foo",
|
||||
"a=b&i2paddresshelper&c=d",
|
||||
"a=b&i2paddresshelper=foo&c=d",
|
||||
"a=b;i2paddresshelper=foo;c=d",
|
||||
"a=b&i2paddresshelper=foo&c"
|
||||
};
|
||||
|
||||
public static void main(String[] args) {
|
||||
for (int i = 0; i < tests.length; i++) {
|
||||
String[] s = removeHelper(tests[i]);
|
||||
if (s != null)
|
||||
System.out.println("Test \"" + tests[i] + "\" q=\"" + s[0] + "\" h=\"" + s[1] + "\"");
|
||||
else
|
||||
System.out.println("Test \"" + tests[i] + "\" no match");
|
||||
}
|
||||
}
|
||||
****/
|
||||
|
||||
/** */
|
||||
private static final String BUNDLE_NAME = "net.i2p.i2ptunnel.web.messages";
|
||||
|
||||
/** lang in routerconsole.lang property, else current locale */
|
||||
|
@ -67,12 +67,13 @@ public abstract class LocalHTTPServer {
|
||||
* uncaught vulnerabilities.
|
||||
* Restrict to the /themes/ directory for now.
|
||||
*
|
||||
* @param targetRequest "proxy.i2p/themes/foo.png HTTP/1.1"
|
||||
* @param targetRequest decoded path only, non-null
|
||||
* @param query raw (encoded), may be null
|
||||
*/
|
||||
public static void serveLocalFile(OutputStream out, String method, String targetRequest, String proxyNonce) {
|
||||
public static void serveLocalFile(OutputStream out, String method, String targetRequest, String query, String proxyNonce) {
|
||||
//System.err.println("targetRequest: \"" + targetRequest + "\"");
|
||||
// a home page message for the curious...
|
||||
if (targetRequest.startsWith(I2PTunnelHTTPClient.LOCAL_SERVER + "/ ")) {
|
||||
if (targetRequest.equals("/")) {
|
||||
try {
|
||||
out.write(("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nCache-Control: max-age=86400\r\n\r\nI2P HTTP proxy OK").getBytes());
|
||||
out.flush();
|
||||
@ -80,12 +81,11 @@ public abstract class LocalHTTPServer {
|
||||
return;
|
||||
}
|
||||
if ((method.equals("GET") || method.equals("HEAD")) &&
|
||||
targetRequest.startsWith(I2PTunnelHTTPClient.LOCAL_SERVER + "/themes/") &&
|
||||
targetRequest.startsWith("/themes/") &&
|
||||
!targetRequest.contains("..")) {
|
||||
int space = targetRequest.indexOf(' ');
|
||||
String filename = null;
|
||||
try {
|
||||
filename = targetRequest.substring(I2PTunnelHTTPClient.LOCAL_SERVER.length() + 8, space); // "/themes/".length
|
||||
filename = targetRequest.substring(8); // "/themes/".length
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
return;
|
||||
}
|
||||
@ -118,10 +118,9 @@ public abstract class LocalHTTPServer {
|
||||
// Add to addressbook (form submit)
|
||||
// Parameters are url, host, dest, nonce, and master | router | private.
|
||||
// Do the add and redirect.
|
||||
if (targetRequest.startsWith(I2PTunnelHTTPClient.LOCAL_SERVER + "/add?")) {
|
||||
int spc = targetRequest.indexOf(' ');
|
||||
String query = targetRequest.substring(I2PTunnelHTTPClient.LOCAL_SERVER.length() + 5, spc); // "/add?".length()
|
||||
if (targetRequest.equals("/add")) {
|
||||
Map<String, String> opts = new HashMap(8);
|
||||
// this only works if all keys are followed by =value
|
||||
StringTokenizer tok = new StringTokenizer(query, "=&;");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String k = tok.nextToken();
|
||||
@ -207,7 +206,7 @@ public abstract class LocalHTTPServer {
|
||||
* Decode %xx encoding
|
||||
* @since 0.8.7
|
||||
*/
|
||||
private static String decode(String s) {
|
||||
public static String decode(String s) {
|
||||
if (!s.contains("%"))
|
||||
return s;
|
||||
StringBuilder buf = new StringBuilder(s.length());
|
||||
|
Reference in New Issue
Block a user