From 291550a4843edbecd371e6f040c3b154dac6189f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 29 Jul 2015 12:38:41 +0200 Subject: [PATCH] AntPathMatcher allows for case-insensitive matching Issue: SPR-13286 --- .../springframework/util/AntPathMatcher.java | 47 ++++++++++++++----- .../util/AntPathMatcherTests.java | 25 ++++++---- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java index 3132b1c46e0..635f1234f1d 100644 --- a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java +++ b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java @@ -79,6 +79,8 @@ public class AntPathMatcher implements PathMatcher { private PathSeparatorPatternCache pathSeparatorPatternCache; + private boolean caseSensitive = true; + private boolean trimTokens = true; private volatile Boolean cachePatterns; @@ -117,6 +119,15 @@ public class AntPathMatcher implements PathMatcher { this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); } + /** + * Specify whether to perform pattern matching in a case-sensitive fashion. + *

Default is {@code true}. Switch this to {@code false} for case-insensitive matching. + * @since 4.2 + */ + public void setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + } + /** * Specify whether to trim tokenized paths and patterns. *

Default is {@code true}. @@ -134,6 +145,7 @@ public class AntPathMatcher implements PathMatcher { * turn it off when encountering too many patterns to cache at runtime * (the threshold is 65536), assuming that arbitrary permutations of patterns * are coming in, with little chance for encountering a recurring pattern. + * @since 4.0.1 * @see #getStringMatcher(String) */ public void setCachePatterns(boolean cachePatterns) { @@ -363,7 +375,7 @@ public class AntPathMatcher implements PathMatcher { matcher = this.stringMatcherCache.get(pattern); } if (matcher == null) { - matcher = new AntPathStringMatcher(pattern); + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { // Try to adapt to the runtime situation that we're encountering: // There are obviously too many different patterns coming in here... @@ -418,7 +430,9 @@ public class AntPathMatcher implements PathMatcher { public Map extractUriTemplateVariables(String pattern, String path) { Map variables = new LinkedHashMap(); boolean result = doMatch(pattern, path, true, variables); - Assert.state(result, "Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } return variables; } @@ -553,12 +567,16 @@ public class AntPathMatcher implements PathMatcher { private final List variableNames = new LinkedList(); public AntPathStringMatcher(String pattern) { + this(pattern, true); + } + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { StringBuilder patternBuilder = new StringBuilder(); - Matcher m = GLOB_PATTERN.matcher(pattern); + Matcher matcher = GLOB_PATTERN.matcher(pattern); int end = 0; - while (m.find()) { - patternBuilder.append(quote(pattern, end, m.start())); - String match = m.group(); + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); if ("?".equals(match)) { patternBuilder.append('.'); } @@ -569,7 +587,7 @@ public class AntPathMatcher implements PathMatcher { int colonIdx = match.indexOf(':'); if (colonIdx == -1) { patternBuilder.append(DEFAULT_VARIABLE_PATTERN); - this.variableNames.add(m.group(1)); + this.variableNames.add(matcher.group(1)); } else { String variablePattern = match.substring(colonIdx + 1, match.length() - 1); @@ -580,10 +598,11 @@ public class AntPathMatcher implements PathMatcher { this.variableNames.add(variableName); } } - end = m.end(); + end = matcher.end(); } patternBuilder.append(quote(pattern, end, pattern.length())); - this.pattern = Pattern.compile(patternBuilder.toString()); + this.pattern = (caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); } private String quote(String s, int start, int end) { @@ -602,10 +621,12 @@ public class AntPathMatcher implements PathMatcher { if (matcher.matches()) { if (uriTemplateVariables != null) { // SPR-8455 - Assert.isTrue(this.variableNames.size() == matcher.groupCount(), - "The number of capturing groups in the pattern segment " + this.pattern + - " does not match the number of URI template variables it defines, which can occur if " + - " capturing groups are used in a URI template regex. Use non-capturing groups instead."); + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } for (int i = 1; i <= matcher.groupCount(); i++) { String name = this.variableNames.get(i - 1); String value = matcher.group(i); diff --git a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java index 8cbd3cd4db8..29a2486a290 100644 --- a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java +++ b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java @@ -298,7 +298,7 @@ public class AntPathMatcherTests { assertEquals("/docs/commit.html", pathMatcher.extractPathWithinPattern("*.html", "/docs/commit.html")); assertEquals("/docs/commit.html", pathMatcher.extractPathWithinPattern("**/*.*", "/docs/commit.html")); assertEquals("/docs/commit.html", pathMatcher.extractPathWithinPattern("*", "/docs/commit.html")); - //SPR-10515 + // SPR-10515 assertEquals("/docs/cvs/other/commit.html", pathMatcher.extractPathWithinPattern("**/commit.html", "/docs/cvs/other/commit.html")); assertEquals("cvs/other/commit.html", pathMatcher.extractPathWithinPattern("/docs/**/commit.html", "/docs/cvs/other/commit.html")); assertEquals("cvs/other/commit.html", pathMatcher.extractPathWithinPattern("/docs/**/**/**/**", "/docs/cvs/other/commit.html")); @@ -452,7 +452,7 @@ public class AntPathMatcherTests { assertEquals(-1, comparator.compare("/hotels/{hotel}/booking", "/hotels/{hotel}/bookings/{booking}")); assertEquals(1, comparator.compare("/hotels/{hotel}/bookings/{booking}", "/hotels/{hotel}/booking")); - //SPR-10550 + // SPR-10550 assertEquals(-1, comparator.compare("/hotels/{hotel}/bookings/{booking}/cutomers/{customer}", "/**")); assertEquals(1, comparator.compare("/**","/hotels/{hotel}/bookings/{booking}/cutomers/{customer}")); assertEquals(0, comparator.compare("/**","/**")); @@ -466,7 +466,7 @@ public class AntPathMatcherTests { assertEquals(-1, comparator.compare("/hotels/new", "/hotels/new.*")); assertEquals(2, comparator.compare("/hotels/{hotel}", "/hotels/{hotel}.*")); - //SPR-6741 + // SPR-6741 assertEquals(-1, comparator.compare("/hotels/{hotel}/bookings/{booking}/cutomers/{customer}", "/hotels/**")); assertEquals(1, comparator.compare("/hotels/**", "/hotels/{hotel}/bookings/{booking}/cutomers/{customer}")); assertEquals(1, comparator.compare("/hotels/foo/bar/**", "/hotels/{hotel}")); @@ -474,13 +474,13 @@ public class AntPathMatcherTests { assertEquals(2, comparator.compare("/hotels/**/bookings/**", "/hotels/**")); assertEquals(-2, comparator.compare("/hotels/**", "/hotels/**/bookings/**")); - //SPR-8683 + // SPR-8683 assertEquals(1, comparator.compare("/**", "/hotels/{hotel}")); // longer is better assertEquals(1, comparator.compare("/hotels", "/hotels2")); - //SPR-13139 + // SPR-13139 assertEquals(-1, comparator.compare("*", "*/**")); assertEquals(1, comparator.compare("*/**", "*")); } @@ -581,15 +581,22 @@ public class AntPathMatcherTests { paths.clear(); } - /** - * SPR-8687 - */ - @Test + @Test // SPR-8687 public void trimTokensOff() { pathMatcher.setTrimTokens(false); assertTrue(pathMatcher.match("/group/{groupName}/members", "/group/sales/members")); assertTrue(pathMatcher.match("/group/{groupName}/members", "/group/ sales/members")); + assertFalse(pathMatcher.match("/group/{groupName}/members", "/Group/ Sales/Members")); + } + + @Test // SPR-13286 + public void caseInsensitive() { + pathMatcher.setCaseSensitive(false); + + assertTrue(pathMatcher.match("/group/{groupName}/members", "/group/sales/members")); + assertTrue(pathMatcher.match("/group/{groupName}/members", "/Group/Sales/Members")); + assertTrue(pathMatcher.match("/Group/{groupName}/Members", "/group/Sales/members")); } @Test