Issue #4877 - refactor PathSpec into an interface whose implementations use final fields

Signed-off-by: Ludovic Orban <lorban@bitronix.be>
This commit is contained in:
Ludovic Orban 2020-05-18 11:58:18 +02:00
parent 3845790c5d
commit 85a5452f49
9 changed files with 352 additions and 337 deletions

View File

@ -100,7 +100,7 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
List<MappedResource<E>> ret = new ArrayList<>();
for (MappedResource<E> mr : _mappings)
{
switch (mr.getPathSpec().group)
switch (mr.getPathSpec().getGroup())
{
case ROOT:
if (isRootPath)
@ -225,7 +225,7 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
public boolean put(PathSpec pathSpec, E resource)
{
MappedResource<E> entry = new MappedResource<>(pathSpec, resource);
switch (pathSpec.group)
switch (pathSpec.getGroup())
{
case EXACT:
String exact = pathSpec.getPrefix();
@ -260,7 +260,7 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
@SuppressWarnings("incomplete-switch")
public boolean remove(PathSpec pathSpec)
{
switch (pathSpec.group)
switch (pathSpec.getGroup())
{
case EXACT:
_exactMap.remove(pathSpec.getPrefix());

View File

@ -19,66 +19,23 @@
package org.eclipse.jetty.http.pathmap;
/**
* The base PathSpec, what all other path specs are based on
* A path specification is a URI path template that can be matched against.
*/
public abstract class PathSpec implements Comparable<PathSpec>
public interface PathSpec extends Comparable<PathSpec>
{
protected String pathSpec;
protected PathSpecGroup group;
protected int pathDepth;
protected int specLength;
protected String prefix;
protected String suffix;
/**
* The length of the spec.
*
* @return the length of the spec.
*/
int getSpecLength();
@Override
public int compareTo(PathSpec other)
{
// Grouping (increasing)
int diff = this.group.ordinal() - other.group.ordinal();
if (diff != 0)
{
return diff;
}
// Spec Length (decreasing)
diff = other.specLength - this.specLength;
if (diff != 0)
{
return diff;
}
// Path Spec Name (alphabetical)
return this.pathSpec.compareTo(other.pathSpec);
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
PathSpec other = (PathSpec)obj;
if (pathSpec == null)
{
return other.pathSpec == null;
}
else
return pathSpec.equals(other.pathSpec);
}
public PathSpecGroup getGroup()
{
return group;
}
/**
* The spec group.
*
* @return the spec group.
*/
PathSpecGroup getGroup();
/**
* Get the number of path elements that this path spec declares.
@ -87,10 +44,7 @@ public abstract class PathSpec implements Comparable<PathSpec>
*
* @return the depth of the path segments that this spec declares
*/
public int getPathDepth()
{
return pathDepth;
}
int getPathDepth();
/**
* Return the portion of the path that is after the path spec.
@ -98,7 +52,7 @@ public abstract class PathSpec implements Comparable<PathSpec>
* @param path the path to match against
* @return the path info portion of the string
*/
public abstract String getPathInfo(String path);
String getPathInfo(String path);
/**
* Return the portion of the path that matches a path spec.
@ -106,55 +60,28 @@ public abstract class PathSpec implements Comparable<PathSpec>
* @param path the path to match against
* @return the match, or null if no match at all
*/
public abstract String getPathMatch(String path);
String getPathMatch(String path);
/**
* The as-provided path spec.
*
* @return the as-provided path spec
*/
public String getDeclaration()
{
return pathSpec;
}
String getDeclaration();
/**
* A simple prefix match for the pathspec or null
*
* @return A simple prefix match for the pathspec or null
*/
public String getPrefix()
{
return prefix;
}
String getPrefix();
/**
* A simple suffix match for the pathspec or null
*
* @return A simple suffix match for the pathspec or null
*/
public String getSuffix()
{
return suffix;
}
/**
* Get the relative path.
*
* @param base the base the path is relative to
* @param path the additional path
* @return the base plus path with pathSpec portion removed
*/
public abstract String getRelativePath(String base, String path);
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = (prime * result) + ((pathSpec == null) ? 0 : pathSpec.hashCode());
return result;
}
String getSuffix();
/**
* Test to see if the provided path matches this path spec
@ -162,17 +89,21 @@ public abstract class PathSpec implements Comparable<PathSpec>
* @param path the path to test
* @return true if the path matches this path spec, false otherwise
*/
public abstract boolean matches(String path);
boolean matches(String path);
@Override
public String toString()
default int compareTo(PathSpec other)
{
StringBuilder str = new StringBuilder();
str.append(this.getClass().getSimpleName()).append("[\"");
str.append(pathSpec);
str.append("\",pathDepth=").append(pathDepth);
str.append(",group=").append(group);
str.append("]");
return str.toString();
// Grouping (increasing)
int diff = getGroup().ordinal() - other.getGroup().ordinal();
if (diff != 0)
return diff;
// Spec Length (decreasing)
diff = other.getSpecLength() - getSpecLength();
if (diff != 0)
return diff;
// Path Spec Name (alphabetical)
return getDeclaration().compareTo(other.getDeclaration());
}
}

View File

@ -26,8 +26,8 @@ package org.eclipse.jetty.http.pathmap;
* Search Order:
* <ol>
* <li>{@link PathSpecGroup#ordinal()} [increasing]</li>
* <li>{@link PathSpec#specLength} [decreasing]</li>
* <li>{@link PathSpec#pathSpec} [natural sort order]</li>
* <li>{@link PathSpec#getSpecLength()} [decreasing]</li>
* <li>{@link PathSpec#getDeclaration()} [natural sort order]</li>
* </ol>
*/
public enum PathSpecGroup

View File

@ -21,27 +21,30 @@ package org.eclipse.jetty.http.pathmap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexPathSpec extends PathSpec
public class RegexPathSpec implements PathSpec
{
protected Pattern pattern;
protected RegexPathSpec()
{
super();
}
private final String _declaration;
private final PathSpecGroup _group;
private final int _pathDepth;
private final int _specLength;
private final Pattern _pattern;
public RegexPathSpec(String regex)
{
super.pathSpec = regex;
String declaration;
if (regex.startsWith("regex|"))
super.pathSpec = regex.substring("regex|".length());
this.pathDepth = 0;
this.specLength = pathSpec.length();
declaration = regex.substring("regex|".length());
else
declaration = regex;
int specLength = declaration.length();
// build up a simple signature we can use to identify the grouping
boolean inGrouping = false;
StringBuilder signature = new StringBuilder();
for (char c : pathSpec.toCharArray())
int pathDepth = 0;
for (int i = 0; i < declaration.length(); i++)
{
char c = declaration.charAt(i);
switch (c)
{
case '[':
@ -56,54 +59,64 @@ public class RegexPathSpec extends PathSpec
break;
case '/':
if (!inGrouping)
{
this.pathDepth++;
}
pathDepth++;
break;
default:
if (!inGrouping)
{
if (Character.isLetterOrDigit(c))
{
if (!inGrouping && Character.isLetterOrDigit(c))
signature.append('l'); // literal (exact)
}
}
break;
}
}
this.pattern = Pattern.compile(pathSpec);
Pattern pattern = Pattern.compile(declaration);
// Figure out the grouping based on the signature
String sig = signature.toString();
PathSpecGroup group;
if (Pattern.matches("^l*$", sig))
{
this.group = PathSpecGroup.EXACT;
}
group = PathSpecGroup.EXACT;
else if (Pattern.matches("^l*g+", sig))
{
this.group = PathSpecGroup.PREFIX_GLOB;
}
group = PathSpecGroup.PREFIX_GLOB;
else if (Pattern.matches("^g+l+$", sig))
{
this.group = PathSpecGroup.SUFFIX_GLOB;
}
group = PathSpecGroup.SUFFIX_GLOB;
else
{
this.group = PathSpecGroup.MIDDLE_GLOB;
}
group = PathSpecGroup.MIDDLE_GLOB;
_declaration = declaration;
_group = group;
_pathDepth = pathDepth;
_specLength = specLength;
_pattern = pattern;
}
public Matcher getMatcher(String path)
protected Matcher getMatcher(String path)
{
return this.pattern.matcher(path);
return _pattern.matcher(path);
}
@Override
public int getSpecLength()
{
return _specLength;
}
@Override
public PathSpecGroup getGroup()
{
return _group;
}
@Override
public int getPathDepth()
{
return _pathDepth;
}
@Override
public String getPathInfo(String path)
{
// Path Info only valid for PREFIX_GLOB types
if (group == PathSpecGroup.PREFIX_GLOB)
if (_group == PathSpecGroup.PREFIX_GLOB)
{
Matcher matcher = getMatcher(path);
if (matcher.matches())
@ -112,16 +125,12 @@ public class RegexPathSpec extends PathSpec
{
String pathInfo = matcher.group(1);
if ("".equals(pathInfo))
{
return "/";
}
else
{
return pathInfo;
}
}
}
}
return null;
}
@ -137,9 +146,7 @@ public class RegexPathSpec extends PathSpec
if (idx > 0)
{
if (path.charAt(idx - 1) == '/')
{
idx--;
}
return path.substring(0, idx);
}
}
@ -148,18 +155,29 @@ public class RegexPathSpec extends PathSpec
return null;
}
public Pattern getPattern()
@Override
public String getDeclaration()
{
return this.pattern;
return _declaration;
}
@Override
public String getRelativePath(String base, String path)
public String getPrefix()
{
// TODO Auto-generated method stub
return null;
}
@Override
public String getSuffix()
{
return null;
}
public Pattern getPattern()
{
return _pattern;
}
@Override
public boolean matches(final String path)
{

View File

@ -19,15 +19,20 @@
package org.eclipse.jetty.http.pathmap;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class ServletPathSpec extends PathSpec
public class ServletPathSpec implements PathSpec
{
private static final Logger LOG = Log.getLogger(ServletPathSpec.class);
private final String _declaration;
private final PathSpecGroup _group;
private final int _pathDepth;
private final int _specLength;
private final String _prefix;
private final String _suffix;
/**
* If a servlet or filter path mapping isn't a suffix mapping, ensure
* it starts with '/'
@ -53,42 +58,51 @@ public class ServletPathSpec extends PathSpec
// The Root Path Spec
if (servletPathSpec.isEmpty())
{
super.pathSpec = "";
super.pathDepth = -1; // force this to be at the end of the sort order
this.specLength = 1;
this.group = PathSpecGroup.ROOT;
_declaration = "";
_group = PathSpecGroup.ROOT;
_pathDepth = -1; // Set pathDepth to -1 to force this to be at the end of the sort order.
_specLength = 1;
_prefix = null;
_suffix = null;
return;
}
// The Default Path Spec
if ("/".equals(servletPathSpec))
{
super.pathSpec = "/";
super.pathDepth = -1; // force this to be at the end of the sort order
this.specLength = 1;
this.group = PathSpecGroup.DEFAULT;
_declaration = "/";
_group = PathSpecGroup.DEFAULT;
_pathDepth = -1; // Set pathDepth to -1 to force this to be at the end of the sort order.
_specLength = 1;
_prefix = null;
_suffix = null;
return;
}
this.specLength = servletPathSpec.length();
super.pathDepth = 0;
char lastChar = servletPathSpec.charAt(specLength - 1);
int specLength = servletPathSpec.length();
PathSpecGroup group;
String prefix;
String suffix;
// prefix based
if (servletPathSpec.charAt(0) == '/' && servletPathSpec.endsWith("/*"))
{
this.group = PathSpecGroup.PREFIX_GLOB;
this.prefix = servletPathSpec.substring(0, specLength - 2);
group = PathSpecGroup.PREFIX_GLOB;
prefix = servletPathSpec.substring(0, specLength - 2);
suffix = null;
}
// suffix based
else if (servletPathSpec.charAt(0) == '*' && servletPathSpec.length() > 1)
{
this.group = PathSpecGroup.SUFFIX_GLOB;
this.suffix = servletPathSpec.substring(2, specLength);
group = PathSpecGroup.SUFFIX_GLOB;
prefix = null;
suffix = servletPathSpec.substring(2, specLength);
}
else
{
this.group = PathSpecGroup.EXACT;
this.prefix = servletPathSpec;
group = PathSpecGroup.EXACT;
prefix = servletPathSpec;
suffix = null;
if (servletPathSpec.endsWith("*"))
{
LOG.warn("Suspicious URL pattern: '{}'; see sections 12.1 and 12.2 of the Servlet specification",
@ -96,25 +110,27 @@ public class ServletPathSpec extends PathSpec
}
}
int pathDepth = 0;
for (int i = 0; i < specLength; i++)
{
int cp = servletPathSpec.codePointAt(i);
if (cp < 128)
{
char c = (char)cp;
switch (c)
{
case '/':
super.pathDepth++;
break;
}
if (c == '/')
pathDepth++;
}
}
super.pathSpec = servletPathSpec;
_declaration = servletPathSpec;
_group = group;
_pathDepth = pathDepth;
_specLength = specLength;
_prefix = prefix;
_suffix = suffix;
}
private void assertValidServletPathSpec(String servletPathSpec)
private static void assertValidServletPathSpec(String servletPathSpec)
{
if ((servletPathSpec == null) || servletPathSpec.equals(""))
{
@ -147,35 +163,49 @@ public class ServletPathSpec extends PathSpec
int idx = servletPathSpec.indexOf('/');
// cannot have path separator
if (idx >= 0)
{
throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have path separators: bad spec \"" + servletPathSpec + "\"");
}
idx = servletPathSpec.indexOf('*', 2);
// only allowed to have 1 glob '*', at the start of the path spec
if (idx >= 1)
{
throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have multiple glob '*': bad spec \"" + servletPathSpec + "\"");
}
}
else
{
throw new IllegalArgumentException("Servlet Spec 12.2 violation: path spec must start with \"/\" or \"*.\": bad spec \"" + servletPathSpec + "\"");
}
}
@Override
public int getSpecLength()
{
return _specLength;
}
@Override
public PathSpecGroup getGroup()
{
return _group;
}
@Override
public int getPathDepth()
{
return _pathDepth;
}
@Override
public String getPathInfo(String path)
{
switch (group)
switch (_group)
{
case ROOT:
return path;
case PREFIX_GLOB:
if (path.length() == (specLength - 2))
if (path.length() == (_specLength - 2))
return null;
return path.substring(specLength - 2);
return path.substring(_specLength - 2);
default:
return null;
@ -185,23 +215,23 @@ public class ServletPathSpec extends PathSpec
@Override
public String getPathMatch(String path)
{
switch (group)
switch (_group)
{
case ROOT:
return "";
case EXACT:
if (pathSpec.equals(path))
if (_declaration.equals(path))
return path;
return null;
case PREFIX_GLOB:
if (isWildcardMatch(path))
return path.substring(0, specLength - 2);
return path.substring(0, _specLength - 2);
return null;
case SUFFIX_GLOB:
if (path.regionMatches(path.length() - (specLength - 1), pathSpec, 1, specLength - 1))
if (path.regionMatches(path.length() - (_specLength - 1), _declaration, 1, _specLength - 1))
return path;
return null;
@ -214,62 +244,43 @@ public class ServletPathSpec extends PathSpec
}
@Override
public String getRelativePath(String base, String path)
public String getDeclaration()
{
String info = getPathInfo(path);
if (info == null)
{
info = path;
return _declaration;
}
if (info.startsWith("./"))
@Override
public String getPrefix()
{
info = info.substring(2);
return _prefix;
}
if (base.endsWith(URIUtil.SLASH))
@Override
public String getSuffix()
{
if (info.startsWith(URIUtil.SLASH))
{
path = base + info.substring(1);
}
else
{
path = base + info;
}
}
else if (info.startsWith(URIUtil.SLASH))
{
path = base + info;
}
else
{
path = base + URIUtil.SLASH + info;
}
return path;
return _suffix;
}
private boolean isWildcardMatch(String path)
{
// For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
int cpl = specLength - 2;
if ((group == PathSpecGroup.PREFIX_GLOB) && (path.regionMatches(0, pathSpec, 0, cpl)))
{
int cpl = _specLength - 2;
if ((_group == PathSpecGroup.PREFIX_GLOB) && (path.regionMatches(0, _declaration, 0, cpl)))
return (path.length() == cpl) || ('/' == path.charAt(cpl));
}
return false;
}
@Override
public boolean matches(String path)
{
switch (group)
switch (_group)
{
case EXACT:
return pathSpec.equals(path);
return _declaration.equals(path);
case PREFIX_GLOB:
return isWildcardMatch(path);
case SUFFIX_GLOB:
return path.regionMatches((path.length() - specLength) + 1, pathSpec, 1, specLength - 1);
return path.regionMatches((path.length() - _specLength) + 1, _declaration, 1, _specLength - 1);
case ROOT:
// Only "/" matches
return ("/".equals(path));

View File

@ -38,7 +38,7 @@ import org.eclipse.jetty.util.log.Logger;
*
* @see <a href="https://tools.ietf.org/html/rfc6570">URI Templates (Level 1)</a>
*/
public class UriTemplatePathSpec extends RegexPathSpec
public class UriTemplatePathSpec implements PathSpec
{
private static final Logger LOG = Log.getLogger(UriTemplatePathSpec.class);
@ -62,49 +62,41 @@ public class UriTemplatePathSpec extends RegexPathSpec
FORBIDDEN_SEGMENTS.add("//");
}
private String[] variables;
private final String _declaration;
private final PathSpecGroup _group;
private final int _pathDepth;
private final int _specLength;
private final Pattern _pattern;
private final String[] _variables;
public UriTemplatePathSpec(String rawSpec)
{
super();
Objects.requireNonNull(rawSpec, "Path Param Spec cannot be null");
if ("".equals(rawSpec) || "/".equals(rawSpec))
{
super.pathSpec = "/";
super.pattern = Pattern.compile("^/$");
super.pathDepth = 1;
this.specLength = 1;
this.variables = new String[0];
this.group = PathSpecGroup.EXACT;
_declaration = "/";
_group = PathSpecGroup.EXACT;
_pathDepth = 1;
_specLength = 1;
_pattern = Pattern.compile("^/$");
_variables = new String[0];
return;
}
if (rawSpec.charAt(0) != '/')
{
// path specs must start with '/'
StringBuilder err = new StringBuilder();
err.append("Syntax Error: path spec \"");
err.append(rawSpec);
err.append("\" must start with '/'");
throw new IllegalArgumentException(err.toString());
throw new IllegalArgumentException("Syntax Error: path spec \"" + rawSpec + "\" must start with '/'");
}
for (String forbidden : FORBIDDEN_SEGMENTS)
{
if (rawSpec.contains(forbidden))
{
StringBuilder err = new StringBuilder();
err.append("Syntax Error: segment ");
err.append(forbidden);
err.append(" is forbidden in path spec: ");
err.append(rawSpec);
throw new IllegalArgumentException(err.toString());
}
throw new IllegalArgumentException("Syntax Error: segment " + forbidden + " is forbidden in path spec: " + rawSpec);
}
this.pathSpec = rawSpec;
String declaration = rawSpec;
StringBuilder regex = new StringBuilder();
regex.append('^');
@ -112,7 +104,7 @@ public class UriTemplatePathSpec extends RegexPathSpec
// split up into path segments (ignoring the first slash that will always be empty)
String[] segments = rawSpec.substring(1).split("/");
char[] segmentSignature = new char[segments.length];
this.pathDepth = segments.length;
int pathDepth = segments.length;
for (int i = 0; i < segments.length; i++)
{
String segment = segments[i];
@ -125,15 +117,10 @@ public class UriTemplatePathSpec extends RegexPathSpec
if (varNames.contains(variable))
{
// duplicate variable names
StringBuilder err = new StringBuilder();
err.append("Syntax Error: variable ");
err.append(variable);
err.append(" is duplicated in path spec: ");
err.append(rawSpec);
throw new IllegalArgumentException(err.toString());
throw new IllegalArgumentException("Syntax Error: variable " + variable + " is duplicated in path spec: " + rawSpec);
}
assertIsValidVariableLiteral(variable);
assertIsValidVariableLiteral(variable, declaration);
segmentSignature[i] = 'v'; // variable
// valid variable name
@ -144,32 +131,17 @@ public class UriTemplatePathSpec extends RegexPathSpec
else if (mat.find(0))
{
// variable exists as partial segment
StringBuilder err = new StringBuilder();
err.append("Syntax Error: variable ");
err.append(mat.group());
err.append(" must exist as entire path segment: ");
err.append(rawSpec);
throw new IllegalArgumentException(err.toString());
throw new IllegalArgumentException("Syntax Error: variable " + mat.group() + " must exist as entire path segment: " + rawSpec);
}
else if ((segment.indexOf('{') >= 0) || (segment.indexOf('}') >= 0))
{
// variable is split with a path separator
StringBuilder err = new StringBuilder();
err.append("Syntax Error: invalid path segment /");
err.append(segment);
err.append("/ variable declaration incomplete: ");
err.append(rawSpec);
throw new IllegalArgumentException(err.toString());
throw new IllegalArgumentException("Syntax Error: invalid path segment /" + segment + "/ variable declaration incomplete: " + rawSpec);
}
else if (segment.indexOf('*') >= 0)
{
// glob segment
StringBuilder err = new StringBuilder();
err.append("Syntax Error: path segment /");
err.append(segment);
err.append("/ contains a wildcard symbol (not supported by this uri-template implementation): ");
err.append(rawSpec);
throw new IllegalArgumentException(err.toString());
throw new IllegalArgumentException("Syntax Error: path segment /" + segment + "/ contains a wildcard symbol (not supported by this uri-template implementation): " + rawSpec);
}
else
{
@ -178,12 +150,11 @@ public class UriTemplatePathSpec extends RegexPathSpec
// build regex
regex.append('/');
// escape regex special characters
for (char c : segment.toCharArray())
for (int j = 0; j < segment.length(); j++)
{
char c = segment.charAt(j);
if ((c == '.') || (c == '[') || (c == ']') || (c == '\\'))
{
regex.append('\\');
}
regex.append(c);
}
}
@ -191,42 +162,40 @@ public class UriTemplatePathSpec extends RegexPathSpec
// Handle trailing slash (which is not picked up during split)
if (rawSpec.charAt(rawSpec.length() - 1) == '/')
{
regex.append('/');
}
regex.append('$');
this.pattern = Pattern.compile(regex.toString());
Pattern pattern = Pattern.compile(regex.toString());
int varcount = varNames.size();
this.variables = varNames.toArray(new String[varcount]);
String[] variables = varNames.toArray(new String[varcount]);
// Convert signature to group
String sig = String.valueOf(segmentSignature);
PathSpecGroup group;
if (Pattern.matches("^e*$", sig))
{
this.group = PathSpecGroup.EXACT;
}
group = PathSpecGroup.EXACT;
else if (Pattern.matches("^e*v+", sig))
{
this.group = PathSpecGroup.PREFIX_GLOB;
}
group = PathSpecGroup.PREFIX_GLOB;
else if (Pattern.matches("^v+e+", sig))
{
this.group = PathSpecGroup.SUFFIX_GLOB;
}
group = PathSpecGroup.SUFFIX_GLOB;
else
{
this.group = PathSpecGroup.MIDDLE_GLOB;
}
group = PathSpecGroup.MIDDLE_GLOB;
_declaration = declaration;
_group = group;
_pathDepth = pathDepth;
_specLength = declaration.length();
_pattern = pattern;
_variables = variables;
}
/**
* Validate variable literal name, per RFC6570, Section 2.1 Literals
*/
private void assertIsValidVariableLiteral(String variable)
private static void assertIsValidVariableLiteral(String variable, String declaration)
{
int len = variable.length();
@ -240,16 +209,12 @@ public class UriTemplatePathSpec extends RegexPathSpec
i += Character.charCount(codepoint);
// basic letters, digits, or symbols
if (isValidBasicLiteralCodepoint(codepoint))
{
if (isValidBasicLiteralCodepoint(codepoint, declaration))
continue;
}
// The ucschar and iprivate pieces
if (Character.isSupplementaryCodePoint(codepoint))
{
continue;
}
// pct-encoded
if (codepoint == '%')
@ -264,11 +229,9 @@ public class UriTemplatePathSpec extends RegexPathSpec
codepoint |= TypeUtil.convertHexDigit(variable.codePointAt(i++));
// validate basic literal
if (isValidBasicLiteralCodepoint(codepoint))
{
if (isValidBasicLiteralCodepoint(codepoint, declaration))
continue;
}
}
valid = false;
}
@ -276,35 +239,26 @@ public class UriTemplatePathSpec extends RegexPathSpec
if (!valid)
{
// invalid variable name
StringBuilder err = new StringBuilder();
err.append("Syntax Error: variable {");
err.append(variable);
err.append("} an invalid variable name: ");
err.append(pathSpec);
throw new IllegalArgumentException(err.toString());
throw new IllegalArgumentException("Syntax Error: variable {" + variable + "} an invalid variable name: " + declaration);
}
}
private boolean isValidBasicLiteralCodepoint(int codepoint)
private static boolean isValidBasicLiteralCodepoint(int codepoint, String declaration)
{
// basic letters or digits
if ((codepoint >= 'a' && codepoint <= 'z') ||
(codepoint >= 'A' && codepoint <= 'Z') ||
(codepoint >= '0' && codepoint <= '9'))
{
return true;
}
// basic allowed symbols
if (VARIABLE_SYMBOLS.indexOf(codepoint) >= 0)
{
return true; // valid simple value
}
// basic reserved symbols
if (VARIABLE_RESERVED.indexOf(codepoint) >= 0)
{
LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"", (char)codepoint, pathSpec);
LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"", (char)codepoint, declaration);
return false; // valid simple value
}
@ -316,28 +270,129 @@ public class UriTemplatePathSpec extends RegexPathSpec
Matcher matcher = getMatcher(path);
if (matcher.matches())
{
if (group == PathSpecGroup.EXACT)
{
if (_group == PathSpecGroup.EXACT)
return Collections.emptyMap();
}
Map<String, String> ret = new HashMap<>();
int groupCount = matcher.groupCount();
for (int i = 1; i <= groupCount; i++)
{
ret.put(this.variables[i - 1], matcher.group(i));
}
ret.put(_variables[i - 1], matcher.group(i));
return ret;
}
return null;
}
protected Matcher getMatcher(String path)
{
return _pattern.matcher(path);
}
@Override
public int getSpecLength()
{
return _specLength;
}
@Override
public PathSpecGroup getGroup()
{
return _group;
}
@Override
public int getPathDepth()
{
return _pathDepth;
}
@Override
public String getPathInfo(String path)
{
// Path Info only valid for PREFIX_GLOB types
if (_group == PathSpecGroup.PREFIX_GLOB)
{
Matcher matcher = getMatcher(path);
if (matcher.matches())
{
if (matcher.groupCount() >= 1)
{
String pathInfo = matcher.group(1);
if ("".equals(pathInfo))
return "/";
else
return pathInfo;
}
}
}
return null;
}
@Override
public String getPathMatch(String path)
{
Matcher matcher = getMatcher(path);
if (matcher.matches())
{
if (matcher.groupCount() >= 1)
{
int idx = matcher.start(1);
if (idx > 0)
{
if (path.charAt(idx - 1) == '/')
idx--;
return path.substring(0, idx);
}
}
return path;
}
return null;
}
@Override
public String getDeclaration()
{
return _declaration;
}
@Override
public String getPrefix()
{
return null;
}
@Override
public String getSuffix()
{
return null;
}
public Pattern getPattern()
{
return _pattern;
}
@Override
public boolean matches(final String path)
{
int idx = path.indexOf('?');
if (idx >= 0)
{
// match only non-query part
return getMatcher(path.substring(0, idx)).matches();
}
else
{
// match entire path
return getMatcher(path).matches();
}
}
public int getVariableCount()
{
return variables.length;
return _variables.length;
}
public String[] getVariables()
{
return this.variables;
return _variables;
}
}

View File

@ -199,7 +199,7 @@ public class PathMappingsTest
p.put(new ServletPathSpec("/*"), "0");
// assertEquals("1", p.get("/abs/path"), "Get absolute path");
assertEquals("/abs/path", p.getMatch("/abs/path").getPathSpec().pathSpec, "Match absolute path");
assertEquals("/abs/path", p.getMatch("/abs/path").getPathSpec().getDeclaration(), "Match absolute path");
assertEquals("1", p.getMatch("/abs/path").getResource(), "Match absolute path");
assertEquals("0", p.getMatch("/abs/path/xxx").getResource(), "Mismatch absolute path");
assertEquals("0", p.getMatch("/abs/pith").getResource(), "Mismatch absolute path");

View File

@ -45,7 +45,7 @@ public class RegexPathSpecTest
assertEquals("^/a$", spec.getDeclaration(), "Spec.pathSpec");
assertEquals("^/a$", spec.getPattern().pattern(), "Spec.pattern");
assertEquals(1, spec.getPathDepth(), "Spec.pathDepth");
assertEquals(PathSpecGroup.EXACT, spec.group, "Spec.group");
assertEquals(PathSpecGroup.EXACT, spec.getGroup(), "Spec.group");
assertMatches(spec, "/a");
@ -60,7 +60,7 @@ public class RegexPathSpecTest
assertEquals("^/rest/([^/]*)/list$", spec.getDeclaration(), "Spec.pathSpec");
assertEquals("^/rest/([^/]*)/list$", spec.getPattern().pattern(), "Spec.pattern");
assertEquals(3, spec.getPathDepth(), "Spec.pathDepth");
assertEquals(PathSpecGroup.MIDDLE_GLOB, spec.group, "Spec.group");
assertEquals(PathSpecGroup.MIDDLE_GLOB, spec.getGroup(), "Spec.group");
assertMatches(spec, "/rest/api/list");
assertMatches(spec, "/rest/1.0/list");
@ -81,7 +81,7 @@ public class RegexPathSpecTest
assertEquals("^/rest/[^/]+/list$", spec.getDeclaration(), "Spec.pathSpec");
assertEquals("^/rest/[^/]+/list$", spec.getPattern().pattern(), "Spec.pattern");
assertEquals(3, spec.getPathDepth(), "Spec.pathDepth");
assertEquals(PathSpecGroup.MIDDLE_GLOB, spec.group, "Spec.group");
assertEquals(PathSpecGroup.MIDDLE_GLOB, spec.getGroup(), "Spec.group");
assertMatches(spec, "/rest/api/list");
assertMatches(spec, "/rest/1.0/list");
@ -102,7 +102,7 @@ public class RegexPathSpecTest
assertEquals("^/a/(.*)$", spec.getDeclaration(), "Spec.pathSpec");
assertEquals("^/a/(.*)$", spec.getPattern().pattern(), "Spec.pattern");
assertEquals(2, spec.getPathDepth(), "Spec.pathDepth");
assertEquals(PathSpecGroup.PREFIX_GLOB, spec.group, "Spec.group");
assertEquals(PathSpecGroup.PREFIX_GLOB, spec.getGroup(), "Spec.group");
assertMatches(spec, "/a/");
assertMatches(spec, "/a/b");
@ -120,7 +120,7 @@ public class RegexPathSpecTest
assertEquals("^(.*).do$", spec.getDeclaration(), "Spec.pathSpec");
assertEquals("^(.*).do$", spec.getPattern().pattern(), "Spec.pattern");
assertEquals(0, spec.getPathDepth(), "Spec.pathDepth");
assertEquals(PathSpecGroup.SUFFIX_GLOB, spec.group, "Spec.group");
assertEquals(PathSpecGroup.SUFFIX_GLOB, spec.getGroup(), "Spec.group");
assertMatches(spec, "/a.do");
assertMatches(spec, "/a/b/c.do");

View File

@ -82,7 +82,7 @@ public class ServletPathSpecMatchListTest
{
if (delim)
actual.append(", ");
actual.append(res.getPathSpec().pathSpec).append('=').append(res.getResource());
actual.append(res.getPathSpec().getDeclaration()).append('=').append(res.getResource());
delim = true;
}
actual.append(']');