Files
i2p.i2p/apps/routerconsole/java/src/net/i2p/router/web/HostCheckHandler.java
zzz fe808a8800 Console: Fix gzip enable logic
Don't compress js
Set charset for war resources
2018-03-10 14:53:57 +00:00

198 lines
6.8 KiB
Java

package net.i2p.router.web;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.Addresses;
import net.i2p.util.Log;
import net.i2p.util.PortMapper;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.servlets.gzip.GzipHandler;
/**
* Block certain Host headers to prevent DNS rebinding attacks.
*
* This Handler wraps the ContextHandlerCollection, which handles
* all the webapps (not just routerconsole).
* Therefore, this protects all the webapps.
*
* @since 0.9.32
*/
public class HostCheckHandler extends GzipHandler
{
private final I2PAppContext _context;
private final PortMapper _portMapper;
private final Set<String> _listenHosts;
private static final String PROP_REDIRECT = "routerconsole.redirectToHTTPS";
private static final String PROP_GZIP = "routerconsole.enableCompression";
/**
* MUST call setListenHosts() afterwards.
*/
public HostCheckHandler(I2PAppContext ctx) {
super();
_context = ctx;
_portMapper = ctx.portMapper();
_listenHosts = new HashSet<String>(8);
setMinGzipSize(64*1024);
if (_context.getBooleanPropertyDefaultTrue(PROP_GZIP)) {
addIncludedMimeTypes(
// our js is very small
//"application/javascript", "application/x-javascript",
"application/xhtml+xml", "application/xml",
"image/svg+xml", "text/css", "text/html", "text/plain"
);
} else {
// poorly documented, but we must put something in,
// if empty all are matched,
// see IncludeExcludeSet
addIncludedMimeTypes("xyzzy");
}
}
/**
* Set the legal hosts.
* Not synched. Call this BEFORE starting.
* If empty, all are allowed.
*
* @param hosts contains hostnames or IPs. But we allow all IPs anyway.
*/
public void setListenHosts(Set<String> hosts) {
_listenHosts.clear();
_listenHosts.addAll(hosts);
}
/**
* Block by Host header,
* redirect HTTP to HTTPS,
* pass everything else to the delegate.
*/
public void handle(String pathInContext,
Request baseRequest,
HttpServletRequest httpRequest,
HttpServletResponse httpResponse)
throws IOException, ServletException
{
String host = httpRequest.getHeader("Host");
if (!allowHost(host)) {
Log log = _context.logManager().getLog(HostCheckHandler.class);
host = DataHelper.stripHTML(getHost(host));
String s = "Console request denied.\n" +
" To allow access using the hostname \"" + host + "\", add the line \"" +
RouterConsoleRunner.PROP_ALLOWED_HOSTS + '=' + host +
"\" to advanced configuration and restart.";
log.logAlways(Log.WARN, s);
httpResponse.sendError(403, s);
baseRequest.setHandled(true);
return;
}
// redirect HTTP to HTTPS if available, AND:
// either 1) PROP_REDIRECT is set to true;
// or 2) PROP_REDIRECT is unset and the Upgrade-Insecure-Requests request header is set
// https://w3c.github.io/webappsec-upgrade-insecure-requests/
if (!httpRequest.isSecure()) {
int httpsPort = _portMapper.getPort(PortMapper.SVC_HTTPS_CONSOLE);
if (httpsPort > 0 && httpRequest.getLocalPort() != httpsPort) {
String redir = _context.getProperty(PROP_REDIRECT);
if (Boolean.valueOf(redir) ||
(redir == null && "1".equals(httpRequest.getHeader("Upgrade-Insecure-Requests")))) {
sendRedirect(httpsPort, httpRequest, httpResponse);
baseRequest.setHandled(true);
return;
}
}
}
super.handle(pathInContext, baseRequest, httpRequest, httpResponse);
}
/**
* Should we allow a request with this Host header?
*
* ref: https://en.wikipedia.org/wiki/DNS_rebinding
*
* @param host the HTTP Host header, null ok
* @return true if OK
*/
private boolean allowHost(String host) {
if (host == null)
return true;
// common cases
if (host.equals("127.0.0.1:7657") ||
host.equals("localhost:7657") ||
host.equals("[::1]:7657") ||
host.equals("127.0.0.1:7667") ||
host.equals("localhost:7667") ||
host.equals("[::1]:7667"))
return true;
// all allowed?
if (_listenHosts.isEmpty())
return true;
host = getHost(host);
if (_listenHosts.contains(host))
return true;
// allow all IP addresses
if (Addresses.isIPAddress(host))
return true;
//System.out.println(host + " not found in " + s);
return false;
}
/**
* Strip [] and port from a host header
*
* @param host the HTTP Host header non-null
*/
private static String getHost(String host) {
if (host.startsWith("[")) {
host = host.substring(1);
int brack = host.indexOf(']');
if (brack >= 0)
host = host.substring(0, brack);
} else {
int colon = host.indexOf(':');
if (colon >= 0)
host = host.substring(0, colon);
}
return host;
}
/**
* Redirect to HTTPS
*
* @since 0.9.34
*/
private static void sendRedirect(int httpsPort, HttpServletRequest httpRequest,
HttpServletResponse httpResponse) throws IOException {
StringBuilder buf = new StringBuilder(64);
buf.append("https://");
String name = httpRequest.getServerName();
boolean ipv6 = name.indexOf(':') >= 0 && !name.startsWith("[");
if (ipv6)
buf.append('[');
buf.append(name);
if (ipv6)
buf.append(']');
buf.append(':').append(httpsPort)
.append(httpRequest.getRequestURI());
String q = httpRequest.getQueryString();
if (q != null)
buf.append('?').append(q);
httpResponse.setHeader("Location", buf.toString());
// https://w3c.github.io/webappsec-upgrade-insecure-requests/
httpResponse.setHeader("Vary", "Upgrade-Insecure-Requests");
httpResponse.setStatus(307);
httpResponse.getOutputStream().close();
}
}