forked from I2P_Developers/i2p.i2p
Merge branch 'x-i2p-location' into 'master'
Adds an X-I2P-Location header filter to the default I2P site See merge request i2p-hackers/i2p.i2p!22
This commit is contained in:
@@ -65,7 +65,6 @@ public class JettyStart implements ClientApp {
|
|||||||
private static final String GZIP_DIR = "eepsite-jetty9.3";
|
private static final String GZIP_DIR = "eepsite-jetty9.3";
|
||||||
private static final String GZIP_CONFIG = "jetty-gzip.xml";
|
private static final String GZIP_CONFIG = "jetty-gzip.xml";
|
||||||
private static final String MIN_GZIP_HANDLER_VER = "9.3";
|
private static final String MIN_GZIP_HANDLER_VER = "9.3";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All args must be XML file names.
|
* All args must be XML file names.
|
||||||
* Does not support any of the other argument types from org.mortbay.start.Main.
|
* Does not support any of the other argument types from org.mortbay.start.Main.
|
||||||
@@ -179,6 +178,10 @@ public class JettyStart implements ClientApp {
|
|||||||
public void run() {
|
public void run() {
|
||||||
for (LifeCycle lc : _jettys) {
|
for (LifeCycle lc : _jettys) {
|
||||||
if (!lc.isRunning()) {
|
if (!lc.isRunning()) {
|
||||||
|
if (lc instanceof Server) {
|
||||||
|
Server server = (Server) lc;
|
||||||
|
server.insertHandler(new XI2PLocationFilter());
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
lc.start();
|
lc.start();
|
||||||
if (_context != null) {
|
if (_context != null) {
|
||||||
|
@@ -0,0 +1,188 @@
|
|||||||
|
package net.i2p.jetty;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.handler.HandlerWrapper;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.I2PException;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.data.PrivateKeyFile;
|
||||||
|
|
||||||
|
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a header, X-I2P-Location, to requests when they do **not** come in on an I2P hostname.
|
||||||
|
* This header contains a URL that looks like: [scheme://][i2phostname.i2p][/path][?query]
|
||||||
|
* and expresses the I2P-Equivalent URL of the clearnet query. Clients can use this to prompt
|
||||||
|
* users to switch from a non-I2P host to an I2P host or to redirect them automatically. It
|
||||||
|
* automatically enabled on the default I2P site located on port 7658 by default.
|
||||||
|
*
|
||||||
|
* @since 0.9.51
|
||||||
|
*/
|
||||||
|
public class XI2PLocationFilter extends HandlerWrapper {
|
||||||
|
private String X_I2P_Location = null;
|
||||||
|
private long lastFailure = -1;
|
||||||
|
private static final long failTimeout = 600000;
|
||||||
|
private static final String encodeUTF = StandardCharsets.UTF_8.toString();
|
||||||
|
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(XI2PLocationFilter.class);
|
||||||
|
|
||||||
|
|
||||||
|
private synchronized void setLocation(String xi2plocation) {
|
||||||
|
if (_log.shouldInfo())
|
||||||
|
_log.info("Checking X-I2P-Location header prefix" + xi2plocation);
|
||||||
|
if (X_I2P_Location != null)
|
||||||
|
return ;
|
||||||
|
if (xi2plocation == null)
|
||||||
|
return ;
|
||||||
|
if (xi2plocation.equals(""))
|
||||||
|
return ;
|
||||||
|
X_I2P_Location = xi2plocation;
|
||||||
|
if (_log.shouldInfo())
|
||||||
|
_log.info("Caching X-I2P-Location header prefix" + X_I2P_Location);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized boolean shouldRecheck(){
|
||||||
|
boolean settable = (X_I2P_Location == null);
|
||||||
|
if (!settable) return settable;
|
||||||
|
if (lastFailure == -1) {
|
||||||
|
lastFailure = System.currentTimeMillis();
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("New instance, attempting to set X-I2P-Location header for the first time");
|
||||||
|
return settable;
|
||||||
|
}
|
||||||
|
if ((System.currentTimeMillis() - lastFailure) > failTimeout){
|
||||||
|
lastFailure = System.currentTimeMillis();
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("More than ten minutes since failing attempt to re-check X-I2P-Location header");
|
||||||
|
return settable;
|
||||||
|
}
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("Not attempting to re-check X-I2P-Location header");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized String getXI2PLocation(String host, String port) {
|
||||||
|
File configDir = I2PAppContext.getGlobalContext().getConfigDir();
|
||||||
|
File tunnelConfig = new File(configDir, "i2ptunnel.config");
|
||||||
|
boolean isSingleFile = tunnelConfig.exists();
|
||||||
|
if (!isSingleFile) {
|
||||||
|
File tunnelConfigD = new File(configDir, "i2ptunnel.config.d");
|
||||||
|
File[] configFiles = tunnelConfigD.listFiles(new net.i2p.util.FileSuffixFilter(".config"));
|
||||||
|
if (configFiles == null)
|
||||||
|
return null;
|
||||||
|
for (int fnum=0; fnum < configFiles.length; fnum++) {
|
||||||
|
Properties tunnelProps = new Properties();
|
||||||
|
try {
|
||||||
|
DataHelper.loadProps(tunnelProps, configFiles[fnum]);
|
||||||
|
String targetHost = tunnelProps.getProperty("targetHost");
|
||||||
|
boolean hostmatch = (host.equals(targetHost) || "0.0.0.0".equals(targetHost) || "::".equals(targetHost));
|
||||||
|
if ( hostmatch && port.equals(tunnelProps.getProperty("targetPort")) ) {
|
||||||
|
String sh = tunnelProps.getProperty("spoofedHost");
|
||||||
|
if (sh != null) {
|
||||||
|
if (sh.endsWith(".i2p"))
|
||||||
|
return sh;
|
||||||
|
}
|
||||||
|
String kf = tunnelProps.getProperty("privKeyFile");
|
||||||
|
if (kf != null) {
|
||||||
|
File keyFile = new File(kf);
|
||||||
|
if (!keyFile.isAbsolute())
|
||||||
|
keyFile = new File(configDir, kf);
|
||||||
|
if (keyFile.exists()) {
|
||||||
|
PrivateKeyFile pkf = new PrivateKeyFile(keyFile);
|
||||||
|
try {
|
||||||
|
Destination rv = pkf.getDestination();
|
||||||
|
if (rv != null)
|
||||||
|
return rv.toBase32();
|
||||||
|
} catch (I2PException e) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("I2PException Unable to set X-I2P-Location, keys arent ready. This is probably safe to ignore and will go away after the first run." + e);
|
||||||
|
return null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("IOE Unable to set X-I2P-Location, location is uninitialized due file not found. This probably means the keys aren't ready. This is probably safe to ignore." + e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("Unable to set X-I2P-Location, location is target not found in any I2PTunnel config file. This should never happen.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("IOE Unable to set X-I2P-Location, location is uninitialized. This is probably safe to ignore. location='" + ioe + "'");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// don't bother
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized String headerContents(final HttpServletRequest httpRequest) {
|
||||||
|
if (X_I2P_Location != null) {
|
||||||
|
String scheme = httpRequest.getScheme();
|
||||||
|
if (scheme == null)
|
||||||
|
scheme = "";
|
||||||
|
String path = httpRequest.getPathInfo();
|
||||||
|
if (path == null)
|
||||||
|
path = "";
|
||||||
|
String query = httpRequest.getQueryString();
|
||||||
|
if (query == null)
|
||||||
|
query = "";
|
||||||
|
try {
|
||||||
|
if (query.equals("")) {
|
||||||
|
URI uri = new URI(scheme, X_I2P_Location, path, null);
|
||||||
|
String encodedURL = uri.toASCIIString();
|
||||||
|
return encodedURL;
|
||||||
|
} else {
|
||||||
|
URI uri = new URI(scheme, X_I2P_Location, path, query, null);
|
||||||
|
String encodedURL = uri.toASCIIString();
|
||||||
|
return encodedURL;
|
||||||
|
}
|
||||||
|
}catch(URISyntaxException use){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(final String target, final Request request, final HttpServletRequest httpRequest, HttpServletResponse httpResponse)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
final String hashHeader = httpRequest.getHeader("X-I2P-DestHash");
|
||||||
|
|
||||||
|
if (hashHeader == null) {
|
||||||
|
if (shouldRecheck()) {
|
||||||
|
String xi2plocation = getXI2PLocation(request.getLocalAddr(), String.valueOf(request.getLocalPort()));
|
||||||
|
if (_log.shouldInfo())
|
||||||
|
_log.info("Checking X-I2P-Location header IP " + request.getLocalAddr() + " port " + request.getLocalPort() + " prefix " + xi2plocation);
|
||||||
|
setLocation(xi2plocation);
|
||||||
|
}
|
||||||
|
String headerURL = headerContents(httpRequest);
|
||||||
|
if (headerURL != null) {
|
||||||
|
if (_log.shouldInfo())
|
||||||
|
_log.info("Checking X-I2P-Location header" + headerURL);
|
||||||
|
httpResponse.addHeader("X-I2P-Location", headerURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handler.handle(target, request, httpRequest, httpResponse);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user