diff --git a/src/main/java/com/alibaba/druid/support/http/WebStatFilter.java b/src/main/java/com/alibaba/druid/support/http/WebStatFilter.java index 589b972d40..7681bb159d 100644 --- a/src/main/java/com/alibaba/druid/support/http/WebStatFilter.java +++ b/src/main/java/com/alibaba/druid/support/http/WebStatFilter.java @@ -239,4 +239,4 @@ public class WebStatFilter implements Filter { } } } -} +} \ No newline at end of file diff --git a/src/main/java/com/alibaba/druid/util/AntPathMatcher.java b/src/main/java/com/alibaba/druid/util/AntPathMatcher.java new file mode 100644 index 0000000000..2629f91907 --- /dev/null +++ b/src/main/java/com/alibaba/druid/util/AntPathMatcher.java @@ -0,0 +1,394 @@ +package com.alibaba.druid.util; + +/** + *
+ * PathMatcher implementation for Ant-style path patterns. Examples are provided below. + *
+ *+ * Part of this mapping code has been kindly borrowed from Apache Ant. + *
+ * The mapping matches URLs using the following rules:
+ *
+ * Some examples:
+ *
com/t?st.jsp - matches com/test.jsp but also com/tast.jsp or
+ * com/txst.jspcom/*.jsp - matches all .jsp files in the com directorycom/**/test.jsp - matches all test.jsp files underneath the com
+ * pathcom/alibaba/druid/**/*.jsp - matches all .jsp files underneath the
+ * org/apache/shiro pathcom/**/servlet/bla.jsp - matches com/alibaba/druid/servlet/bla.jsp but also
+ * com/alibaba/druid/testing/servlet/bla.jsp and com/servlet/bla.jsp+ * N.B.: This class was borrowed (with much appreciation) from the Shiro + * and the Spring Framework with modifications. We didn't want to reinvent + * the wheel of great work they've done, but also didn't want to force every Shiro user to depend on Spring + *
+ *+ * As per the Apache 2.0 license, the original copyright notice and all author and copyright information have remained + * in tact. + *
+ * + * @since 16.07.2003 + */ +public class AntPathMatcher implements PatternMatcher { + + // TODO - complete JavaDoc + + /** + * Default path separator: "/" + */ + public static final String DEFAULT_PATH_SEPARATOR = "/"; + + private String pathSeparator = DEFAULT_PATH_SEPARATOR; + + /** + * Set the path separator to use for pattern parsing. Default is "/", as in Ant. + */ + public void setPathSeparator(String pathSeparator) { + this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR); + } + + public boolean isPattern(String path) { + return (path.indexOf('*') != -1 || path.indexOf('?') != -1); + } + + public boolean matches(String pattern, String source) { + return match(pattern, source); + } + + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true); + } + + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false); + } + + /** + * Actually match the givenpath against the given pattern.
+ *
+ * @param pattern the pattern to match against
+ * @param path the path String to test
+ * @param fullMatch whether a full pattern match is required (else a pattern match as far as the given base path
+ * goes is sufficient)
+ * @return true if the supplied path matched, false if it didn't
+ */
+ protected boolean doMatch(String pattern, String path, boolean fullMatch) {
+ if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
+ return false;
+ }
+
+ String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
+ String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
+
+ int pattIdxStart = 0;
+ int pattIdxEnd = pattDirs.length - 1;
+ int pathIdxStart = 0;
+ int pathIdxEnd = pathDirs.length - 1;
+
+ // Match all elements up to the first **
+ while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
+ String patDir = pattDirs[pattIdxStart];
+ if ("**".equals(patDir)) {
+ break;
+ }
+ if (!matchStrings(patDir, pathDirs[pathIdxStart])) {
+ return false;
+ }
+ pattIdxStart++;
+ pathIdxStart++;
+ }
+
+ if (pathIdxStart > pathIdxEnd) {
+ // Path is exhausted, only match if rest of pattern is * or **'s
+ if (pattIdxStart > pattIdxEnd) {
+ return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) : !path.endsWith(this.pathSeparator));
+ }
+ if (!fullMatch) {
+ return true;
+ }
+ if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
+ return true;
+ }
+ for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
+ if (!pattDirs[i].equals("**")) {
+ return false;
+ }
+ }
+ return true;
+ } else if (pattIdxStart > pattIdxEnd) {
+ // String not exhausted, but pattern is. Failure.
+ return false;
+ } else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
+ // Path start definitely matches due to "**" part in pattern.
+ return true;
+ }
+
+ // up to last '**'
+ while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
+ String patDir = pattDirs[pattIdxEnd];
+ if (patDir.equals("**")) {
+ break;
+ }
+ if (!matchStrings(patDir, pathDirs[pathIdxEnd])) {
+ return false;
+ }
+ pattIdxEnd--;
+ pathIdxEnd--;
+ }
+ if (pathIdxStart > pathIdxEnd) {
+ // String is exhausted
+ for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
+ if (!pattDirs[i].equals("**")) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
+ int patIdxTmp = -1;
+ for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
+ if (pattDirs[i].equals("**")) {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if (patIdxTmp == pattIdxStart + 1) {
+ // '**/**' situation, so skip one
+ pattIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = (patIdxTmp - pattIdxStart - 1);
+ int strLength = (pathIdxEnd - pathIdxStart + 1);
+ int foundIdx = -1;
+
+ strLoop: for (int i = 0; i <= strLength - patLength; i++) {
+ for (int j = 0; j < patLength; j++) {
+ String subPat = (String) pattDirs[pattIdxStart + j + 1];
+ String subStr = (String) pathDirs[pathIdxStart + i + j];
+ if (!matchStrings(subPat, subStr)) {
+ continue strLoop;
+ }
+ }
+ foundIdx = pathIdxStart + i;
+ break;
+ }
+
+ if (foundIdx == -1) {
+ return false;
+ }
+
+ pattIdxStart = patIdxTmp;
+ pathIdxStart = foundIdx + patLength;
+ }
+
+ for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
+ if (!pattDirs[i].equals("**")) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:null.
+ * @param str string which must be matched against the pattern. Must not be null.
+ * @return true if the string matches against the pattern, or false otherwise.
+ */
+ private boolean matchStrings(String pattern, String str) {
+ char[] patArr = pattern.toCharArray();
+ char[] strArr = str.toCharArray();
+ int patIdxStart = 0;
+ int patIdxEnd = patArr.length - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strArr.length - 1;
+ char ch;
+
+ boolean containsStar = false;
+ for (char aPatArr : patArr) {
+ if (aPatArr == '*') {
+ containsStar = true;
+ break;
+ }
+ }
+
+ if (!containsStar) {
+ // No '*'s, so we make a shortcut
+ if (patIdxEnd != strIdxEnd) {
+ return false; // Pattern and string do not have the same size
+ }
+ for (int i = 0; i <= patIdxEnd; i++) {
+ ch = patArr[i];
+ if (ch != '?') {
+ if (ch != strArr[i]) {
+ return false;// Character mismatch
+ }
+ }
+ }
+ return true; // String matches against pattern
+ }
+
+ if (patIdxEnd == 0) {
+ return true; // Pattern contains only '*', which matches anything
+ }
+
+ // Process characters before first star
+ while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
+ if (ch != '?') {
+ if (ch != strArr[strIdxStart]) {
+ return false;// Character mismatch
+ }
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if (strIdxStart > strIdxEnd) {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Process characters after last star
+ while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
+ if (ch != '?') {
+ if (ch != strArr[strIdxEnd]) {
+ return false;// Character mismatch
+ }
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if (strIdxStart > strIdxEnd) {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // process pattern between stars. padIdxStart and patIdxEnd point
+ // always to a '*'.
+ while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
+ int patIdxTmp = -1;
+ for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
+ if (patArr[i] == '*') {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if (patIdxTmp == patIdxStart + 1) {
+ // Two stars next to each other, skip the first one.
+ patIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = (patIdxTmp - patIdxStart - 1);
+ int strLength = (strIdxEnd - strIdxStart + 1);
+ int foundIdx = -1;
+ strLoop: for (int i = 0; i <= strLength - patLength; i++) {
+ for (int j = 0; j < patLength; j++) {
+ ch = patArr[patIdxStart + j + 1];
+ if (ch != '?') {
+ if (ch != strArr[strIdxStart + i + j]) {
+ continue strLoop;
+ }
+ }
+ }
+
+ foundIdx = strIdxStart + i;
+ break;
+ }
+
+ if (foundIdx == -1) {
+ return false;
+ }
+
+ patIdxStart = patIdxTmp;
+ strIdxStart = foundIdx + patLength;
+ }
+
+ // All characters in the string are used. Check if only '*'s are left
+ // in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Given a pattern and a full path, determine the pattern-mapped part.
+ * + * For example: + *
/docs/cvs/commit.html' and '/docs/cvs/commit.html -> ''/docs/*' and '/docs/cvs/commit -> 'cvs/commit'/docs/cvs/*.html' and '/docs/cvs/commit.html -> 'commit.html'/docs/**' and '/docs/cvs/commit -> 'cvs/commit'/docs/**\/*.html' and '/docs/cvs/commit.html -> 'cvs/commit.html'/*.html' and '/docs/cvs/commit.html -> 'docs/cvs/commit.html'*.html' and '/docs/cvs/commit.html -> '/docs/cvs/commit.html'*' and '/docs/cvs/commit.html -> '/docs/cvs/commit.html'
+ * Assumes that {@link #match} returns true for 'pattern' and 'path', but
+ * does not enforce this.
+ */
+ public String extractPathWithinPattern(String pattern, String path) {
+ String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
+ String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
+
+ StringBuilder buffer = new StringBuilder();
+
+ // Add any path parts that have a wildcarded pattern part.
+ int puts = 0;
+ for (int i = 0; i < patternParts.length; i++) {
+ String patternPart = patternParts[i];
+ if ((patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) && pathParts.length >= i + 1) {
+ if (puts > 0 || (i == 0 && !pattern.startsWith(this.pathSeparator))) {
+ buffer.append(this.pathSeparator);
+ }
+ buffer.append(pathParts[i]);
+ puts++;
+ }
+ }
+
+ // Append any trailing path parts.
+ for (int i = patternParts.length; i < pathParts.length; i++) {
+ if (puts > 0 || i > 0) {
+ buffer.append(this.pathSeparator);
+ }
+ buffer.append(pathParts[i]);
+ }
+
+ return buffer.toString();
+ }
+
+}
diff --git a/src/main/java/com/alibaba/druid/util/PatternMatcher.java b/src/main/java/com/alibaba/druid/util/PatternMatcher.java
new file mode 100644
index 0000000000..9b49be34ff
--- /dev/null
+++ b/src/main/java/com/alibaba/druid/util/PatternMatcher.java
@@ -0,0 +1,24 @@
+package com.alibaba.druid.util;
+
+/**
+ * Interface for components that can match source strings against a specified pattern string.
+ *
true if the given source matches the specified pattern,
+ * false otherwise.
+ *
+ * @param pattern the pattern to match against
+ * @param source the source to match
+ * @return true if the given source matches the specified pattern,
+ * false otherwise.
+ */
+ boolean matches(String pattern, String source);
+}
diff --git a/src/main/java/com/alibaba/druid/util/ServletPathMatcher.java b/src/main/java/com/alibaba/druid/util/ServletPathMatcher.java
new file mode 100644
index 0000000000..6395e38aec
--- /dev/null
+++ b/src/main/java/com/alibaba/druid/util/ServletPathMatcher.java
@@ -0,0 +1,41 @@
+package com.alibaba.druid.util;
+
+public class ServletPathMatcher implements PatternMatcher {
+
+ /**
+ * + * three type: endsWithMatch(eg. /xxx*=/xxx/xyz), startsWithMatch(eg. *.xxx=abc.xxx), equals(eg. /xxx=/xxx)。 + *
+ * Notice: *xxx* will match *xxxyyyy. endsWithMatch first. + */ + @Override + public boolean matches(String pattern, String source) { + if (pattern == null || source == null) { + return false; + } + pattern = pattern.trim(); + source = source.trim(); + if (pattern.endsWith("*")) { + // pattern: /druid* source:/druid/index.html + int length = pattern.length() - 1; + if (source.length() >= length) { + if (pattern.substring(0, length).equals(source.substring(0, length))) { + return true; + } + } + } else if (pattern.startsWith("*")) { + // pattern: *.html source:/xx/xx.html + int length = pattern.length() - 1; + if (source.length() >= length && source.endsWith(pattern.substring(1))) { + return true; + } + } else { + // pattern: /druid/index.html source:/druid/index.html + if (pattern.equals(source)) { + return true; + } + } + return false; + } + +} diff --git a/src/main/java/com/alibaba/druid/util/StringUtils.java b/src/main/java/com/alibaba/druid/util/StringUtils.java index 01ce45e10c..1b9f0a8108 100644 --- a/src/main/java/com/alibaba/druid/util/StringUtils.java +++ b/src/main/java/com/alibaba/druid/util/StringUtils.java @@ -1,7 +1,11 @@ package com.alibaba.druid.util; +import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.StringTokenizer; import com.alibaba.druid.support.logging.Log; import com.alibaba.druid.support.logging.LogFactory; @@ -97,6 +101,85 @@ public class StringUtils { return parameters; } + /** + * Tokenize the given String into a String array via a StringTokenizer. Trims tokens and omits empty tokens. + *
+ * The given delimiters string is supposed to consist of any number of delimiter characters. Each of those
+ * characters can be used to separate tokens. A delimiter is always a single character; for multi-character
+ * delimiters, consider using delimitedListToStringArray
+ *
+ * Copied from the Spring Framework while retaining all license, copyright and author information. + * + * @param str the String to tokenize + * @param delimiters the delimiter characters, assembled as String (each of those characters is individually + * considered as delimiter). + * @return an array of the tokens + * @see java.util.StringTokenizer + * @see java.lang.String#trim() + */ + public static String[] tokenizeToStringArray(String str, String delimiters) { + return tokenizeToStringArray(str, delimiters, true, true); + } + + /** + * Tokenize the given String into a String array via a StringTokenizer. + *
+ * The given delimiters string is supposed to consist of any number of delimiter characters. Each of those
+ * characters can be used to separate tokens. A delimiter is always a single character; for multi-character
+ * delimiters, consider using delimitedListToStringArray
+ *
+ * Copied from the Spring Framework while retaining all license, copyright and author information.
+ *
+ * @param str the String to tokenize
+ * @param delimiters the delimiter characters, assembled as String (each of those characters is individually
+ * considered as delimiter)
+ * @param trimTokens trim the tokens via String's trim
+ * @param ignoreEmptyTokens omit empty tokens from the result array (only applies to tokens that are empty after
+ * trimming; StringTokenizer will not consider subsequent delimiters as token in the first place).
+ * @return an array of the tokens (null if the input String was null)
+ * @see java.util.StringTokenizer
+ * @see java.lang.String#trim()
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static String[] tokenizeToStringArray(String str, String delimiters, boolean trimTokens,
+ boolean ignoreEmptyTokens) {
+
+ if (str == null) {
+ return null;
+ }
+ StringTokenizer st = new StringTokenizer(str, delimiters);
+ List tokens = new ArrayList();
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ if (trimTokens) {
+ token = token.trim();
+ }
+ if (!ignoreEmptyTokens || token.length() > 0) {
+ tokens.add(token);
+ }
+ }
+ return toStringArray(tokens);
+ }
+
+ /**
+ * Copy the given Collection into a String array. The Collection must contain String elements only.
+ *
+ * Copied from the Spring Framework while retaining all license, copyright and author information.
+ *
+ * @param collection the Collection to copy
+ * @return the String array (null if the passed-in Collection was null)
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static String[] toStringArray(Collection collection) {
+ if (collection == null) {
+ return null;
+ }
+ return (String[]) collection.toArray(new String[collection.size()]);
+ }
+
public static void main(String args[]) {
System.out.println(JSON.toJSONString(getParameters("test?t=1&f=").get("t")));
}
diff --git a/src/test/java/com/alibaba/druid/util/ServletPathMathcherTest.java b/src/test/java/com/alibaba/druid/util/ServletPathMathcherTest.java
new file mode 100644
index 0000000000..c1f0e68544
--- /dev/null
+++ b/src/test/java/com/alibaba/druid/util/ServletPathMathcherTest.java
@@ -0,0 +1,99 @@
+package com.alibaba.druid.util;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+
+
+public class ServletPathMathcherTest {
+
+ /**
+ * PatternMatcher used in determining which paths to react to for a given request.
+ */
+ protected PatternMatcher pathMatcher = new ServletPathMatcher();
+
+ @Test
+ public void testStartsWithPattern() {
+ String[] bogusPatterns = { "/druid*", "/druid*/what*", "*/druid*" };
+ String[] bogusSources = { "/druid", "/druid/index.html", "/druid*/what/xyu" };
+ boolean result = false;
+ for (int i = 0; i < bogusSources.length; i++) {
+ for (int j = 0; j < bogusPatterns.length; j++) {
+ String bogusSource = bogusSources[i];
+ String bogusPattern = bogusPatterns[j];
+ if (pathMatcher.matches(bogusPattern, bogusSource)) {
+ result = true;
+ }
+ if (result == true) {
+ break;
+ }
+ }
+ assertThat(true, equalTo(result));
+ result = false;
+ }
+ }
+
+ @Test
+ public void testEndsWithPattern() {
+ String[] bogusPatterns = { "*.html", "*.ico", "*.css" };
+ String[] bogusSources = { "/index.html", "/favicon.ico", "/druid.css" };
+ boolean result = false;
+ for (int i = 0; i < bogusSources.length; i++) {
+ for (int j = 0; j < bogusPatterns.length; j++) {
+ String bogusSource = bogusSources[i];
+ String bogusPattern = bogusPatterns[j];
+ if (pathMatcher.matches(bogusPattern, bogusSource)) {
+ result = true;
+ }
+ if (result == true) {
+ break;
+ }
+ }
+ assertThat(true, equalTo(result));
+ result = false;
+ }
+ }
+
+ @Test
+ public void testEqualsPattern() {
+ String[] bogusPatterns = { "/index.html", "/favicon.ico", "/xyz" };
+ String[] bogusSources = { "/index.html", "/favicon.ico", "/xyz" };
+ boolean result = false;
+ for (int i = 0; i < bogusSources.length; i++) {
+ for (int j = 0; j < bogusPatterns.length; j++) {
+ String bogusSource = bogusSources[i];
+ String bogusPattern = bogusPatterns[j];
+ if (pathMatcher.matches(bogusPattern, bogusSource)) {
+ result = true;
+ }
+ if (result == true) {
+ break;
+ }
+ }
+ assertThat(true, equalTo(result));
+ result = false;
+ }
+ }
+
+ @Test
+ public void testPatternPriority() {
+ String[] bogusPatterns = { "*html*", "/favicon.ico*", "*html" };
+ String[] bogusSources = { "*html/ok?", "/favicon.ico/ok?", "/index.html" };
+ boolean result = false;
+ for (int i = 0; i < bogusSources.length; i++) {
+ for (int j = 0; j < bogusPatterns.length; j++) {
+ String bogusSource = bogusSources[i];
+ String bogusPattern = bogusPatterns[j];
+ if (pathMatcher.matches(bogusPattern, bogusSource)) {
+ result = true;
+ }
+ if (result == true) {
+ break;
+ }
+ }
+ assertThat(true, equalTo(result));
+ result = false;
+ }
+ }
+}