forked from I2P_Developers/i2p.i2p
Compare commits
4 Commits
master
...
i2p.i2p.2.
Author | SHA1 | Date | |
---|---|---|---|
eb63ba841b | |||
dda709d41a | |||
9faa916b6f | |||
417cd3ad4c |
@ -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&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&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);
|
||||
|
@ -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
Reference in New Issue
Block a user