Cherry-pick of Improvements to PathSpec for Jetty 10.0.x (#8136)
* Cherry-pick of Improvements to PathSpec.
* From commit: 5b4d1dd1c6
* Fixing ConstraintSecurityHandler usage of PathMappings
* Fixing bad INCLUDE logic from cherry-pick in ServletHandler.doScope()
* Cleanup of non ServletPathSpec behaviors in ServletPathMapping class
* Skip optional group name/info lookup if regex fails.
* Prevent NPE on static servletPathMappings
* Update WebSocketMappings to use new PathMappings.getMatched(String)
Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
parent
351fe53c9a
commit
25dd6d0ef7
|
@ -178,13 +178,14 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
|
|||
{
|
||||
MappedResource<E> candidate = _exactMap.getBest(path, 0, i--);
|
||||
if (candidate == null)
|
||||
continue;
|
||||
break;
|
||||
|
||||
matchedPath = candidate.getPathSpec().matched(path);
|
||||
if (matchedPath != null)
|
||||
{
|
||||
return new MatchedResource<>(candidate.getResource(), candidate.getPathSpec(), matchedPath);
|
||||
}
|
||||
i--;
|
||||
}
|
||||
// If we reached here, there's NO optimized EXACT Match possible, skip simple match below
|
||||
skipRestOfGroup = true;
|
||||
|
@ -201,11 +202,12 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
|
|||
{
|
||||
MappedResource<E> candidate = _prefixMap.getBest(path, 0, i--);
|
||||
if (candidate == null)
|
||||
continue;
|
||||
break;
|
||||
|
||||
matchedPath = candidate.getPathSpec().matched(path);
|
||||
if (matchedPath != null)
|
||||
return new MatchedResource<>(candidate.getResource(), candidate.getPathSpec(), matchedPath);
|
||||
i--;
|
||||
}
|
||||
// If we reached here, there's NO optimized PREFIX Match possible, skip simple match below
|
||||
skipRestOfGroup = true;
|
||||
|
@ -227,7 +229,7 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
|
|||
{
|
||||
MappedResource<E> candidate = _suffixMap.get(path, i + 1, path.length() - i - 1);
|
||||
if (candidate == null)
|
||||
continue;
|
||||
break;
|
||||
|
||||
matchedPath = candidate.getPathSpec().matched(path);
|
||||
if (matchedPath != null)
|
||||
|
@ -259,6 +261,15 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
|
|||
return _mappings.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link PathSpec#from(String)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static PathSpec asPathSpec(String pathSpecString)
|
||||
{
|
||||
return PathSpec.from(pathSpecString);
|
||||
}
|
||||
|
||||
public E get(PathSpec spec)
|
||||
{
|
||||
return _mappings.stream()
|
||||
|
|
|
@ -125,9 +125,8 @@ public class RegexPathSpec extends AbstractPathSpec
|
|||
declaration = regex;
|
||||
int specLength = declaration.length();
|
||||
// build up a simple signature we can use to identify the grouping
|
||||
boolean inCharacterClass = false;
|
||||
boolean inTextList = false;
|
||||
boolean inQuantifier = false;
|
||||
boolean inCaptureGroup = false;
|
||||
StringBuilder signature = new StringBuilder();
|
||||
|
||||
int pathDepth = 0;
|
||||
|
@ -140,6 +139,8 @@ public class RegexPathSpec extends AbstractPathSpec
|
|||
case '^': // ignore anchors
|
||||
case '$': // ignore anchors
|
||||
case '\'': // ignore escaping
|
||||
case '(': // ignore grouping
|
||||
case ')': // ignore grouping
|
||||
break;
|
||||
case '+': // single char quantifier
|
||||
case '?': // single char quantifier
|
||||
|
@ -148,32 +149,25 @@ public class RegexPathSpec extends AbstractPathSpec
|
|||
case '.': // any char token
|
||||
signature.append('g'); // glob
|
||||
break;
|
||||
case '(': // in regex capture group
|
||||
inCaptureGroup = true;
|
||||
break;
|
||||
case ')':
|
||||
inCaptureGroup = false;
|
||||
signature.append('g');
|
||||
break;
|
||||
case '{': // in regex quantifier
|
||||
case '{':
|
||||
inQuantifier = true;
|
||||
break;
|
||||
case '}':
|
||||
inQuantifier = false;
|
||||
break;
|
||||
case '[': // in regex character class
|
||||
inCharacterClass = true;
|
||||
case '[':
|
||||
inTextList = true;
|
||||
break;
|
||||
case ']':
|
||||
inCharacterClass = false;
|
||||
inTextList = false;
|
||||
signature.append('g'); // glob
|
||||
break;
|
||||
case '/':
|
||||
if (!inCharacterClass && !inQuantifier && !inCaptureGroup)
|
||||
if (!inTextList && !inQuantifier)
|
||||
pathDepth++;
|
||||
break;
|
||||
default:
|
||||
if (!inCharacterClass && !inQuantifier && !inCaptureGroup && Character.isLetterOrDigit(c))
|
||||
if (!inTextList && !inQuantifier && Character.isLetterOrDigit(c))
|
||||
{
|
||||
if (last == '\\') // escaped
|
||||
{
|
||||
|
@ -325,6 +319,101 @@ public class RegexPathSpec extends AbstractPathSpec
|
|||
return null;
|
||||
}
|
||||
|
||||
private class RegexMatchedPath implements MatchedPath
|
||||
{
|
||||
private final RegexPathSpec pathSpec;
|
||||
private final String path;
|
||||
private final Matcher matcher;
|
||||
|
||||
public RegexMatchedPath(RegexPathSpec regexPathSpec, String path, Matcher matcher)
|
||||
{
|
||||
this.pathSpec = regexPathSpec;
|
||||
this.path = path;
|
||||
this.matcher = matcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPathMatch()
|
||||
{
|
||||
try
|
||||
{
|
||||
String p = matcher.group("name");
|
||||
if (p != null)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ignore)
|
||||
{
|
||||
// ignore if group name not found.
|
||||
}
|
||||
|
||||
if (pathSpec.getGroup() == PathSpecGroup.PREFIX_GLOB && matcher.groupCount() >= 1)
|
||||
{
|
||||
int idx = matcher.start(1);
|
||||
if (idx > 0)
|
||||
{
|
||||
if (this.path.charAt(idx - 1) == '/')
|
||||
idx--;
|
||||
return this.path.substring(0, idx);
|
||||
}
|
||||
}
|
||||
|
||||
// default is the full path
|
||||
return this.path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPathInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
String p = matcher.group("info");
|
||||
if (p != null)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ignore)
|
||||
{
|
||||
// ignore if group info not found.
|
||||
}
|
||||
|
||||
// Path Info only valid for PREFIX_GLOB
|
||||
if (pathSpec.getGroup() == PathSpecGroup.PREFIX_GLOB && matcher.groupCount() >= 1)
|
||||
{
|
||||
String pathInfo = matcher.group(1);
|
||||
if ("".equals(pathInfo))
|
||||
return "/";
|
||||
else
|
||||
return pathInfo;
|
||||
}
|
||||
|
||||
// default is null
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return getClass().getSimpleName() + "[" +
|
||||
"pathSpec=" + pathSpec +
|
||||
", path=\"" + path + "\"" +
|
||||
", matcher=" + matcher +
|
||||
']';
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatchedPath matched(String path)
|
||||
{
|
||||
Matcher matcher = getMatcher(path);
|
||||
if (matcher.matches())
|
||||
{
|
||||
return new RegexMatchedPath(this, path, matcher);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private class RegexMatchedPath implements MatchedPath
|
||||
{
|
||||
private final RegexPathSpec pathSpec;
|
||||
|
|
|
@ -28,6 +28,9 @@ import static org.hamcrest.Matchers.nullValue;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class RegexPathSpecTest
|
||||
{
|
||||
|
@ -201,8 +204,8 @@ public class RegexPathSpecTest
|
|||
assertNotMatches(spec, "/aa/bb");
|
||||
assertNotMatches(spec, "/aa/bb.do/more");
|
||||
|
||||
assertThat(spec.getPathMatch("/a/b/c.do"), equalTo(""));
|
||||
assertThat(spec.getPathInfo("/a/b/c.do"), equalTo("/a/b/c.do"));
|
||||
assertThat(spec.getPathMatch("/a/b/c.do"), equalTo("/a/b/c.do"));
|
||||
assertThat(spec.getPathInfo("/a/b/c.do"), nullValue());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.server.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.http.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.http.pathmap.LEVEL=DEBUG
|
||||
|
|
|
@ -16,6 +16,7 @@ package org.eclipse.jetty.ee10.servlet;
|
|||
import jakarta.servlet.http.HttpServletMapping;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.MappingMatch;
|
||||
import org.eclipse.jetty.http.pathmap.MatchedPath;
|
||||
import org.eclipse.jetty.http.pathmap.PathSpec;
|
||||
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
|
||||
|
||||
|
@ -38,71 +39,100 @@ public class ServletPathMapping implements HttpServletMapping
|
|||
private final String _servletPath;
|
||||
private final String _pathInfo;
|
||||
|
||||
public ServletPathMapping(PathSpec pathSpec, String servletName, String pathInContext)
|
||||
public ServletPathMapping(PathSpec pathSpec, String servletName, String pathInContext, MatchedPath matchedPath)
|
||||
{
|
||||
_servletName = (servletName == null ? "" : servletName);
|
||||
_pattern = pathSpec == null ? null : pathSpec.getDeclaration();
|
||||
|
||||
if (pathSpec instanceof ServletPathSpec && pathInContext != null)
|
||||
{
|
||||
switch (pathSpec.getGroup())
|
||||
{
|
||||
case ROOT:
|
||||
_mappingMatch = MappingMatch.CONTEXT_ROOT;
|
||||
_matchValue = "";
|
||||
_servletPath = "";
|
||||
_pathInfo = "/";
|
||||
break;
|
||||
|
||||
case DEFAULT:
|
||||
_mappingMatch = MappingMatch.DEFAULT;
|
||||
_matchValue = "";
|
||||
_servletPath = pathInContext;
|
||||
_pathInfo = null;
|
||||
break;
|
||||
|
||||
case EXACT:
|
||||
_mappingMatch = MappingMatch.EXACT;
|
||||
_matchValue = _pattern.startsWith("/") ? _pattern.substring(1) : _pattern;
|
||||
_servletPath = _pattern;
|
||||
_pathInfo = null;
|
||||
break;
|
||||
|
||||
case PREFIX_GLOB:
|
||||
_mappingMatch = MappingMatch.PATH;
|
||||
_servletPath = pathSpec.getPrefix();
|
||||
// TODO avoid the substring on the known servletPath!
|
||||
_matchValue = _servletPath.startsWith("/") ? _servletPath.substring(1) : _servletPath;
|
||||
_pathInfo = pathSpec.getPathInfo(pathInContext);
|
||||
break;
|
||||
|
||||
case SUFFIX_GLOB:
|
||||
_mappingMatch = MappingMatch.EXTENSION;
|
||||
int dot = pathInContext.lastIndexOf('.');
|
||||
_matchValue = pathInContext.substring(pathInContext.startsWith("/") ? 1 : 0, dot);
|
||||
_servletPath = pathInContext;
|
||||
_pathInfo = null;
|
||||
break;
|
||||
|
||||
case MIDDLE_GLOB:
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
else if (pathSpec != null)
|
||||
{
|
||||
_mappingMatch = null;
|
||||
_servletPath = pathSpec.getPathMatch(pathInContext);
|
||||
_matchValue = _servletPath.startsWith("/") ? _servletPath.substring(1) : _servletPath;
|
||||
_pathInfo = pathSpec.getPathInfo(pathInContext);
|
||||
}
|
||||
else
|
||||
if (pathSpec == null)
|
||||
{
|
||||
_pattern = null;
|
||||
_mappingMatch = null;
|
||||
_matchValue = "";
|
||||
_servletPath = pathInContext;
|
||||
_pathInfo = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathInContext == null)
|
||||
{
|
||||
_pattern = pathSpec.getDeclaration();
|
||||
_mappingMatch = null;
|
||||
_matchValue = "";
|
||||
_servletPath = "";
|
||||
_pathInfo = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Path Spec types that are not ServletPathSpec
|
||||
if (!(pathSpec instanceof ServletPathSpec))
|
||||
{
|
||||
_pattern = pathSpec.getDeclaration();
|
||||
_mappingMatch = null;
|
||||
if (matchedPath != null)
|
||||
{
|
||||
_servletPath = matchedPath.getPathMatch();
|
||||
_pathInfo = matchedPath.getPathInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
_servletPath = pathInContext;
|
||||
_pathInfo = null;
|
||||
}
|
||||
_matchValue = _servletPath.substring(_servletPath.charAt(0) == '/' ? 1 : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// from here down is ServletPathSpec behavior
|
||||
_pattern = pathSpec.getDeclaration();
|
||||
|
||||
switch (pathSpec.getGroup())
|
||||
{
|
||||
case ROOT:
|
||||
_mappingMatch = MappingMatch.CONTEXT_ROOT;
|
||||
_matchValue = "";
|
||||
_servletPath = "";
|
||||
_pathInfo = "/";
|
||||
break;
|
||||
|
||||
case DEFAULT:
|
||||
_mappingMatch = MappingMatch.DEFAULT;
|
||||
_matchValue = "";
|
||||
_servletPath = pathInContext;
|
||||
_pathInfo = null;
|
||||
break;
|
||||
|
||||
case EXACT:
|
||||
_mappingMatch = MappingMatch.EXACT;
|
||||
_matchValue = _pattern.startsWith("/") ? _pattern.substring(1) : _pattern;
|
||||
_servletPath = _pattern;
|
||||
_pathInfo = null;
|
||||
break;
|
||||
|
||||
case PREFIX_GLOB:
|
||||
_mappingMatch = MappingMatch.PATH;
|
||||
_servletPath = pathSpec.getPrefix();
|
||||
// TODO avoid the substring on the known servletPath!
|
||||
_matchValue = _servletPath.startsWith("/") ? _servletPath.substring(1) : _servletPath;
|
||||
_pathInfo = matchedPath != null ? matchedPath.getPathInfo() : null;
|
||||
break;
|
||||
|
||||
case SUFFIX_GLOB:
|
||||
_mappingMatch = MappingMatch.EXTENSION;
|
||||
int dot = pathInContext.lastIndexOf('.');
|
||||
_matchValue = pathInContext.substring(pathInContext.startsWith("/") ? 1 : 0, dot);
|
||||
_servletPath = pathInContext;
|
||||
_pathInfo = null;
|
||||
break;
|
||||
|
||||
case MIDDLE_GLOB:
|
||||
default:
|
||||
throw new IllegalStateException("ServletPathSpec of type MIDDLE_GLOB");
|
||||
}
|
||||
}
|
||||
|
||||
public ServletPathMapping(PathSpec pathSpec, String servletName, String pathInContext)
|
||||
{
|
||||
this(pathSpec, servletName, pathInContext, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -64,6 +64,7 @@ import org.eclipse.jetty.http.HttpURI;
|
|||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.http.UriCompliance;
|
||||
import org.eclipse.jetty.http.pathmap.MatchedPath;
|
||||
import org.eclipse.jetty.http.pathmap.RegexPathSpec;
|
||||
import org.eclipse.jetty.http.pathmap.MatchedPath;
|
||||
import org.eclipse.jetty.http.pathmap.RegexPathSpec;
|
||||
|
|
Loading…
Reference in New Issue