Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version=5.3.39.RELEASE-TT.3
version=5.3.39.RELEASE-TT.4
org.gradle.jvmargs=-Xmx2048m
org.gradle.caching=true
org.gradle.parallel=true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.logging.Log;
Expand Down Expand Up @@ -460,15 +459,13 @@ public String[] getAllowedFields() {
* <p>Mark fields as disallowed, for example to avoid unwanted
* modifications by malicious users when binding HTTP request parameters.
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality.
* <p>The default implementation of this method stores disallowed field patterns
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
* form. As of Spring Framework 5.2.21, the default implementation also transforms
* disallowed field patterns to {@linkplain String#toLowerCase() lowercase} to
* support case-insensitive pattern matching in {@link #isAllowed}. Subclasses
* which override this method must therefore take both of these transformations
* into account.
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts),
* as well as direct equality.
* <p>The default implementation of this method stores disallowed field
* patterns in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String)
* canonical} form, and subsequently pattern matching in {@link #isAllowed}
* is case-insensitive. Subclasses that override this method must therefore
* take this transformation into account.
* <p>More sophisticated matching can be implemented by overriding the
* {@link #isAllowed} method.
* <p>Alternatively, specify a list of <i>allowed</i> field patterns.
Expand All @@ -483,8 +480,7 @@ public void setDisallowedFields(@Nullable String... disallowedFields) {
else {
String[] fieldPatterns = new String[disallowedFields.length];
for (int i = 0; i < fieldPatterns.length; i++) {
String field = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]);
fieldPatterns[i] = field.toLowerCase(Locale.ROOT);
fieldPatterns[i] = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]);
}
this.disallowedFields = fieldPatterns;
}
Expand Down Expand Up @@ -808,9 +804,9 @@ protected void checkAllowedFields(MutablePropertyValues mpvs) {
* Determine if the given field is allowed for binding.
* <p>Invoked for each passed-in property value.
* <p>Checks for {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality, in the configured lists of allowed field patterns
* and disallowed field patterns.
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts),
* as well as direct equality, in the configured lists of allowed field
* patterns and disallowed field patterns.
* <p>Matching against allowed field patterns is case-sensitive; whereas,
* matching against disallowed field patterns is case-insensitive.
* <p>A field matching a disallowed pattern will not be accepted even if it
Expand All @@ -826,8 +822,13 @@ protected void checkAllowedFields(MutablePropertyValues mpvs) {
protected boolean isAllowed(String field) {
String[] allowed = getAllowedFields();
String[] disallowed = getDisallowedFields();
return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field.toLowerCase(Locale.ROOT))));
if (!ObjectUtils.isEmpty(allowed) && !PatternMatchUtils.simpleMatch(allowed, field)) {
return false;
}
if (!ObjectUtils.isEmpty(disallowed)) {
return !PatternMatchUtils.simpleMatchIgnoreCase(disallowed, field);
}
return true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,24 @@ public abstract class PatternMatchUtils {
* @return whether the String matches the given pattern
*/
public static boolean simpleMatch(@Nullable String pattern, @Nullable String str) {
return simpleMatch(pattern, str, false);
}

/**
* Variant of {@link #simpleMatch(String, String)} that ignores upper/lower case.
*/
public static boolean simpleMatchIgnoreCase(@Nullable String pattern, @Nullable String str) {
return simpleMatch(pattern, str, true);
}

private static boolean simpleMatch(@Nullable String pattern, @Nullable String str, boolean ignoreCase) {
if (pattern == null || str == null) {
return false;
}

int firstIndex = pattern.indexOf('*');
if (firstIndex == -1) {
return pattern.equals(str);
return (ignoreCase ? pattern.equalsIgnoreCase(str) : pattern.equals(str));
}

if (firstIndex == 0) {
Expand All @@ -51,25 +62,43 @@ public static boolean simpleMatch(@Nullable String pattern, @Nullable String str
}
int nextIndex = pattern.indexOf('*', 1);
if (nextIndex == -1) {
return str.endsWith(pattern.substring(1));
String part = pattern.substring(1);
return (ignoreCase ? StringUtils.endsWithIgnoreCase(str, part) : str.endsWith(part));
}
String part = pattern.substring(1, nextIndex);
if (part.isEmpty()) {
return simpleMatch(pattern.substring(nextIndex), str);
return simpleMatch(pattern.substring(nextIndex), str, ignoreCase);
}
int partIndex = str.indexOf(part);
int partIndex = indexOf(str, part, 0, ignoreCase);
while (partIndex != -1) {
if (simpleMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()))) {
if (simpleMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()), ignoreCase)) {
return true;
}
partIndex = str.indexOf(part, partIndex + 1);
partIndex = indexOf(str, part, partIndex + 1, ignoreCase);
}
return false;
}

return (str.length() >= firstIndex &&
pattern.startsWith(str.substring(0, firstIndex)) &&
simpleMatch(pattern.substring(firstIndex), str.substring(firstIndex)));
checkStartsWith(pattern, str, firstIndex, ignoreCase) &&
simpleMatch(pattern.substring(firstIndex), str.substring(firstIndex), ignoreCase));
}

private static boolean checkStartsWith(String pattern, String str, int index, boolean ignoreCase) {
String part = str.substring(0, index);
return (ignoreCase ? StringUtils.startsWithIgnoreCase(pattern, part) : pattern.startsWith(part));
}

private static int indexOf(String str, String otherStr, int startIndex, boolean ignoreCase) {
if (!ignoreCase) {
return str.indexOf(otherStr, startIndex);
}
for (int i = startIndex; i <= (str.length() - otherStr.length()); i++) {
if (str.regionMatches(true, i, otherStr, 0, otherStr.length())) {
return i;
}
}
return -1;
}

/**
Expand All @@ -91,4 +120,18 @@ public static boolean simpleMatch(@Nullable String[] patterns, String str) {
return false;
}

/**
* Variant of {@link #simpleMatch(String[], String)} that ignores upper/lower case.
*/
public static boolean simpleMatchIgnoreCase(@Nullable String[] patterns, @Nullable String str) {
if (patterns != null) {
for (String pattern : patterns) {
if (simpleMatch(pattern, str, true)) {
return true;
}
}
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,39 @@
*/
class PatternMatchUtilsTests {


@Test
void nullAndEmptyValues() {
assertDoesNotMatch((String) null, null);
assertDoesNotMatch((String) null, "");
assertDoesNotMatch("123", null);

assertDoesNotMatch((String[]) null, null);
assertDoesNotMatch((String[]) null, "");
assertDoesNotMatch(new String[] {}, null);
}

@Test
void trivial() {
assertThat(PatternMatchUtils.simpleMatch((String) null, "")).isFalse();
assertThat(PatternMatchUtils.simpleMatch("1", null)).isFalse();
doTest("*", "123", true);
doTest("123", "123", true);
testMixedCaseMatch("abC", "Abc");
}

@Test
void startsWith() {
doTest("get*", "getMe", true);
doTest("get*", "setMe", false);
testMixedCaseMatch("geT*", "GetMe");
}

@Test
void endsWith() {
doTest("*Test", "getMeTest", true);
doTest("*Test", "setMe", false);
testMixedCaseMatch("*TeSt", "getMeTesT");
}

@Test
Expand All @@ -53,6 +68,10 @@ void between() {
doTest("*stuff*", "stuffTest", true);
doTest("*stuff*", "getstuff", true);
doTest("*stuff*", "stuff", true);
testMixedCaseMatch("*stuff*", "getStuffTest");
testMixedCaseMatch("*stuff*", "StuffTest");
testMixedCaseMatch("*stuff*", "getStuff");
testMixedCaseMatch("*stuff*", "Stuff");
}

@Test
Expand All @@ -61,6 +80,8 @@ void startsEnds() {
doTest("on*Event", "onEvent", true);
doTest("3*3", "3", false);
doTest("3*3", "33", true);
testMixedCaseMatch("on*Event", "OnMyEvenT");
testMixedCaseMatch("on*Event", "OnEvenT");
}

@Test
Expand Down Expand Up @@ -103,4 +124,19 @@ private void doTest(String pattern, String str, boolean shouldMatch) {
assertThat(PatternMatchUtils.simpleMatch(pattern, str)).isEqualTo(shouldMatch);
}

private void testMixedCaseMatch(String pattern, String str) {
assertThat(PatternMatchUtils.simpleMatch(pattern, str)).isFalse();
assertThat(PatternMatchUtils.simpleMatchIgnoreCase(pattern, str)).isTrue();
}

private void assertDoesNotMatch(String pattern, String str) {
assertThat(PatternMatchUtils.simpleMatch(pattern, str)).isFalse();
assertThat(PatternMatchUtils.simpleMatchIgnoreCase(pattern, str)).isFalse();
}

private void assertDoesNotMatch(String[] patterns, String str) {
assertThat(PatternMatchUtils.simpleMatch(patterns, str)).isFalse();
assertThat(PatternMatchUtils.simpleMatchIgnoreCase(patterns, str)).isFalse();
}

}