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:
parent
3845790c5d
commit
85a5452f49
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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(']');
|
||||
|
|
Loading…
Reference in New Issue