Compare commits

...

4 Commits

3 changed files with 1287 additions and 761 deletions

View File

@ -37,6 +37,7 @@ import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.i2ptunnel.localServer.LocalHTTPServer;
import net.i2p.i2ptunnel.util.HTTPRequestReader;
import net.i2p.util.ConvertToHash;
import net.i2p.util.DNSOverHTTPS;
import net.i2p.util.EventDispatcher;
@ -81,7 +82,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
* Map of hostname to base64 destination for destinations collected
* via address helper links
*/
private final ConcurrentHashMap<String, String> addressHelpers = new ConcurrentHashMap<String, String>(8);
public final ConcurrentHashMap<String, String> addressHelpers = new ConcurrentHashMap<String, String>(8);
/**
* Used to protect actions via http://proxy.i2p/
@ -89,16 +90,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
private final String _proxyNonce;
public static final String AUTH_REALM = "I2P HTTP Proxy";
private static final String UA_I2P = "User-Agent: " +
public static final String UA_I2P = "User-Agent: " +
"MYOB/6.66 (AN/ON)" +
"\r\n";
// ESR version of Firefox, same as Tor Browser
private static final String UA_CLEARNET = "User-Agent: " +
public static final String UA_CLEARNET = "User-Agent: " +
DNSOverHTTPS.UA_CLEARNET +
"\r\n";
// overrides
private static final String PROP_UA_I2P = "httpclient.userAgent.i2p";
private static final String PROP_UA_CLEARNET = "httpclient.userAgent.outproxy";
public static final String PROP_UA_I2P = "httpclient.userAgent.i2p";
public static final String PROP_UA_CLEARNET = "httpclient.userAgent.outproxy";
public static final String OPT_KEEPALIVE_BROWSER = "keepalive.browser";
public static final String OPT_KEEPALIVE_I2P = "keepalive.i2p";
@ -111,7 +112,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
/**
* These are backups if the xxx.ht error page is missing.
*/
private final static String ERR_REQUEST_DENIED =
public final static String ERR_REQUEST_DENIED =
"HTTP/1.1 403 Access Denied\r\n" +
"Content-Type: text/html; charset=iso-8859-1\r\n" +
"Cache-Control: no-cache\r\n" +
@ -133,7 +134,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
"the following Destination:<BR><BR>")
.getBytes();
*****/
private final static String ERR_NO_OUTPROXY =
public final static String ERR_NO_OUTPROXY =
"HTTP/1.1 503 Service Unavailable\r\n" +
"Content-Type: text/html; charset=iso-8859-1\r\n" +
"Cache-Control: no-cache\r\n" +
@ -143,7 +144,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
"Your request was for a site outside of I2P, but you have no " +
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel";
private final static String ERR_AHELPER_CONFLICT =
public final static String ERR_AHELPER_CONFLICT =
"HTTP/1.1 409 Conflict\r\n" +
"Content-Type: text/html; charset=iso-8859-1\r\n" +
"Cache-Control: no-cache\r\n" +
@ -159,7 +160,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
"discarding the host entry from your host database, " +
"or naming one of them differently.<p>";
private final static String ERR_AHELPER_NOTFOUND =
public final static String ERR_AHELPER_NOTFOUND =
"HTTP/1.1 404 Not Found\r\n" +
"Content-Type: text/html; charset=iso-8859-1\r\n" +
"Cache-Control: no-cache\r\n" +
@ -192,7 +193,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 =
public 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" +
@ -202,7 +203,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
"The request URI is invalid, and probably contains illegal characters. " +
"If you clicked e.g. a forum link, check the end of the URI for any characters the browser has mistakenly added on.<BR>";
private final static String ERR_LOCALHOST =
public final static String ERR_LOCALHOST =
"HTTP/1.1 403 Access Denied\r\n" +
"Content-Type: text/html; charset=iso-8859-1\r\n" +
"Cache-Control: no-cache\r\n" +
@ -365,9 +366,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
return AUTH_REALM;
}
private static final String HELPER_PARAM = "i2paddresshelper";
public static final String HELPER_PARAM = "i2paddresshelper";
public static final String LOCAL_SERVER = "proxy.i2p";
private static final boolean DEFAULT_GZIP = true;
public static final boolean DEFAULT_GZIP = true;
/** all default to false */
public static final String PROP_REFERER = "i2ptunnel.httpclient.sendReferer";
public static final String PROP_USER_AGENT = "i2ptunnel.httpclient.sendUserAgent";
@ -404,7 +405,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
String internalRawQuery = null;
String currentProxy = null;
long requestId = __requestId.incrementAndGet();
boolean shout = false;
boolean isConnect = false;
boolean isHead = false;
I2PSocket i2ps = null;
@ -438,738 +438,22 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
if (_log.shouldInfo())
_log.info("Keepalive, awaiting request #" + requestCount);
}
String line, method = null, protocol = null, host = null, destination = null;
String hostLowerCase = null;
final HTTPRequestReader hrr = new HTTPRequestReader(s, _context, reader, __requestId,
I2PTunnelHTTPClientBase.BROWSER_READ_TIMEOUT, getTunnel(), this);
String method = hrr.getMethod();
String protocol = hrr.getProtocol();
String hostLowerCase = hrr.getHostLowerCase();
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;
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);
}
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;
}
String protocolVersion = params[2];
if (!protocolVersion.equals("HTTP/1.1"))
keepalive = false;
protocol = requestURI.getScheme();
host = requestURI.getHost();
if(protocol == null || host == null) {
_log.warn("Null protocol or host: " + request + ' ' + protocol + ' ' + host);
method = null;
break;
}
int port = requestURI.getPort();
// Go through the various types of hostnames, set
// the host and destination variables accordingly,
// and transform the first line.
// For all i2p network hosts, ensure that the host is a
// Base 32 hostname so that we do not reveal our name for it
// in our addressbook (all naming is local),
// and it is removed from the request line.
hostLowerCase = host.toLowerCase(Locale.US);
if(hostLowerCase.equals(LOCAL_SERVER)) {
// so we don't do any naming service lookups
destination = host;
usingInternalServer = true;
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 newURI = oldPath.substring(slash);
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("Bad http://i2p/b64dest " + request);
host = null;
break;
}
} else if(hostLowerCase.endsWith(".i2p")) {
// Destination gets the hostname
destination = host;
// Host becomes the destination's "{b32}.b32.i2p" string, or "i2p" on lookup failure
host = getHostName(destination);
int rPort = requestURI.getPort();
if (rPort > 0) {
// Save it to put in the I2PSocketOptions,
remotePort = rPort;
/********
// but strip it from the URL
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;
}
******/
} else if ("https".equals(protocol) || isConnect) {
remotePort = 443;
} else {
remotePort = 80;
}
String query = requestURI.getRawQuery();
if(query != null) {
boolean ahelperConflict = false;
// Try to find an address helper in the query
String[] helperStrings = removeHelper(query);
if(helperStrings != null &&
!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER))) {
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 I2P Site 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);
}
String header = getErrorPage("ahelper-notfound", ERR_AHELPER_NOTFOUND);
try {
out.write(header.getBytes("UTF-8"));
out.write(("<p>" + _t("This seems to be a bad destination:") + " " + ahelperKey + " " +
_t("i2paddresshelper cannot help you with a destination like that!") +
"</p>").getBytes("UTF-8"));
writeFooter(out);
reader.drain();
} catch (IOException ioe) {
// ignore
}
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;
// inr address helper links without trailing '=', so omit from comparison
if ((!ahelperNew) && !old.replace("=", "").equals(ahelperKey.replace("=", ""))) {
// 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 + "].");
}
}
}
}
} // ahelperKey
} // helperstrings
// Did addresshelper key conflict?
if(ahelperConflict) {
try {
// convert ahelperKey to b32
String alias = getHostName(ahelperKey);
if(alias.equals("i2p")) {
// bad ahelperKey
String header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN);
writeErrorMessage(header, out, targetRequest, false, destination);
} else {
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();
String header = getErrorPage("ahelper-conflict", ERR_AHELPER_CONFLICT);
out.write(header.getBytes("UTF-8"));
out.write("<p>".getBytes("UTF-8"));
out.write(_t("To visit the destination in your address book, click <a href=\"{0}\">here</a>. To visit the conflicting addresshelper destination, click <a href=\"{1}\">here</a>.",
trustedURL, conflictURL).getBytes("UTF-8"));
out.write("</p>".getBytes("UTF-8"));
Hash h1 = ConvertToHash.getHash(requestURI.getHost());
Hash h2 = ConvertToHash.getHash(ahelperKey);
if (h1 != null && h2 != null) {
String conURL = _context.portMapper().getConsoleURL();
out.write(("\n<table class=\"conflict\"><tr><th align=\"center\">" +
"<a href=\"" + trustedURL + "\">").getBytes("UTF-8"));
out.write(_t("Destination for {0} in address book", requestURI.getHost()).getBytes("UTF-8"));
out.write(("</a></th>\n<th align=\"center\">" +
"<a href=\"" + conflictURL + "\">").getBytes("UTF-8"));
out.write(_t("Conflicting address helper destination").getBytes("UTF-8"));
out.write(("</a></th></tr>\n").getBytes("UTF-8"));
if (_context.portMapper().isRegistered(PortMapper.SVC_IMAGEGEN)) {
out.write(("<tr><td align=\"center\">" +
"<a href=\"" + trustedURL + "\">" +
"<img src=\"" +
conURL + "imagegen/id?s=160&amp;c=" +
h1.toBase64().replace("=", "%3d") +
"\" width=\"160\" height=\"160\"></a>\n" +
"</td>\n<td align=\"center\">" +
"<a href=\"" + conflictURL + "\">" +
"<img src=\"" +
conURL + "imagegen/id?s=160&amp;c=" +
h2.toBase64().replace("=", "%3d") +
"\" width=\"160\" height=\"160\"></a>\n" +
"</td></tr>").getBytes("UTF-8"));
}
out.write("</table>".getBytes("UTF-8"));
}
out.write("</div>".getBytes("UTF-8"));
writeFooter(out);
}
reader.drain();
} catch (IOException ioe) {
// ignore
}
return;
}
} // end query processing
String addressHelper = addressHelpers.get(destination);
if(addressHelper != null) {
host = getHostName(addressHelper);
}
targetRequest = requestURI.toASCIIString();
if (!isConnect) {
// now strip everything but path and query from URI
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(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
try {
out.write(getErrorPage("localhost", ERR_LOCALHOST).getBytes("UTF-8"));
writeFooter(out);
reader.drain();
} catch (IOException ioe) {
// ignore
}
return;
} else if(host.contains(".") || host.startsWith("[")) {
if (Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_USE_OUTPROXY_PLUGIN, "true"))) {
ClientAppManager mgr = _context.clientAppManager();
if (mgr != null) {
ClientApp op = mgr.getRegisteredApp(Outproxy.NAME);
if (op != null) {
outproxy = (Outproxy) op;
int rPort = requestURI.getPort();
if (rPort > 0)
remotePort = rPort;
else if ("https".equals(protocol) || isConnect)
remotePort = 443;
else
remotePort = 80;
usingInternalOutproxy = true;
targetRequest = requestURI.toASCIIString();
if(_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + " [" + host + "]: outproxy!");
}
}
}
if (!usingInternalOutproxy) {
if(port >= 0) {
host = host + ':' + port;
}
// The request must be forwarded to a WWW proxy
if(_log.shouldLog(Log.DEBUG)) {
_log.debug("Before selecting outproxy for " + host);
}
if ("https".equals(protocol) || isConnect)
currentProxy = selectSSLProxy(hostLowerCase);
else
currentProxy = selectProxy(hostLowerCase);
if(_log.shouldLog(Log.DEBUG)) {
_log.debug("After selecting outproxy for " + host + ": " + currentProxy);
}
if(currentProxy == null) {
if(_log.shouldLog(Log.WARN)) {
_log.warn("No outproxy configured for request: " + requestURI);
}
try {
out.write(getErrorPage("noproxy", ERR_NO_OUTPROXY).getBytes("UTF-8"));
writeFooter(out);
reader.drain();
} catch (IOException ioe) {
// ignore
}
return;
}
destination = currentProxy;
usingWWWProxy = true;
targetRequest = requestURI.toASCIIString();
if(_log.shouldLog(Log.DEBUG)) {
_log.debug(getPrefix(requestId) + " [" + host + "]: wwwProxy!");
}
}
} else {
// what is left for here? a hostname with no dots, and != "i2p"
// and not a destination ???
// Perhaps something in privatehosts.txt ...
// Rather than look it up, just bail out.
if(_log.shouldLog(Log.WARN)) {
_log.warn("NODOTS, NOI2P: " + request);
}
try {
out.write(getErrorPage("denied", ERR_REQUEST_DENIED).getBytes("UTF-8"));
writeFooter(out);
reader.drain();
} catch (IOException ioe) {
// ignore
}
return;
} // end hostname processing
boolean isValid = usingInternalOutproxy || usingWWWProxy ||
usingInternalServer || isSupportedAddress(host, protocol);
if(!isValid) {
if(_log.shouldLog(Log.INFO)) {
_log.info(getPrefix(requestId) + "notValid(" + host + ")");
}
method = null;
destination = null;
break;
}
if (isConnect) {
// fix up the change to requestURI above to get back to the original host:port
if (usingInternalOutproxy || usingWWWProxy)
line = method + ' ' + requestURI.getHost() + ':' + requestURI.getPort() + ' ' + protocolVersion;
else
line = method + ' ' + host + ':' + remotePort + ' ' + protocolVersion;
} else {
line = method + ' ' + requestURI.toASCIIString() + ' ' + protocolVersion;
}
if(_log.shouldLog(Log.DEBUG)) {
_log.debug(getPrefix(requestId) + "REQ : \"" + request + "\"");
_log.debug(getPrefix(requestId) + "REQURI: \"" + requestURI + "\"");
_log.debug(getPrefix(requestId) + "NEWREQ: \"" + line + "\"");
_log.debug(getPrefix(requestId) + "HOST : \"" + host + "\"");
_log.debug(getPrefix(requestId) + "RPORT : \"" + remotePort + "\"");
_log.debug(getPrefix(requestId) + "DEST : \"" + destination + "\"");
}
// end first line processing
} else {
if (lowercaseLine.startsWith("connection: ")) {
if (lowercaseLine.contains("upgrade")) {
// pass through for websocket
preserveConnectionHeader = true;
keepalive = false;
} else if (lowercaseLine.contains("keep-alive")) {
// pass through
if (!keepalive)
continue;
// pass through
preserveConnectionHeader = true;
} else {
if (lowercaseLine.contains("close"))
keepalive = false;
continue;
}
} else if (lowercaseLine.startsWith("keep-alive: ") ||
lowercaseLine.startsWith("proxy-connection: ")) {
if (lowercaseLine.contains("close"))
keepalive = false;
continue;
} else if (lowercaseLine.startsWith("host: ") && !usingWWWProxy && !usingInternalOutproxy) {
// Note that we only pass the original Host: line through to the outproxy
// But we don't create a Host: line if it wasn't sent to us
line = "Host: " + host;
if (_log.shouldDebug()) {
_log.debug(getPrefix(requestId) + "Setting host = " + host);
}
} else if(lowercaseLine.startsWith("user-agent: ")) {
// save for deciding whether to offer address book form
userAgent = lowercaseLine.substring(12);
if(!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT))) {
line = null;
continue;
}
} else if(lowercaseLine.startsWith("accept: ")) {
if (!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_ACCEPT))) {
// Replace with a standard one if possible
boolean html = lowercaseLine.indexOf("text/html") > 0;
boolean css = lowercaseLine.indexOf("text/css") > 0;
boolean img = lowercaseLine.indexOf("image") > 0;
if (html && !img && !css) {
// firefox, tor browser
line = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
} else if (img && !html && !css) {
// chrome
line = "Accept: image/webp,image/apng,image/*,*/*;q=0.8";
} else if (css && !html && !img) {
// chrome, firefox
line = "Accept: text/css,*/*;q=0.1";
} // else allow as-is
}
} else if(lowercaseLine.startsWith("accept")) {
// strip the accept-blah headers, as they vary dramatically from
// browser to browser
// But allow Accept-Encoding: gzip, deflate
if (lowercaseLine.startsWith("accept-encoding: ")) {
allowGzip = lowercaseLine.contains("gzip");
} else if (!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_ACCEPT))) {
line = null;
continue;
}
} else if (lowercaseLine.startsWith("referer: ")) {
// save for address helper form below
referer = line.substring(9);
if (!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_REFERER))) {
try {
// Either strip or rewrite the referer line
URI refererURI = new URI(referer);
String refererHost = refererURI.getHost();
if (refererHost != null) {
String origHost = origRequestURI.getHost();
if (!refererHost.equals(origHost) ||
refererURI.getPort() != origRequestURI.getPort() ||
!DataHelper.eq(refererURI.getScheme(), origRequestURI.getScheme())) {
line = null;
continue; // completely strip the line if everything doesn't match
}
// Strip to a relative URI, to hide the original hostname
StringBuilder buf = new StringBuilder();
buf.append("Referer: ");
String refererPath = refererURI.getRawPath();
buf.append(refererPath != null ? refererPath : "/");
String refererQuery = refererURI.getRawQuery();
if (refererQuery != null)
buf.append('?').append(refererQuery);
line = buf.toString();
} // else relative URI, leave in
} catch (URISyntaxException use) {
line = null;
continue; // completely strip the line
}
} // else allow
} else if(lowercaseLine.startsWith("via: ") &&
!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_VIA))) {
//line = "Via: i2p";
line = null;
continue; // completely strip the line
} else if(lowercaseLine.startsWith("from: ")) {
//line = "From: i2p";
line = null;
continue; // completely strip the line
} else if(lowercaseLine.startsWith("authorization: ntlm ")) {
// Block Windows NTLM after 401
line = null;
continue;
} else if(lowercaseLine.startsWith("proxy-authorization: ")) {
// This should be for us. It is a
// hop-by-hop header, and we definitely want to block Windows NTLM after a far-end 407.
// Response to far-end shouldn't happen, as we
// strip Proxy-Authenticate from the response in HTTPResponseOutputStream
authorization = line.substring(21); // "proxy-authorization: ".length()
line = null;
continue;
} else if(lowercaseLine.startsWith("icy")) {
// icecast/shoutcast, We need to leave the user-agent alone.
shout = true;
}
}
if(line.length() == 0) {
// No more headers, add our own and break out of the loop
String ok = getTunnel().getClientOptions().getProperty("i2ptunnel.gzip");
boolean gzip = DEFAULT_GZIP;
if(ok != null) {
gzip = Boolean.parseBoolean(ok);
}
if(gzip && !usingInternalServer && !isConnect) {
// according to rfc2616 s14.3, this *should* force identity, even if
// an explicit q=0 for gzip doesn't. tested against orion.i2p, and it
// seems to work.
//if (!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_ACCEPT)))
// newRequest.append("Accept-Encoding: \r\n");
if (!usingInternalOutproxy)
newRequest.append("X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n");
}
if(!shout && !isConnect) {
if(!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT))) {
// let's not advertise to external sites that we are from I2P
String ua;
if (usingWWWProxy || usingInternalOutproxy) {
ua = getTunnel().getClientOptions().getProperty(PROP_UA_CLEARNET);
if (ua != null)
ua = "User-Agent: " + ua + "\r\n";
else
ua = UA_CLEARNET;
} else {
ua = getTunnel().getClientOptions().getProperty(PROP_UA_I2P);
if (ua != null)
ua = "User-Agent: " + ua + "\r\n";
else
ua = UA_I2P;
}
newRequest.append(ua);
}
}
// Add Proxy-Authentication header for next hop (outproxy)
if(usingWWWProxy && Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_AUTH))) {
// specific for this proxy
String user = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_USER_PREFIX + currentProxy);
String pw = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_PW_PREFIX + currentProxy);
if(user == null || pw == null) {
// if not, look at default user and pw
user = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_USER);
pw = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_PW);
}
if(user != null && pw != null) {
newRequest.append("Proxy-Authorization: Basic ")
.append(Base64.encode((user + ':' + pw).getBytes("UTF-8"), true)) // true = use standard alphabet
.append("\r\n");
}
}
if (preserveConnectionHeader)
newRequest.append("\r\n");
else
newRequest.append("Connection: close\r\n\r\n");
s.setSoTimeout(BROWSER_READ_TIMEOUT);
break;
} else {
newRequest.append(line).append("\r\n"); // HTTP spec
}
} // end header processing
boolean ahelperPresent = hrr.getAhelperNew();
boolean ahelperNew = hrr.getAhelperNew();
String ahelperKey = hrr.getAhelperKey();
String userAgent = hrr.getUserAgent();
String authorization = hrr.getAuthorization();
int remotePort = hrr.getRemotePort();
String referer = hrr.getReferer();
boolean allowGzip = hrr.getAllowGzip();
String destination = hrr.getDestination();
String host = hrr.getHost();
if (newRequest.length() > 0 && _log.shouldDebug())
_log.debug(getPrefix(requestId) + "NewRequest header: [" + newRequest + ']');
@ -1687,14 +971,14 @@ 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 {
public static class InputReader {
InputStream _s;
public InputReader(InputStream s) {
_s = s;
}
String readLine(String method) throws IOException {
public String readLine(String method) throws IOException {
// Use unbuffered until we can find a BufferedReader that limits line length
//if (method == null || "POST".equals(method))
return DataHelper.readLine(_s);

View File

@ -84,7 +84,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
* Failsafe
* @since 0.9.42
*/
protected static final int BROWSER_READ_TIMEOUT = 4*60*60*1000;
public static final int BROWSER_READ_TIMEOUT = 4*60*60*1000;
private static final String ERR_AUTH1 =
"HTTP/1.1 407 Proxy Authentication Required\r\n" +
@ -109,8 +109,8 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
"<html><body><H1>I2P ERROR: No outproxy found</H1>"+
"Your request was for a site outside of I2P, but you have no "+
"outproxy configured. Please configure an outproxy in I2PTunnel";
protected final static String ERR_DESTINATION_UNKNOWN =
public final static String ERR_DESTINATION_UNKNOWN =
"HTTP/1.1 503 Service Unavailable\r\n" +
"Content-Type: text/html; charset=iso-8859-1\r\n" +
"Cache-Control: no-cache\r\n" +
@ -161,7 +161,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
// SSL proxy config is parsed on the fly;
// allow both to be changed and store the SSL proxy list.
// TODO should track more than one failed proxy
/**
* Simple random selection, with caching by hostname,
* and avoidance of the last one to fail.
@ -169,7 +169,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
* @param host the clearnet hostname we're targeting
* @return null if none configured
*/
protected String selectProxy(String host) {
public String selectProxy(String host) {
String rv;
synchronized (_proxyList) {
int size = _proxyList.size();
@ -207,7 +207,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
* @return null if none configured
* @since 0.9.11, moved from I2PTunnelHTTPClient in 0.9.39
*/
protected String selectSSLProxy(String host) {
public String selectSSLProxy(String host) {
String s = getTunnel().getClientOptions().getProperty(PROP_SSL_OUTPROXIES);
if (s == null)
return null;
@ -242,7 +242,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
_log.info("Selected SSL proxy for " + host + ": " + rv);
return rv;
}
/**
* Update the cache and note if failed.
*
@ -289,11 +289,11 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
* so that large POSTs won't timeout on the read side
*/
protected static final int DEFAULT_READ_TIMEOUT = -1;
protected static final AtomicLong __requestId = new AtomicLong();
public I2PTunnelHTTPClientBase(int localPort, boolean ownDest, Logging l,
EventDispatcher notifyThis, String handlerName,
public I2PTunnelHTTPClientBase(int localPort, boolean ownDest, Logging l,
EventDispatcher notifyThis, String handlerName,
I2PTunnel tunnel) throws IllegalArgumentException {
super(localPort, ownDest, l, notifyThis, handlerName, tunnel);
// force connect delay and bulk profile
@ -722,7 +722,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
* @return non-null
* @since 0.9.4 moved from I2PTunnelHTTPClient
*/
protected static String getErrorPage(I2PAppContext ctx, String base, String backup) {
public static String getErrorPage(I2PAppContext ctx, String base, String backup) {
String file = "proxy/" + base + "-header.ht";
try {
return readFile(ctx, file);
@ -732,7 +732,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
}
/** these strings go in the jar, not the war */
private static final String BUNDLE_NAME = "net.i2p.i2ptunnel.proxy.messages";
public static final String BUNDLE_NAME = "net.i2p.i2ptunnel.proxy.messages";
/**
* As of 0.9.49, loads the error pages from the jar, not the file system.
@ -934,7 +934,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
* No jump servers or extra message
* @since 0.9.14
*/
protected void writeErrorMessage(String errMessage, OutputStream out, String targetRequest,
public void writeErrorMessage(String errMessage, OutputStream out, String targetRequest,
boolean usingWWWProxy, String wwwProxy) throws IOException {
writeErrorMessage(errMessage, null, out, targetRequest, usingWWWProxy, wwwProxy, null);
}
@ -1108,7 +1108,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
out.flush();
}
private static String getFooter() {
public static String getFooter() {
// The css is hiding this div for now, but we'll keep it here anyway
// Tag the strings below for translation if we unhide it.
//StringBuilder buf = new StringBuilder(128);

File diff suppressed because it is too large Load Diff