2

I'm implementing the Servlet URL pattern matching follow the Servlet Specification. My matching method:

public static boolean match(String pattern, String str, boolean isCaseSensitive) {
        char[] patArr = pattern.toCharArray();
        char[] strArr = str.toCharArray();
        int patIdxStart = 0;
        int patIdxEnd = patArr.length - 1;
        int strIdxStart = 0;
        int strIdxEnd = strArr.length - 1;

        boolean containsStar = false;
        for (int i = 0; i < patArr.length; i++) {
            if (patArr[i] != '*') {
                continue;
            }
            containsStar = true;
            break;
        }

        if (!containsStar) {
            if (patIdxEnd != strIdxEnd) {
                return false;
            }
            for (int i = 0; i <= patIdxEnd; i++) {
                char ch = patArr[i];
                if (ch == '?')
                    continue;
                if ((isCaseSensitive) && (ch != strArr[i])) {
                    return false;
                }
                if ((!isCaseSensitive)
                        && (Character.toUpperCase(ch) != Character
                                .toUpperCase(strArr[i]))) {
                    return false;
                }
            }

            return true;
        }

        if (patIdxEnd == 0) {
            return true;
        }

        char ch;
        while (((ch = patArr[patIdxStart]) != '*')
                && (strIdxStart <= strIdxEnd)) {
            if (ch != '?') {
                if ((isCaseSensitive) && (ch != strArr[strIdxStart])) {
                    return false;
                }
                if ((!isCaseSensitive)
                        && (Character.toUpperCase(ch) != Character
                                .toUpperCase(strArr[strIdxStart]))) {
                    return false;
                }
            }
            patIdxStart++;
            strIdxStart++;
        }

        if (strIdxStart > strIdxEnd) {
            for (int i = patIdxStart; i <= patIdxEnd; i++) {
                if (patArr[i] != '*') {
                    return false;
                }
            }
            return true;
        }

        while (((ch = patArr[patIdxEnd]) != '*') && (strIdxStart <= strIdxEnd)) {
            if (ch != '?') {
                if ((isCaseSensitive) && (ch != strArr[strIdxEnd])) {
                    return false;
                }
                if ((!isCaseSensitive)
                        && (Character.toUpperCase(ch) != Character
                                .toUpperCase(strArr[strIdxEnd]))) {
                    return false;
                }
            }
            patIdxEnd--;
            strIdxEnd--;
        }

        if (strIdxStart > strIdxEnd) {
            for (int i = patIdxStart; i <= patIdxEnd; i++) {
                if (patArr[i] != '*') {
                    return false;
                }
            }
            return true;
        }

        while ((patIdxStart != patIdxEnd) && (strIdxStart <= strIdxEnd)) {
            int patIdxTmp = -1;
            for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
                if (patArr[i] != '*') {
                    continue;
                }
                patIdxTmp = i;
                break;
            }

            if (patIdxTmp == patIdxStart + 1) {
                patIdxStart++;
                continue;
            }

            int patLength = patIdxTmp - patIdxStart - 1;
            int strLength = strIdxEnd - strIdxStart + 1;
            int foundIdx = -1;
            for (int i = 0; i <= strLength - patLength; i++) {
                int j = 0;
                while (true)
                    if (j < patLength) {
                        ch = patArr[(patIdxStart + j + 1)];
                        if (ch != '?') {
                            if ((isCaseSensitive)
                                    && (ch != strArr[(strIdxStart + i + j)])) {
                                break;
                            }
                            if ((!isCaseSensitive)
                                    && (Character.toUpperCase(ch) != Character
                                            .toUpperCase(strArr[(strIdxStart
                                                    + i + j)])))
                                break;
                        } else {
                            j++;
                            continue;
                        }

                    } else {
                        foundIdx = strIdxStart + i;
                        break;
                    }
            }

            if (foundIdx == -1) {
                return false;
            }

            patIdxStart = patIdxTmp;
            strIdxStart = foundIdx + patLength;
        }

        for (int i = patIdxStart; i <= patIdxEnd; i++) {
            if (patArr[i] != '*') {
                return false;
            }
        }
        return true;
    }

But when I test with case below:

String pattern = "*.a*";
String path = "/index.abc";
String matches = match(pattern, path, true) ? "matches" : "unmatches";
System.out.println(path + " " + matches + " " + pattern);

The test case runs forever and cannot stop. I have 2 questions:

  1. Is pattern "*.a*" valid with Servlet URL pattern matching spec?
  2. How to fix this error to break the infinite loop?

2 Answers 2

1

Here is my Java Servlet Specification 3.1 (April 2013) Mapping Requests to Servlets implementation.

/**
 * Java Servlet Specification 3.1 (April 2013)
 * Mapping Requests to Servlets (Chapter 12) implementation.
 *
 * This class is thread safe.
 */
public class ServletMappingMatcher {
    private final String[] patterns;
    private final String[] welcomePages;

    public ServletMappingMatcher(String... patterns) {
        this(patterns, new String[0]);
    }

    public ServletMappingMatcher(String[] patterns, String[] welcomePages) {
        this.patterns = patterns;
        this.welcomePages = welcomePages;
    }

    public String getPatternForPath(String path) {
        for (String pattern : patterns) {
            if (matches(pattern, path)) {
                return pattern;
            }
        }

        return null;
    }

    private boolean matches(String pattern, String path) {
        if (isPathMapping(pattern)) {
            return pathMatches(pattern, path);
        } else if (isExtensionPattern(pattern)) {
            return extensionMatches(pattern, path);
        } else if (isApplicationContextRoot(pattern)) {
            return matchesApplicationContextRoot(path);
        } else if (isDefaultServlet(pattern)) {
            return matchesDefaultServlet(path);
        }

        return strictlyMatches(pattern, path);
    }

    private boolean isPathMapping(String pattern) {
        return pattern.startsWith("/") && pattern.endsWith("/*");
    }

    private boolean isExtensionPattern(String pattern) {
        return pattern.startsWith("*.");
    }

    private boolean isApplicationContextRoot(String pattern) {
        return pattern.isEmpty();
    }

    private boolean isDefaultServlet(String pattern) {
        return pattern.equals("/");
    }

    private boolean pathMatches(String pattern, String path) {
        return path.startsWith(pattern.substring(0, pattern.length() - 1)) ||
                path.equals(pattern.substring(0, pattern.length() - 2));
    }

    private boolean extensionMatches(String pattern, String path) {
        return path.endsWith(pattern.substring(1));
    }

    private boolean matchesApplicationContextRoot(String path) {
        return path.equals("/");
    }

    private boolean strictlyMatches(String pattern, String path) {
        return path.equals(pattern);
    }

    private boolean matchesDefaultServlet(String path) {
        if (path.endsWith("/")) {
            return true;
        }

        for (String welcomePage : welcomePages) {
            if (path.endsWith("/" + welcomePage)) {
                return true;
            }
        }

        return false;
    }
}

And JUnit tests:

import org.junit.Assert;
import org.junit.Test;

public class ServletMappingMatcherTest {
    @Test
    public void testsFromSpec() {
        final String servlet1Pattern = "/foo/bar/*";
        final String servlet2Pattern = "/baz/*";
        final String servlet3Pattern = "/catalog";
        final String servlet4Pattern = "*.bop";
        final String defaultServlet = "/";

        final String[] patterns = {servlet1Pattern, servlet2Pattern, servlet3Pattern, servlet4Pattern, defaultServlet};
        final String[] welcomePages = {"index.html"};

        final ServletMappingMatcher matcher = new ServletMappingMatcher(patterns, welcomePages);

        Assert.assertEquals(servlet1Pattern, matcher.getPatternForPath("/foo/bar/index.html"));
        Assert.assertEquals(servlet1Pattern, matcher.getPatternForPath("/foo/bar/index.bop"));
        Assert.assertEquals(servlet2Pattern, matcher.getPatternForPath("/baz"));
        Assert.assertEquals(servlet2Pattern, matcher.getPatternForPath("/baz/index.html"));
        Assert.assertEquals(servlet3Pattern, matcher.getPatternForPath("/catalog"));
        Assert.assertEquals(defaultServlet, matcher.getPatternForPath("/catalog/index.html"));
        Assert.assertEquals(servlet4Pattern, matcher.getPatternForPath("/catalog/rececar.bop"));
        Assert.assertEquals(servlet4Pattern, matcher.getPatternForPath("/index.bop"));
    }
}
Sign up to request clarification or add additional context in comments.

Comments

0

I fixed it, in the while(true) loop add below line to break the loop:

[...]
while(true) {
     if (ch != '?') {
          if(...) {
             //...
             break;
          }

          if(...) {
             //...
             break;
          }
          j++;  // Add this line to avoid infinite loop
     }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.