Merge pull request #4615 from eclipse/jetty-10.0.x-4598-InetAccessHandler

Issue #4598 - rework InetAccessHandler to use path mappings
This commit is contained in:
Lachlan 2020-03-01 18:43:32 +11:00 committed by GitHub
commit 6151fc0c36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 610 additions and 389 deletions

View File

@ -26,16 +26,20 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.IncludeExclude;
import org.eclipse.jetty.util.IncludeExcludeSet; import org.eclipse.jetty.util.IncludeExcludeSet;
import org.eclipse.jetty.util.InetAddressPattern;
import org.eclipse.jetty.util.InetAddressSet; import org.eclipse.jetty.util.InetAddressSet;
import org.eclipse.jetty.util.component.DumpableCollection; import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import static org.eclipse.jetty.server.handler.InetAccessSet.AccessTuple;
import static org.eclipse.jetty.server.handler.InetAccessSet.PatternTuple;
/** /**
* InetAddress Access Handler * InetAddress Access Handler
* <p> * <p>
@ -43,18 +47,13 @@ import org.eclipse.jetty.util.log.Logger;
* provided by and {@link IncludeExcludeSet} over a {@link InetAddressSet}. This * provided by and {@link IncludeExcludeSet} over a {@link InetAddressSet}. This
* handler uses the real internet address of the connection, not one reported in * handler uses the real internet address of the connection, not one reported in
* the forwarded for headers, as this cannot be as easily forged. * the forwarded for headers, as this cannot be as easily forged.
* <p> * </p>
* Additionally, there may be times when you want to only apply this handler to
* a subset of your connectors. In this situation you can use
* <b>connectorNames</b> to specify the connector names that you want this IP
* access filter to apply to.
*/ */
public class InetAccessHandler extends HandlerWrapper public class InetAccessHandler extends HandlerWrapper
{ {
private static final Logger LOG = Log.getLogger(InetAccessHandler.class); private static final Logger LOG = Log.getLogger(InetAccessHandler.class);
private final IncludeExcludeSet<String, InetAddress> _addrs = new IncludeExcludeSet<>(InetAddressSet.class); private final IncludeExcludeSet<PatternTuple, AccessTuple> _set = new IncludeExcludeSet<>(InetAccessSet.class);
private final IncludeExclude<String> _names = new IncludeExclude<>();
/** /**
* Clears all the includes, excludes, included connector names and excluded * Clears all the includes, excludes, included connector names and excluded
@ -62,92 +61,150 @@ public class InetAccessHandler extends HandlerWrapper
*/ */
public void clear() public void clear()
{ {
_addrs.clear(); _set.clear();
_names.clear();
} }
/** /**
* Includes an InetAddress pattern * Includes an InetAccess pattern with an optional connector name, address and URI mapping.
* *
* @param pattern InetAddress pattern to include * <p>The connector name is separated from the InetAddress pattern with an '@' character,
* and the InetAddress pattern is separated from the URI pattern using the "|" (pipe)
* character. URI patterns follow the servlet specification for simple * prefix and
* suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).</p>
*
* <br>Examples:
* <ul>
* <li>"connector1@127.0.0.1|/foo"</li>
* <li>"127.0.0.1|/foo"</li>
* <li>"connector1@127.0.0.1"</li>
* <li>"127.0.0.1"</li>
* </ul>
*
* @param pattern InetAccess pattern to include
* @see InetAddressSet * @see InetAddressSet
*/ */
public void include(String pattern) public void include(String pattern)
{ {
_addrs.include(pattern); _set.include(PatternTuple.from(pattern));
} }
/** /**
* Includes InetAddress patterns * Includes InetAccess patterns
* *
* @param patterns InetAddress patterns to include * @param patterns InetAddress patterns to include
* @see InetAddressSet * @see InetAddressSet
*/ */
public void include(String... patterns) public void include(String... patterns)
{ {
_addrs.include(patterns); for (String pattern : patterns)
include(pattern);
} }
/** /**
* Excludes an InetAddress pattern * Includes an InetAccess entry.
* @param connectorName optional name of a connector to include.
* @param addressPattern optional InetAddress pattern to include.
* @param pathSpec optional pathSpec to include.
*/
public void include(String connectorName, String addressPattern, PathSpec pathSpec)
{
_set.include(new PatternTuple(connectorName, InetAddressPattern.from(addressPattern), pathSpec));
}
/**
* Excludes an InetAccess entry pattern with an optional connector name, address and URI mapping.
*
* <p>The connector name is separated from the InetAddress pattern with an '@' character,
* and the InetAddress pattern is separated from the URI pattern using the "|" (pipe)
* character. URI patterns follow the servlet specification for simple * prefix and
* suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).</p>
*
* <br>Examples:
* <ul>
* <li>"connector1@127.0.0.1|/foo"</li>
* <li>"127.0.0.1|/foo"</li>
* <li>"connector1@127.0.0.1"</li>
* <li>"127.0.0.1"</li>
* </ul>
* *
* @param pattern InetAddress pattern to exclude * @param pattern InetAddress pattern to exclude
* @see InetAddressSet * @see InetAddressSet
*/ */
public void exclude(String pattern) public void exclude(String pattern)
{ {
_addrs.exclude(pattern); _set.exclude(PatternTuple.from(pattern));
} }
/** /**
* Excludes InetAddress patterns * Excludes InetAccess patterns
* *
* @param patterns InetAddress patterns to exclude * @param patterns InetAddress patterns to exclude
* @see InetAddressSet * @see InetAddressSet
*/ */
public void exclude(String... patterns) public void exclude(String... patterns)
{ {
_addrs.exclude(patterns); for (String pattern : patterns)
exclude(pattern);
}
/**
* Excludes an InetAccess entry.
* @param connectorName optional name of a connector to exclude.
* @param addressPattern optional InetAddress pattern to exclude.
* @param pathSpec optional pathSpec to exclude.
*/
public void exclude(String connectorName, String addressPattern, PathSpec pathSpec)
{
_set.exclude(new PatternTuple(connectorName, InetAddressPattern.from(addressPattern), pathSpec));
} }
/** /**
* Includes a connector name. * Includes a connector name.
* *
* @param name Connector name to include in this handler. * @param name Connector name to include in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/ */
@Deprecated
public void includeConnector(String name) public void includeConnector(String name)
{ {
_names.include(name); throw new UnsupportedOperationException();
} }
/** /**
* Excludes a connector name. * Excludes a connector name.
* *
* @param name Connector name to exclude in this handler. * @param name Connector name to exclude in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/ */
@Deprecated
public void excludeConnector(String name) public void excludeConnector(String name)
{ {
_names.exclude(name); _set.exclude(new PatternTuple(name, null, null));
} }
/** /**
* Includes connector names. * Includes connector names.
* *
* @param names Connector names to include in this handler. * @param names Connector names to include in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/ */
@Deprecated
public void includeConnectors(String... names) public void includeConnectors(String... names)
{ {
_names.include(names); throw new UnsupportedOperationException();
} }
/** /**
* Excludes connector names. * Excludes connector names.
* *
* @param names Connector names to exclude in this handler. * @param names Connector names to exclude in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/ */
@Deprecated
public void excludeConnectors(String... names) public void excludeConnectors(String... names)
{ {
_names.exclude(names); for (String name : names)
excludeConnector(name);
} }
/** /**
@ -187,26 +244,15 @@ public class InetAccessHandler extends HandlerWrapper
*/ */
protected boolean isAllowed(InetAddress addr, Request baseRequest, HttpServletRequest request) protected boolean isAllowed(InetAddress addr, Request baseRequest, HttpServletRequest request)
{ {
String name = baseRequest.getHttpChannel().getConnector().getName(); String connectorName = baseRequest.getHttpChannel().getConnector().getName();
boolean filterAppliesToConnector = _names.test(name); return _set.test(new AccessTuple(connectorName, addr, baseRequest.getPathInfo()));
boolean allowedByAddr = _addrs.test(addr);
if (LOG.isDebugEnabled())
{
LOG.debug("name = {}/{} addr={}/{} appliesToConnector={} allowedByAddr={}",
name, _names, addr, _addrs, filterAppliesToConnector, allowedByAddr);
}
if (!filterAppliesToConnector)
return true;
return allowedByAddr;
} }
@Override @Override
public void dump(Appendable out, String indent) throws IOException public void dump(Appendable out, String indent) throws IOException
{ {
dumpObjects(out, indent, dumpObjects(out, indent,
new DumpableCollection("included", _addrs.getIncluded()), new DumpableCollection("included", _set.getIncluded()),
new DumpableCollection("excluded", _addrs.getExcluded()), new DumpableCollection("excluded", _set.getExcluded()));
new DumpableCollection("includedConnector", _names.getIncluded()),
new DumpableCollection("excludedConnector", _names.getExcluded()));
} }
} }

View File

@ -0,0 +1,158 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.server.handler;
import java.net.InetAddress;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Predicate;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.util.InetAddressPattern;
import org.eclipse.jetty.util.StringUtil;
public class InetAccessSet extends AbstractSet<InetAccessSet.PatternTuple> implements Set<InetAccessSet.PatternTuple>, Predicate<InetAccessSet.AccessTuple>
{
private ArrayList<PatternTuple> tuples = new ArrayList<>();
@Override
public boolean add(PatternTuple storageTuple)
{
return tuples.add(storageTuple);
}
@Override
public boolean remove(Object o)
{
return tuples.remove(o);
}
@Override
public Iterator<PatternTuple> iterator()
{
return tuples.iterator();
}
@Override
public int size()
{
return tuples.size();
}
@Override
public boolean test(AccessTuple entry)
{
if (entry == null)
return false;
for (PatternTuple tuple : tuples)
{
if (tuple.test(entry))
return true;
}
return false;
}
static class PatternTuple implements Predicate<AccessTuple>
{
private final String connector;
private final InetAddressPattern address;
private final PathSpec pathSpec;
public static PatternTuple from(String pattern)
{
String path = null;
int pathIndex = pattern.indexOf('|');
if (pathIndex >= 0)
path = pattern.substring(pathIndex + 1);
String connector = null;
int connectorIndex = pattern.indexOf('@');
if (connectorIndex >= 0)
connector = pattern.substring(0, connectorIndex);
String addr = null;
int addrStart = (connectorIndex < 0) ? 0 : connectorIndex + 1;
int addrEnd = (pathIndex < 0) ? pattern.length() : pathIndex;
if (addrStart != addrEnd)
addr = pattern.substring(addrStart, addrEnd);
return new PatternTuple(connector, InetAddressPattern.from(addr),
StringUtil.isEmpty(path) ? null : new ServletPathSpec(path));
}
public PatternTuple(String connector, InetAddressPattern address, PathSpec pathSpec)
{
this.connector = connector;
this.address = address;
this.pathSpec = pathSpec;
}
@Override
public boolean test(AccessTuple entry)
{
// Match for connector.
if ((connector != null) && !connector.equals(entry.getConnector()))
return false;
// If we have a path we must must be at this path to match for an address.
if ((pathSpec != null) && !pathSpec.matches(entry.getPath()))
return false;
// Match for InetAddress.
if ((address != null) && !address.test(entry.getAddress()))
return false;
return true;
}
}
static class AccessTuple
{
private final String connector;
private final InetAddress address;
private final String path;
public AccessTuple(String connector, InetAddress address, String path)
{
this.connector = connector;
this.address = address;
this.path = path;
}
public String getConnector()
{
return connector;
}
public InetAddress getAddress()
{
return address;
}
public String getPath()
{
return path;
}
}
}

View File

@ -25,7 +25,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -36,6 +35,7 @@ import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
@ -67,7 +67,6 @@ public class InetAccessHandlerTest
{ {
@Override @Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{ {
baseRequest.setHandled(true); baseRequest.setHandled(true);
response.setStatus(HttpStatus.OK_200); response.setStatus(HttpStatus.OK_200);
@ -85,7 +84,7 @@ public class InetAccessHandlerTest
@ParameterizedTest @ParameterizedTest
@MethodSource("data") @MethodSource("data")
public void testHandler(String include, String exclude, String includeConnectors, String excludeConnectors, String code) public void testHandler(String path, String include, String exclude, String includeConnectors, String excludeConnectors, String code)
throws Exception throws Exception
{ {
_handler.clear(); _handler.clear();
@ -107,14 +106,14 @@ public class InetAccessHandlerTest
{ {
if (inc.length() > 0) if (inc.length() > 0)
{ {
_handler.includeConnector(inc); _handler.include(inc + "@");
} }
} }
for (String exc : excludeConnectors.split(";", -1)) for (String exc : excludeConnectors.split(";", -1))
{ {
if (exc.length() > 0) if (exc.length() > 0)
{ {
_handler.excludeConnector(exc); _handler.exclude(exc + "@");
} }
} }
@ -127,11 +126,11 @@ public class InetAccessHandlerTest
} }
} }
testConnector(_connector1.getLocalPort(), include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(0)); testConnector(_connector1.getLocalPort(), path, include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(0));
testConnector(_connector2.getLocalPort(), include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(1)); testConnector(_connector2.getLocalPort(), path, include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(1));
} }
private void testConnector(int port, String include, String exclude, String includeConnectors, String excludeConnectors, String code) throws IOException private void testConnector(int port, String path, String include, String exclude, String includeConnectors, String excludeConnectors, String code) throws IOException
{ {
try (Socket socket = new Socket("127.0.0.1", port);) try (Socket socket = new Socket("127.0.0.1", port);)
{ {
@ -139,7 +138,7 @@ public class InetAccessHandlerTest
HttpTester.Request request = HttpTester.newRequest(); HttpTester.Request request = HttpTester.newRequest();
request.setMethod("GET"); request.setMethod("GET");
request.setURI("/path"); request.setURI(StringUtil.isEmpty(path) ? "/" : path);
request.setHeader("Host", "127.0.0.1"); request.setHeader("Host", "127.0.0.1");
request.setVersion(HttpVersion.HTTP_1_0); request.setVersion(HttpVersion.HTTP_1_0);
@ -166,60 +165,81 @@ public class InetAccessHandlerTest
Object[][] data = new Object[][] Object[][] data = new Object[][]
{ {
// Empty lists 1 // Empty lists 1
{"", "", "", "", "200;200"}, {"", "", "", "", "", "200;200"},
// test simple filters // test simple filters
{"127.0.0.1", "", "", "", "200;200"}, {"", "127.0.0.1", "", "", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "", "", "200;200"}, {"", "127.0.0.1-127.0.0.254", "", "", "", "200;200"},
{"192.0.0.1", "", "", "", "403;403"}, {"", "127.0.0.1-127.0.0.254", "", "", "", "200;200"},
{"192.0.0.1-192.0.0.254", "", "", "", "403;403"}, {"", "192.0.0.1", "", "", "", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "", "403;403"},
// test includeConnector // test includeConnector
{"127.0.0.1", "", "http_connector1", "", "200;200"}, {"", "127.0.0.1", "", "http_connector1", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "http_connector1", "", "200;200"}, {"", "127.0.0.1-127.0.0.254", "", "http_connector1", "", "200;200"},
{"192.0.0.1", "", "http_connector1", "", "403;200"}, {"", "192.0.0.1", "", "http_connector1", "", "200;403"},
{"192.0.0.1-192.0.0.254", "", "http_connector1", "", "403;200"}, {"", "192.0.0.1-192.0.0.254", "", "http_connector1", "", "200;403"},
{"192.0.0.1", "", "http_connector2", "", "200;403"}, {"", "192.0.0.1", "", "http_connector2", "", "403;200"},
{"192.0.0.1-192.0.0.254", "", "http_connector2", "", "200;403"}, {"", "192.0.0.1-192.0.0.254", "", "http_connector2", "", "403;200"},
// test includeConnector names where none of them match // test includeConnector names where none of them match
{"127.0.0.1", "", "nothttp", "", "200;200"}, {"", "127.0.0.1", "", "nothttp", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "nothttp", "", "200;200"}, {"", "127.0.0.1-127.0.0.254", "", "nothttp", "", "200;200"},
{"192.0.0.1", "", "nothttp", "", "200;200"}, {"", "192.0.0.1", "", "nothttp", "", "403;403"},
{"192.0.0.1-192.0.0.254", "", "nothttp", "", "200;200"}, {"", "192.0.0.1-192.0.0.254", "", "nothttp", "", "403;403"},
// text excludeConnector // text excludeConnector
{"127.0.0.1", "", "", "http_connector1", "200;200"}, {"", "127.0.0.1", "", "", "http_connector1", "403;200"},
{"127.0.0.1-127.0.0.254", "", "", "http_connector1", "200;200"}, {"", "127.0.0.1-127.0.0.254", "", "", "http_connector1", "403;200"},
{"192.0.0.1", "", "", "http_connector1", "200;403"}, {"", "192.0.0.1", "", "", "http_connector1", "403;403"},
{"192.0.0.1-192.0.0.254", "", "", "http_connector1", "200;403"}, {"", "192.0.0.1-192.0.0.254", "", "", "http_connector1", "403;403"},
{"192.0.0.1", "", "", "http_connector2", "403;200"}, {"", "192.0.0.1", "", "", "http_connector2", "403;403"},
{"192.0.0.1-192.0.0.254", "", "", "http_connector2", "403;200"}, {"", "192.0.0.1-192.0.0.254", "", "", "http_connector2", "403;403"},
// test excludeConnector where none of them match. // test excludeConnector where none of them match.
{"127.0.0.1", "", "", "nothttp", "200;200"}, {"", "127.0.0.1", "", "", "nothttp", "200;200"},
{"127.0.0.1-127.0.0.254", "", "", "nothttp", "200;200"}, {"", "127.0.0.1-127.0.0.254", "", "", "nothttp", "200;200"},
{"192.0.0.1", "", "", "nothttp", "403;403"}, {"", "192.0.0.1", "", "", "nothttp", "403;403"},
{"192.0.0.1-192.0.0.254", "", "", "nothttp", "403;403"}, {"", "192.0.0.1-192.0.0.254", "", "", "nothttp", "403;403"},
// both connectors are excluded // both connectors are excluded
{"127.0.0.1", "", "", "http_connector1;http_connector2", "200;200"}, {"", "127.0.0.1", "", "", "http_connector1;http_connector2", "403;403"},
{"127.0.0.1-127.0.0.254", "", "", "http_connector1;http_connector2", "200;200"}, {"", "127.0.0.1-127.0.0.254", "", "", "http_connector1;http_connector2", "403;403"},
{"192.0.0.1", "", "", "http_connector1;http_connector2", "200;200"}, {"", "192.0.0.1", "", "", "http_connector1;http_connector2", "403;403"},
{"192.0.0.1-192.0.0.254", "", "", "http_connector1;http_connector2", "200;200"}, {"", "192.0.0.1-192.0.0.254", "", "", "http_connector1;http_connector2", "403;403"},
// both connectors are included // both connectors are included
{"127.0.0.1", "", "http_connector1;http_connector2", "", "200;200"}, {"", "127.0.0.1", "", "http_connector1;http_connector2", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "", "200;200"}, {"", "127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "", "200;200"},
{"192.0.0.1", "", "http_connector1;http_connector2", "", "403;403"}, {"", "192.0.0.1", "", "http_connector1;http_connector2", "", "200;200"},
{"192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "", "403;403"}, {"", "192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "", "200;200"},
{"", "", "127.0.0.1", "http_connector1;http_connector2", "", "403;403"},
// exclude takes precedence over include // exclude takes precedence over include
{"127.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"}, {"", "127.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "403;403"},
{"127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"}, {"", "127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "403;403"},
{"192.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"}, {"", "192.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "403;403"},
{"192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"} {"", "192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "403;403"},
// Test path specific include/exclude.
{"/testPath", "", "", "http_connector1", "", "200;403"},
{"/", "127.0.0.1", "127.0.0.1|/testPath", "http_connector1", "", "200;200"},
{"/testPath", "127.0.0.1", "127.0.0.1|/testPath", "http_connector1", "", "403;403"},
{"/notTestPath", "127.0.1.11|/testPath", "", "http_connector1", "", "200;403"},
{"/testPath", "127.0.1.11|/testPath", "", "http_connector1", "", "200;403"},
{"/testPath", "127.0.0.13|/testPath;127.0.0.1|/testPath", "", "http_connector1", "", "200;200"},
{"/testPath", "127.0.0.1", "127.0.0.1|/testPath", "http_connector1", "", "403;403"},
{"/", "127.0.0.1", "127.0.0.1|/testPath", "http_connector1", "", "200;200"},
{"/a/b", "", "127.0.0.1|/a/*", "", "", "403;403"},
{"/b/a", "", "127.0.0.1|/a/*", "", "", "200;200"},
{"/org/eclipse/jetty/test.html", "127.0.0.1|*.html", "127.0.0.1|*.xml", "", "", "200;200"},
{"/org/eclipse/jetty/test.xml", "127.0.0.1|*.html", "127.0.0.1|*.xml", "", "", "403;403"},
{"/org/eclipse/jetty/test.pdf", "127.0.0.1|*.html", "127.0.0.1|*.xml", "", "", "403;403"},
{"/a/test.html", "", "127.0.0.1|*.html;127.0.0.10|/a/*", "", "", "403;403"},
{"/foo/bar/test.xml", "127.0.0.1|/foo/*", "127.0.0.0-127.0.0.2|*.html", "", "", "200;200"},
{"/foo/bar/test.html", "127.0.0.1|/foo/*", "127.0.0.0-127.0.0.2|*.html", "", "", "403;403"},
{"/foo/bar/test.xml", "127.0.0.1|/foo/bar/*", "127.0.0.1|/foo/*", "", "", "403;403"},
}; };
return Arrays.asList(data).stream().map(Arguments::of); return Arrays.stream(data).map(Arguments::of);
} }
} }

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.util; package org.eclipse.jetty.util;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -145,10 +146,7 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
public void include(T... element) public void include(T... element)
{ {
for (T e : element) _includes.addAll(Arrays.asList(element));
{
_includes.add(e);
}
} }
public void exclude(T element) public void exclude(T element)
@ -158,10 +156,7 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
public void exclude(T... element) public void exclude(T... element)
{ {
for (T e : element) _excludes.addAll(Arrays.asList(element));
{
_excludes.add(e);
}
} }
@Override @Override
@ -233,34 +228,4 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
{ {
return _includes.isEmpty() && _excludes.isEmpty(); return _includes.isEmpty() && _excludes.isEmpty();
} }
/**
* Match items in combined IncludeExcludeSets.
* @param item1 The item to match against set1
* @param set1 A IncludeExcludeSet to match item1 against
* @param item2 The item to match against set2
* @param set2 A IncludeExcludeSet to match item2 against
* @param <T1> The type of item1
* @param <T2> The type of item2
* @return True IFF <ul>
* <li>Neither item is excluded from their respective sets</li>
* <li>Both sets have no includes OR at least one of the items is included in its respective set</li>
* </ul>
*/
public static <T1,T2> boolean matchCombined(T1 item1, IncludeExcludeSet<?,T1> set1, T2 item2, IncludeExcludeSet<?,T2> set2)
{
Boolean match1 = set1.isIncludedAndNotExcluded(item1);
Boolean match2 = set2.isIncludedAndNotExcluded(item2);
// if we are excluded from either set, then we do not match
if (match1 == Boolean.FALSE || match2 == Boolean.FALSE)
return false;
// If either set has any includes, then we must be included by one of them
if (set1.hasIncludes() || set2.hasIncludes())
return match1 == Boolean.TRUE || match2 == Boolean.TRUE;
// If not excluded and no includes, then we match
return true;
}
} }

View File

@ -0,0 +1,282 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util;
import java.net.InetAddress;
import java.util.function.Predicate;
/**
* A pattern representing a single or range of {@link InetAddress}. To create a pattern use
* the {@link InetAddressPattern#from(String)} method, which will create a pattern given a
* string conforming to one of the following formats.
*
* <dl>
* <dt>InetAddress</dt>
* <dd>A single InetAddress either in hostname or address format.
* All formats supported by {@link InetAddress} are accepted. Not ethat using hostname
* matches may force domain lookups. eg. "[::1]", "1.2.3.4", "::ffff:127.0.0.1"</dd>
* <dt>InetAddress/CIDR</dt>
* <dd>An InetAddress with a integer number of bits to indicate
* the significant prefix. eg. "192.168.0.0/16" will match from "192.168.0.0" to
* "192.168.255.255" </dd>
* <dt>InetAddress-InetAddress</dt>
* <dd>An inclusive range of InetAddresses.
* eg. "[a000::1]-[afff::]", "192.168.128.0-192.168.128.255"</dd>
* <dt>Legacy format</dt>
* <dd>The legacy format used for IPv4 only.
* eg. "10.10.10-14.0-128"</dd>
* </dl>
*/
public abstract class InetAddressPattern implements Predicate<InetAddress>
{
protected final String _pattern;
public static InetAddressPattern from(String pattern)
{
if (pattern == null)
return null;
int slash = pattern.lastIndexOf('/');
int dash = pattern.lastIndexOf('-');
try
{
if (slash >= 0)
return new CidrInetAddressRange(pattern, InetAddress.getByName(pattern.substring(0, slash).trim()), StringUtil.toInt(pattern, slash + 1));
if (dash >= 0)
return new MinMaxInetAddressRange(pattern, InetAddress.getByName(pattern.substring(0, dash).trim()), InetAddress.getByName(pattern.substring(dash + 1).trim()));
return new SingletonInetAddressRange(pattern, InetAddress.getByName(pattern));
}
catch (Exception e)
{
try
{
if (slash < 0 && dash > 0)
return new LegacyInetAddressRange(pattern);
}
catch (Exception ex2)
{
e.addSuppressed(ex2);
}
throw new IllegalArgumentException("Bad pattern: " + pattern, e);
}
}
public InetAddressPattern(String pattern)
{
_pattern = pattern;
}
@Override
public String toString()
{
return _pattern;
}
static class SingletonInetAddressRange extends InetAddressPattern
{
final InetAddress _address;
public SingletonInetAddressRange(String pattern, InetAddress address)
{
super(pattern);
_address = address;
}
@Override
public boolean test(InetAddress address)
{
return _address.equals(address);
}
}
static class MinMaxInetAddressRange extends InetAddressPattern
{
final int[] _min;
final int[] _max;
public MinMaxInetAddressRange(String pattern, InetAddress min, InetAddress max)
{
super(pattern);
byte[] rawMin = min.getAddress();
byte[] rawMax = max.getAddress();
if (rawMin.length != rawMax.length)
throw new IllegalArgumentException("Cannot mix IPv4 and IPv6: " + pattern);
if (rawMin.length == 4)
{
// there must be 6 '.' or this is likely to be a legacy pattern
int count = 0;
for (char c : pattern.toCharArray())
{
if (c == '.')
count++;
}
if (count != 6)
throw new IllegalArgumentException("Legacy pattern: " + pattern);
}
_min = new int[rawMin.length];
_max = new int[rawMin.length];
for (int i = 0; i < _min.length; i++)
{
_min[i] = 0xff & rawMin[i];
_max[i] = 0xff & rawMax[i];
}
for (int i = 0; i < _min.length; i++)
{
if (_min[i] > _max[i])
throw new IllegalArgumentException("min is greater than max: " + pattern);
if (_min[i] < _max[i])
break;
}
}
@Override
public boolean test(InetAddress address)
{
byte[] raw = address.getAddress();
if (raw.length != _min.length)
return false;
boolean minOk = false;
boolean maxOk = false;
for (int i = 0; i < _min.length; i++)
{
int r = 0xff & raw[i];
if (!minOk)
{
if (r < _min[i])
return false;
if (r > _min[i])
minOk = true;
}
if (!maxOk)
{
if (r > _max[i])
return false;
if (r < _max[i])
maxOk = true;
}
if (minOk && maxOk)
break;
}
return true;
}
}
static class CidrInetAddressRange extends InetAddressPattern
{
final byte[] _raw;
final int _octets;
final int _mask;
final int _masked;
public CidrInetAddressRange(String pattern, InetAddress address, int cidr)
{
super(pattern);
_raw = address.getAddress();
_octets = cidr / 8;
_mask = 0xff & (0xff << (8 - cidr % 8));
_masked = _mask == 0 ? 0 : _raw[_octets] & _mask;
if (cidr > (_raw.length * 8))
throw new IllegalArgumentException("CIDR too large: " + pattern);
if (_mask != 0 && (0xff & _raw[_octets]) != _masked)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
for (int o = _octets + (_mask == 0 ? 0 : 1); o < _raw.length; o++)
{
if (_raw[o] != 0)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
}
}
@Override
public boolean test(InetAddress address)
{
byte[] raw = address.getAddress();
if (raw.length != _raw.length)
return false;
for (int o = 0; o < _octets; o++)
{
if (_raw[o] != raw[o])
return false;
}
return _mask == 0 || (raw[_octets] & _mask) == _masked;
}
}
static class LegacyInetAddressRange extends InetAddressPattern
{
int[] _min = new int[4];
int[] _max = new int[4];
public LegacyInetAddressRange(String pattern)
{
super(pattern);
String[] parts = pattern.split("\\.");
if (parts.length != 4)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
for (int i = 0; i < 4; i++)
{
String part = parts[i].trim();
int dash = part.indexOf('-');
if (dash < 0)
_min[i] = _max[i] = Integer.parseInt(part);
else
{
_min[i] = (dash == 0) ? 0 : StringUtil.toInt(part, 0);
_max[i] = (dash == part.length() - 1) ? 255 : StringUtil.toInt(part, dash + 1);
}
if (_min[i] < 0 || _min[i] > _max[i] || _max[i] > 255)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
}
}
@Override
public boolean test(InetAddress address)
{
byte[] raw = address.getAddress();
if (raw.length != 4)
return false;
for (int i = 0; i < 4; i++)
{
if ((0xff & raw[i]) < _min[i] || (0xff & raw[i]) > _max[i])
return false;
}
return true;
}
}
}

View File

@ -30,62 +30,20 @@ import java.util.function.Predicate;
* A set of InetAddress patterns. * A set of InetAddress patterns.
* <p>This is a {@link Set} of String patterns that are used to match * <p>This is a {@link Set} of String patterns that are used to match
* a {@link Predicate} over InetAddress for containment semantics. * a {@link Predicate} over InetAddress for containment semantics.
* The patterns that may be set are: * The patterns that may be set are defined in {@link InetAddressPattern}.
* </p> * </p>
* <dl>
* <dt>InetAddress</dt><dd>A single InetAddress either in hostname or address format.
* All formats supported by {@link InetAddress} are accepted. Not ethat using hostname
* matches may force domain lookups. eg. "[::1]", "1.2.3.4", "::ffff:127.0.0.1"</dd>
* <dt>InetAddress/CIDR</dt><dd>An InetAddress with a integer number of bits to indicate
* the significant prefix. eg. "192.168.0.0/16" will match from "192.168.0.0" to
* "192.168.255.255" </dd>
* <dt>InetAddress-InetAddress</dt><dd>An inclusive range of InetAddresses.
* eg. "[a000::1]-[afff::]", "192.168.128.0-192.168.128.255"</dd>
* </dl>
* <p>This class is designed to work with {@link IncludeExcludeSet}</p> * <p>This class is designed to work with {@link IncludeExcludeSet}</p>
* *
* @see IncludeExcludeSet * @see IncludeExcludeSet
*/ */
public class InetAddressSet extends AbstractSet<String> implements Set<String>, Predicate<InetAddress> public class InetAddressSet extends AbstractSet<String> implements Set<String>, Predicate<InetAddress>
{ {
private Map<String, InetPattern> _patterns = new HashMap<>(); private Map<String, InetAddressPattern> _patterns = new HashMap<>();
@Override @Override
public boolean add(String pattern) public boolean add(String pattern)
{ {
return _patterns.put(pattern, newInetRange(pattern)) == null; return _patterns.put(pattern, InetAddressPattern.from(pattern)) == null;
}
private InetPattern newInetRange(String pattern)
{
if (pattern == null)
return null;
int slash = pattern.lastIndexOf('/');
int dash = pattern.lastIndexOf('-');
try
{
if (slash >= 0)
return new CidrInetRange(pattern, InetAddress.getByName(pattern.substring(0, slash).trim()), StringUtil.toInt(pattern, slash + 1));
if (dash >= 0)
return new MinMaxInetRange(pattern, InetAddress.getByName(pattern.substring(0, dash).trim()), InetAddress.getByName(pattern.substring(dash + 1).trim()));
return new SingletonInetRange(pattern, InetAddress.getByName(pattern));
}
catch (Exception e)
{
try
{
if (slash < 0 && dash > 0)
return new LegacyInetRange(pattern);
}
catch (Exception e2)
{
e.addSuppressed(e2);
}
throw new IllegalArgumentException("Bad pattern: " + pattern, e);
}
} }
@Override @Override
@ -111,219 +69,11 @@ public class InetAddressSet extends AbstractSet<String> implements Set<String>,
{ {
if (address == null) if (address == null)
return false; return false;
byte[] raw = address.getAddress(); for (InetAddressPattern pattern : _patterns.values())
for (InetPattern pattern : _patterns.values())
{ {
if (pattern.test(address, raw)) if (pattern.test(address))
return true; return true;
} }
return false; return false;
} }
abstract static class InetPattern
{
final String _pattern;
InetPattern(String pattern)
{
_pattern = pattern;
}
abstract boolean test(InetAddress address, byte[] raw);
@Override
public String toString()
{
return _pattern;
}
}
static class SingletonInetRange extends InetPattern
{
final InetAddress _address;
public SingletonInetRange(String pattern, InetAddress address)
{
super(pattern);
_address = address;
}
@Override
public boolean test(InetAddress address, byte[] raw)
{
return _address.equals(address);
}
}
static class MinMaxInetRange extends InetPattern
{
final int[] _min;
final int[] _max;
public MinMaxInetRange(String pattern, InetAddress min, InetAddress max)
{
super(pattern);
byte[] rawMin = min.getAddress();
byte[] rawMax = max.getAddress();
if (rawMin.length != rawMax.length)
throw new IllegalArgumentException("Cannot mix IPv4 and IPv6: " + pattern);
if (rawMin.length == 4)
{
// there must be 6 '.' or this is likely to be a legacy pattern
int count = 0;
for (char c : pattern.toCharArray())
{
if (c == '.')
count++;
}
if (count != 6)
throw new IllegalArgumentException("Legacy pattern: " + pattern);
}
_min = new int[rawMin.length];
_max = new int[rawMin.length];
for (int i = 0; i < _min.length; i++)
{
_min[i] = 0xff & rawMin[i];
_max[i] = 0xff & rawMax[i];
}
for (int i = 0; i < _min.length; i++)
{
if (_min[i] > _max[i])
throw new IllegalArgumentException("min is greater than max: " + pattern);
if (_min[i] < _max[i])
break;
}
}
@Override
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length != _min.length)
return false;
boolean minOk = false;
boolean maxOk = false;
for (int i = 0; i < _min.length; i++)
{
int r = 0xff & raw[i];
if (!minOk)
{
if (r < _min[i])
return false;
if (r > _min[i])
minOk = true;
}
if (!maxOk)
{
if (r > _max[i])
return false;
if (r < _max[i])
maxOk = true;
}
if (minOk && maxOk)
break;
}
return true;
}
}
static class CidrInetRange extends InetPattern
{
final byte[] _raw;
final int _octets;
final int _mask;
final int _masked;
public CidrInetRange(String pattern, InetAddress address, int cidr)
{
super(pattern);
_raw = address.getAddress();
_octets = cidr / 8;
_mask = 0xff & (0xff << (8 - cidr % 8));
_masked = _mask == 0 ? 0 : _raw[_octets] & _mask;
if (cidr > (_raw.length * 8))
throw new IllegalArgumentException("CIDR too large: " + pattern);
if (_mask != 0 && (0xff & _raw[_octets]) != _masked)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
for (int o = _octets + (_mask == 0 ? 0 : 1); o < _raw.length; o++)
{
if (_raw[o] != 0)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
}
}
@Override
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length != _raw.length)
return false;
for (int o = 0; o < _octets; o++)
{
if (_raw[o] != raw[o])
return false;
}
if (_mask != 0 && (raw[_octets] & _mask) != _masked)
return false;
return true;
}
}
static class LegacyInetRange extends InetPattern
{
int[] _min = new int[4];
int[] _max = new int[4];
public LegacyInetRange(String pattern)
{
super(pattern);
String[] parts = pattern.split("\\.");
if (parts.length != 4)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
for (int i = 0; i < 4; i++)
{
String part = parts[i].trim();
int dash = part.indexOf('-');
if (dash < 0)
_min[i] = _max[i] = Integer.parseInt(part);
else
{
_min[i] = (dash == 0) ? 0 : StringUtil.toInt(part, 0);
_max[i] = (dash == part.length() - 1) ? 255 : StringUtil.toInt(part, dash + 1);
}
if (_min[i] < 0 || _min[i] > _max[i] || _max[i] > 255)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
}
}
@Override
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length != 4)
return false;
for (int i = 0; i < 4; i++)
{
if ((0xff & raw[i]) < _min[i] || (0xff & raw[i]) > _max[i])
return false;
}
return true;
}
}
} }