- 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:
zzz
2012-03-01 18:39:07 +00:00
parent cadedeb06c
commit e7bcff5e71
2 changed files with 374 additions and 222 deletions

View File

@ -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 */

View File

@ -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());