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 org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.IncludeExclude;
import org.eclipse.jetty.util.IncludeExcludeSet;
import org.eclipse.jetty.util.InetAddressPattern;
import org.eclipse.jetty.util.InetAddressSet;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.log.Log;
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
* <p>
@ -43,18 +47,13 @@ import org.eclipse.jetty.util.log.Logger;
* provided by and {@link IncludeExcludeSet} over a {@link InetAddressSet}. This
* handler uses the real internet address of the connection, not one reported in
* the forwarded for headers, as this cannot be as easily forged.
* <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.
* </p>
*/
public class InetAccessHandler extends HandlerWrapper
{
private static final Logger LOG = Log.getLogger(InetAccessHandler.class);
private final IncludeExcludeSet<String, InetAddress> _addrs = new IncludeExcludeSet<>(InetAddressSet.class);
private final IncludeExclude<String> _names = new IncludeExclude<>();
private final IncludeExcludeSet<PatternTuple, AccessTuple> _set = new IncludeExcludeSet<>(InetAccessSet.class);
/**
* Clears all the includes, excludes, included connector names and excluded
@ -62,92 +61,150 @@ public class InetAccessHandler extends HandlerWrapper
*/
public void clear()
{
_addrs.clear();
_names.clear();
_set.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
*/
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
* @see InetAddressSet
*/
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
* @see InetAddressSet
*/
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
* @see InetAddressSet
*/
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.
*
* @param name Connector name to include in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/
@Deprecated
public void includeConnector(String name)
{
_names.include(name);
throw new UnsupportedOperationException();
}
/**
* Excludes a connector name.
*
* @param name Connector name to exclude in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/
@Deprecated
public void excludeConnector(String name)
{
_names.exclude(name);
_set.exclude(new PatternTuple(name, null, null));
}
/**
* Includes connector names.
*
* @param names Connector names to include in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/
@Deprecated
public void includeConnectors(String... names)
{
_names.include(names);
throw new UnsupportedOperationException();
}
/**
* Excludes connector names.
*
* @param names Connector names to exclude in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/
@Deprecated
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)
{
String name = baseRequest.getHttpChannel().getConnector().getName();
boolean filterAppliesToConnector = _names.test(name);
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;
String connectorName = baseRequest.getHttpChannel().getConnector().getName();
return _set.test(new AccessTuple(connectorName, addr, baseRequest.getPathInfo()));
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpObjects(out, indent,
new DumpableCollection("included", _addrs.getIncluded()),
new DumpableCollection("excluded", _addrs.getExcluded()),
new DumpableCollection("includedConnector", _names.getIncluded()),
new DumpableCollection("excludedConnector", _names.getExcluded()));
new DumpableCollection("included", _set.getIncluded()),
new DumpableCollection("excluded", _set.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.List;
import java.util.stream.Stream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
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.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
@ -67,7 +67,6 @@ public class InetAccessHandlerTest
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setStatus(HttpStatus.OK_200);
@ -85,7 +84,7 @@ public class InetAccessHandlerTest
@ParameterizedTest
@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
{
_handler.clear();
@ -107,14 +106,14 @@ public class InetAccessHandlerTest
{
if (inc.length() > 0)
{
_handler.includeConnector(inc);
_handler.include(inc + "@");
}
}
for (String exc : excludeConnectors.split(";", -1))
{
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(_connector2.getLocalPort(), include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(1));
testConnector(_connector1.getLocalPort(), path, include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(0));
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);)
{
@ -139,7 +138,7 @@ public class InetAccessHandlerTest
HttpTester.Request request = HttpTester.newRequest();
request.setMethod("GET");
request.setURI("/path");
request.setURI(StringUtil.isEmpty(path) ? "/" : path);
request.setHeader("Host", "127.0.0.1");
request.setVersion(HttpVersion.HTTP_1_0);
@ -164,62 +163,83 @@ public class InetAccessHandlerTest
public static Stream<Arguments> data()
{
Object[][] data = new Object[][]
{
// Empty lists 1
{"", "", "", "", "200;200"},
{
// Empty lists 1
{"", "", "", "", "", "200;200"},
// test simple filters
{"127.0.0.1", "", "", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "", "", "200;200"},
{"192.0.0.1", "", "", "", "403;403"},
{"192.0.0.1-192.0.0.254", "", "", "", "403;403"},
// test simple filters
{"", "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"},
{"", "192.0.0.1-192.0.0.254", "", "", "", "403;403"},
// test includeConnector
{"127.0.0.1", "", "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-192.0.0.254", "", "http_connector1", "", "403;200"},
{"192.0.0.1", "", "http_connector2", "", "200;403"},
{"192.0.0.1-192.0.0.254", "", "http_connector2", "", "200;403"},
// test includeConnector
{"", "127.0.0.1", "", "http_connector1", "", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "http_connector1", "", "200;200"},
{"", "192.0.0.1", "", "http_connector1", "", "200;403"},
{"", "192.0.0.1-192.0.0.254", "", "http_connector1", "", "200;403"},
{"", "192.0.0.1", "", "http_connector2", "", "403;200"},
{"", "192.0.0.1-192.0.0.254", "", "http_connector2", "", "403;200"},
// test includeConnector names where none of them match
{"127.0.0.1", "", "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-192.0.0.254", "", "nothttp", "", "200;200"},
// test includeConnector names where none of them match
{"", "127.0.0.1", "", "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-192.0.0.254", "", "nothttp", "", "403;403"},
// text excludeConnector
{"127.0.0.1", "", "", "http_connector1", "200;200"},
{"127.0.0.1-127.0.0.254", "", "", "http_connector1", "200;200"},
{"192.0.0.1", "", "", "http_connector1", "200;403"},
{"192.0.0.1-192.0.0.254", "", "", "http_connector1", "200;403"},
{"192.0.0.1", "", "", "http_connector2", "403;200"},
{"192.0.0.1-192.0.0.254", "", "", "http_connector2", "403;200"},
// text excludeConnector
{"", "127.0.0.1", "", "", "http_connector1", "403;200"},
{"", "127.0.0.1-127.0.0.254", "", "", "http_connector1", "403;200"},
{"", "192.0.0.1", "", "", "http_connector1", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "http_connector1", "403;403"},
{"", "192.0.0.1", "", "", "http_connector2", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "http_connector2", "403;403"},
// test excludeConnector where none of them match.
{"127.0.0.1", "", "", "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-192.0.0.254", "", "", "nothttp", "403;403"},
// test excludeConnector where none of them match.
{"", "127.0.0.1", "", "", "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-192.0.0.254", "", "", "nothttp", "403;403"},
// both connectors are excluded
{"127.0.0.1", "", "", "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", "200;200"},
{"192.0.0.1-192.0.0.254", "", "", "http_connector1;http_connector2", "200;200"},
// both connectors are excluded
{"", "127.0.0.1", "", "", "http_connector1;http_connector2", "403;403"},
{"", "127.0.0.1-127.0.0.254", "", "", "http_connector1;http_connector2", "403;403"},
{"", "192.0.0.1", "", "", "http_connector1;http_connector2", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "http_connector1;http_connector2", "403;403"},
// both connectors are included
{"127.0.0.1", "", "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-192.0.0.254", "", "http_connector1;http_connector2", "", "403;403"},
// both connectors are included
{"", "127.0.0.1", "", "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", "", "200;200"},
{"", "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
{"127.0.0.1", "", "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", "200;200"},
{"192.0.0.1", "", "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", "200;200"}
};
return Arrays.asList(data).stream().map(Arguments::of);
// exclude takes precedence over include
{"", "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", "403;403"},
{"", "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", "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.stream(data).map(Arguments::of);
}
}

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.util;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@ -145,10 +146,7 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
public void include(T... element)
{
for (T e : element)
{
_includes.add(e);
}
_includes.addAll(Arrays.asList(element));
}
public void exclude(T element)
@ -158,10 +156,7 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
public void exclude(T... element)
{
for (T e : element)
{
_excludes.add(e);
}
_excludes.addAll(Arrays.asList(element));
}
@Override
@ -233,34 +228,4 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
{
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.
* <p>This is a {@link Set} of String patterns that are used to match
* 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>
* <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>
*
* @see IncludeExcludeSet
*/
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
public boolean add(String pattern)
{
return _patterns.put(pattern, newInetRange(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);
}
return _patterns.put(pattern, InetAddressPattern.from(pattern)) == null;
}
@Override
@ -111,219 +69,11 @@ public class InetAddressSet extends AbstractSet<String> implements Set<String>,
{
if (address == null)
return false;
byte[] raw = address.getAddress();
for (InetPattern pattern : _patterns.values())
for (InetAddressPattern pattern : _patterns.values())
{
if (pattern.test(address, raw))
if (pattern.test(address))
return true;
}
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;
}
}
}