diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessHandler.java index 3407ac1f6a7..b3292aeb38a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessHandler.java @@ -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 *

@@ -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. - *

- * 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 - * connectorNames to specify the connector names that you want this IP - * access filter to apply to. + *

*/ public class InetAccessHandler extends HandlerWrapper { private static final Logger LOG = Log.getLogger(InetAccessHandler.class); - private final IncludeExcludeSet _addrs = new IncludeExcludeSet<>(InetAddressSet.class); - private final IncludeExclude _names = new IncludeExclude<>(); + private final IncludeExcludeSet _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 + *

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).

+ * + *
Examples: + *
    + *
  • "connector1@127.0.0.1|/foo"
  • + *
  • "127.0.0.1|/foo"
  • + *
  • "connector1@127.0.0.1"
  • + *
  • "127.0.0.1"
  • + *
+ * + * @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. + * + *

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).

+ * + *
Examples: + *
    + *
  • "connector1@127.0.0.1|/foo"
  • + *
  • "127.0.0.1|/foo"
  • + *
  • "connector1@127.0.0.1"
  • + *
  • "127.0.0.1"
  • + *
* * @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())); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessSet.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessSet.java new file mode 100644 index 00000000000..8cf62c3f279 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessSet.java @@ -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 implements Set, Predicate +{ + private ArrayList 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 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 + { + 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; + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/InetAccessHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/InetAccessHandlerTest.java index 244c41e6a6d..cd7d5427783 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/InetAccessHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/InetAccessHandlerTest.java @@ -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 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); } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExcludeSet.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExcludeSet.java index a04fa5ff3c5..99a6817ac9a 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExcludeSet.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExcludeSet.java @@ -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 implements Predicate

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 implements Predicate

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 implements Predicate

{ 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 The type of item1 - * @param The type of item2 - * @return True IFF

    - *
  • Neither item is excluded from their respective sets
  • - *
  • Both sets have no includes OR at least one of the items is included in its respective set
  • - *
- */ - public static boolean matchCombined(T1 item1, IncludeExcludeSet set1, T2 item2, IncludeExcludeSet 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; - } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/InetAddressPattern.java b/jetty-util/src/main/java/org/eclipse/jetty/util/InetAddressPattern.java new file mode 100644 index 00000000000..e64b818de5a --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/InetAddressPattern.java @@ -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. + * + *
+ *
InetAddress
+ *
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"
+ *
InetAddress/CIDR
+ *
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"
+ *
InetAddress-InetAddress
+ *
An inclusive range of InetAddresses. + * eg. "[a000::1]-[afff::]", "192.168.128.0-192.168.128.255"
+ *
Legacy format
+ *
The legacy format used for IPv4 only. + * eg. "10.10.10-14.0-128"
+ *
+ */ +public abstract class InetAddressPattern implements Predicate +{ + 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; + } + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/InetAddressSet.java b/jetty-util/src/main/java/org/eclipse/jetty/util/InetAddressSet.java index 621cdb7905c..6aa3e9038f9 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/InetAddressSet.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/InetAddressSet.java @@ -30,62 +30,20 @@ import java.util.function.Predicate; * A set of InetAddress patterns. *

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}. *

- *
- *
InetAddress
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"
- *
InetAddress/CIDR
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"
- *
InetAddress-InetAddress
An inclusive range of InetAddresses. - * eg. "[a000::1]-[afff::]", "192.168.128.0-192.168.128.255"
- *
*

This class is designed to work with {@link IncludeExcludeSet}

* * @see IncludeExcludeSet */ public class InetAddressSet extends AbstractSet implements Set, Predicate { - private Map _patterns = new HashMap<>(); + private Map _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 implements Set, { 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; - } - } }