diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java index de4d32d2973..d7f68f414b0 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java @@ -43,6 +43,8 @@ import org.slf4j.LoggerFactory; public class PathMappings implements Iterable>, Dumpable { private static final Logger LOG = LoggerFactory.getLogger(PathMappings.class); + // In prefix matches, this is the length ("/*".length() + 1) - used for the best prefix match loop + private static final int PREFIX_TAIL_LEN = 3; private final Set> _mappings = new TreeSet<>(Comparator.comparing(MappedResource::getPathSpec)); /** @@ -205,11 +207,14 @@ public class PathMappings implements Iterable>, Dumpable // Try a prefix match MappedResource prefix = _prefixMap.getBest(path); - if (prefix != null) + while (prefix != null) { - MatchedPath matchedPath = prefix.getPathSpec().matched(path); + PathSpec pathSpec = prefix.getPathSpec(); + MatchedPath matchedPath = pathSpec.matched(path); if (matchedPath != null) - return new MatchedResource<>(prefix.getResource(), prefix.getPathSpec(), matchedPath); + return new MatchedResource<>(prefix.getResource(), pathSpec, matchedPath); + int specLength = pathSpec.getSpecLength(); + prefix = specLength > PREFIX_TAIL_LEN ? _prefixMap.getBest(path, 0, specLength - PREFIX_TAIL_LEN) : null; } // Try a suffix match @@ -223,13 +228,13 @@ public class PathMappings implements Iterable>, Dumpable // Loop 3: "foo" while ((i = path.indexOf('.', i + 1)) > 0) { - prefix = _suffixMap.get(path, i + 1, path.length() - i - 1); - if (prefix == null) + MappedResource suffix = _suffixMap.get(path, i + 1, path.length() - i - 1); + if (suffix == null) continue; - MatchedPath matchedPath = prefix.getPathSpec().matched(path); + MatchedPath matchedPath = suffix.getPathSpec().matched(path); if (matchedPath != null) - return new MatchedResource<>(prefix.getResource(), prefix.getPathSpec(), matchedPath); + return new MatchedResource<>(suffix.getResource(), suffix.getPathSpec(), matchedPath); } } @@ -286,12 +291,15 @@ public class PathMappings implements Iterable>, Dumpable { if (_optimizedPrefix) { - MappedResource candidate = _prefixMap.getBest(path); - if (candidate != null) + MappedResource prefix = _prefixMap.getBest(path); + while (prefix != null) { - matchedPath = candidate.getPathSpec().matched(path); + PathSpec pathSpec = prefix.getPathSpec(); + matchedPath = pathSpec.matched(path); if (matchedPath != null) - return new MatchedResource<>(candidate.getResource(), candidate.getPathSpec(), matchedPath); + return new MatchedResource<>(prefix.getResource(), pathSpec, matchedPath); + int specLength = pathSpec.getSpecLength(); + prefix = specLength > PREFIX_TAIL_LEN ? _prefixMap.getBest(path, 0, specLength - PREFIX_TAIL_LEN) : null; } // If we reached here, there's NO optimized PREFIX Match possible, skip simple match below @@ -312,13 +320,13 @@ public class PathMappings implements Iterable>, Dumpable // Loop 3: "foo" while ((i = path.indexOf('.', i + 1)) > 0) { - MappedResource candidate = _suffixMap.get(path, i + 1, path.length() - i - 1); - if (candidate == null) + MappedResource suffix = _suffixMap.get(path, i + 1, path.length() - i - 1); + if (suffix == null) continue; - matchedPath = candidate.getPathSpec().matched(path); + matchedPath = suffix.getPathSpec().matched(path); if (matchedPath != null) - return new MatchedResource<>(candidate.getResource(), candidate.getPathSpec(), matchedPath); + return new MatchedResource<>(suffix.getResource(), suffix.getPathSpec(), matchedPath); } // If we reached here, there's NO optimized SUFFIX Match possible, skip simple match below skipRestOfGroup = true; diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java index 3c312d7d6b3..4eee6faf712 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java @@ -92,6 +92,55 @@ public class PathMappingsTest assertMatch(p, "/", "any"); } + /** + * Test the match order rules imposed by the Servlet API (any vs specific sub-dir) + */ + @Test + public void testServletMatchPrefix() + { + PathMappings p = new PathMappings<>(); + + p.put(new ServletPathSpec("/*"), "any"); + p.put(new ServletPathSpec("/foo/*"), "foo"); + p.put(new ServletPathSpec("/food/*"), "food"); + p.put(new ServletPathSpec("/a/*"), "a"); + p.put(new ServletPathSpec("/a/b/*"), "ab"); + + assertMatch(p, "/abs/path", "any"); + assertMatch(p, "/abs/foo/bar", "any"); + assertMatch(p, "/foo/bar", "foo"); + assertMatch(p, "/", "any"); + assertMatch(p, "/foo", "foo"); + assertMatch(p, "/fo", "any"); + assertMatch(p, "/foobar", "any"); + assertMatch(p, "/foob", "any"); + assertMatch(p, "/food", "food"); + assertMatch(p, "/food/zed", "food"); + assertMatch(p, "/foodie", "any"); + assertMatch(p, "/a/bc", "a"); + assertMatch(p, "/a/b/c", "ab"); + assertMatch(p, "/a/", "a"); + assertMatch(p, "/a", "a"); + + // Try now with order important + p.put(new RegexPathSpec("/other.*/"), "other"); + assertMatch(p, "/abs/path", "any"); + assertMatch(p, "/abs/foo/bar", "any"); + assertMatch(p, "/foo/bar", "foo"); + assertMatch(p, "/", "any"); + assertMatch(p, "/foo", "foo"); + assertMatch(p, "/fo", "any"); + assertMatch(p, "/foobar", "any"); + assertMatch(p, "/foob", "any"); + assertMatch(p, "/food", "food"); + assertMatch(p, "/food/zed", "food"); + assertMatch(p, "/foodie", "any"); + assertMatch(p, "/a/bc", "a"); + assertMatch(p, "/a/b/c", "ab"); + assertMatch(p, "/a/", "a"); + assertMatch(p, "/a", "a"); + } + /** * Test the match order rules with a mixed Servlet and URI Template path specs *