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--);
|
MappedResource<E> candidate = _exactMap.getBest(path, 0, i--);
|
||||||
if (candidate == null)
|
if (candidate == null)
|
||||||
continue;
|
break;
|
||||||
|
|
||||||
matchedPath = candidate.getPathSpec().matched(path);
|
matchedPath = candidate.getPathSpec().matched(path);
|
||||||
if (matchedPath != null)
|
if (matchedPath != null)
|
||||||
{
|
{
|
||||||
return new MatchedResource<>(candidate.getResource(), candidate.getPathSpec(), matchedPath);
|
return new MatchedResource<>(candidate.getResource(), candidate.getPathSpec(), matchedPath);
|
||||||
}
|
}
|
||||||
|
i--;
|
||||||
}
|
}
|
||||||
// If we reached here, there's NO optimized EXACT Match possible, skip simple match below
|
// If we reached here, there's NO optimized EXACT Match possible, skip simple match below
|
||||||
skipRestOfGroup = true;
|
skipRestOfGroup = true;
|
||||||
|
@ -201,11 +202,12 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
|
||||||
{
|
{
|
||||||
MappedResource<E> candidate = _prefixMap.getBest(path, 0, i--);
|
MappedResource<E> candidate = _prefixMap.getBest(path, 0, i--);
|
||||||
if (candidate == null)
|
if (candidate == null)
|
||||||
continue;
|
break;
|
||||||
|
|
||||||
matchedPath = candidate.getPathSpec().matched(path);
|
matchedPath = candidate.getPathSpec().matched(path);
|
||||||
if (matchedPath != null)
|
if (matchedPath != null)
|
||||||
return new MatchedResource<>(candidate.getResource(), candidate.getPathSpec(), matchedPath);
|
return new MatchedResource<>(candidate.getResource(), candidate.getPathSpec(), matchedPath);
|
||||||
|
i--;
|
||||||
}
|
}
|
||||||
// If we reached here, there's NO optimized PREFIX Match possible, skip simple match below
|
// If we reached here, there's NO optimized PREFIX Match possible, skip simple match below
|
||||||
skipRestOfGroup = true;
|
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);
|
MappedResource<E> candidate = _suffixMap.get(path, i + 1, path.length() - i - 1);
|
||||||
if (candidate == null)
|
if (candidate == null)
|
||||||
continue;
|
break;
|
||||||
|
|
||||||
matchedPath = candidate.getPathSpec().matched(path);
|
matchedPath = candidate.getPathSpec().matched(path);
|
||||||
if (matchedPath != null)
|
if (matchedPath != null)
|
||||||
|
@ -259,6 +261,15 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
|
||||||
return _mappings.iterator();
|
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)
|
public E get(PathSpec spec)
|
||||||
{
|
{
|
||||||
return _mappings.stream()
|
return _mappings.stream()
|
||||||
|
|
|
@ -125,9 +125,8 @@ public class RegexPathSpec extends AbstractPathSpec
|
||||||
declaration = regex;
|
declaration = regex;
|
||||||
int specLength = declaration.length();
|
int specLength = declaration.length();
|
||||||
// build up a simple signature we can use to identify the grouping
|
// build up a simple signature we can use to identify the grouping
|
||||||
boolean inCharacterClass = false;
|
boolean inTextList = false;
|
||||||
boolean inQuantifier = false;
|
boolean inQuantifier = false;
|
||||||
boolean inCaptureGroup = false;
|
|
||||||
StringBuilder signature = new StringBuilder();
|
StringBuilder signature = new StringBuilder();
|
||||||
|
|
||||||
int pathDepth = 0;
|
int pathDepth = 0;
|
||||||
|
@ -140,6 +139,8 @@ public class RegexPathSpec extends AbstractPathSpec
|
||||||
case '^': // ignore anchors
|
case '^': // ignore anchors
|
||||||
case '$': // ignore anchors
|
case '$': // ignore anchors
|
||||||
case '\'': // ignore escaping
|
case '\'': // ignore escaping
|
||||||
|
case '(': // ignore grouping
|
||||||
|
case ')': // ignore grouping
|
||||||
break;
|
break;
|
||||||
case '+': // single char quantifier
|
case '+': // single char quantifier
|
||||||
case '?': // single char quantifier
|
case '?': // single char quantifier
|
||||||
|
@ -148,32 +149,25 @@ public class RegexPathSpec extends AbstractPathSpec
|
||||||
case '.': // any char token
|
case '.': // any char token
|
||||||
signature.append('g'); // glob
|
signature.append('g'); // glob
|
||||||
break;
|
break;
|
||||||
case '(': // in regex capture group
|
case '{':
|
||||||
inCaptureGroup = true;
|
|
||||||
break;
|
|
||||||
case ')':
|
|
||||||
inCaptureGroup = false;
|
|
||||||
signature.append('g');
|
|
||||||
break;
|
|
||||||
case '{': // in regex quantifier
|
|
||||||
inQuantifier = true;
|
inQuantifier = true;
|
||||||
break;
|
break;
|
||||||
case '}':
|
case '}':
|
||||||
inQuantifier = false;
|
inQuantifier = false;
|
||||||
break;
|
break;
|
||||||
case '[': // in regex character class
|
case '[':
|
||||||
inCharacterClass = true;
|
inTextList = true;
|
||||||
break;
|
break;
|
||||||
case ']':
|
case ']':
|
||||||
inCharacterClass = false;
|
inTextList = false;
|
||||||
signature.append('g'); // glob
|
signature.append('g'); // glob
|
||||||
break;
|
break;
|
||||||
case '/':
|
case '/':
|
||||||
if (!inCharacterClass && !inQuantifier && !inCaptureGroup)
|
if (!inTextList && !inQuantifier)
|
||||||
pathDepth++;
|
pathDepth++;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (!inCharacterClass && !inQuantifier && !inCaptureGroup && Character.isLetterOrDigit(c))
|
if (!inTextList && !inQuantifier && Character.isLetterOrDigit(c))
|
||||||
{
|
{
|
||||||
if (last == '\\') // escaped
|
if (last == '\\') // escaped
|
||||||
{
|
{
|
||||||
|
@ -325,6 +319,101 @@ public class RegexPathSpec extends AbstractPathSpec
|
||||||
return null;
|
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 class RegexMatchedPath implements MatchedPath
|
||||||
{
|
{
|
||||||
private final RegexPathSpec pathSpec;
|
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.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
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
|
public class RegexPathSpecTest
|
||||||
{
|
{
|
||||||
|
@ -201,8 +204,8 @@ public class RegexPathSpecTest
|
||||||
assertNotMatches(spec, "/aa/bb");
|
assertNotMatches(spec, "/aa/bb");
|
||||||
assertNotMatches(spec, "/aa/bb.do/more");
|
assertNotMatches(spec, "/aa/bb.do/more");
|
||||||
|
|
||||||
assertThat(spec.getPathMatch("/a/b/c.do"), equalTo(""));
|
assertThat(spec.getPathMatch("/a/b/c.do"), equalTo("/a/b/c.do"));
|
||||||
assertThat(spec.getPathInfo("/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.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.server.LEVEL=DEBUG
|
#org.eclipse.jetty.server.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.http.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.HttpServletMapping;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.MappingMatch;
|
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.PathSpec;
|
||||||
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
|
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
|
||||||
|
|
||||||
|
@ -38,71 +39,100 @@ public class ServletPathMapping implements HttpServletMapping
|
||||||
private final String _servletPath;
|
private final String _servletPath;
|
||||||
private final String _pathInfo;
|
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);
|
_servletName = (servletName == null ? "" : servletName);
|
||||||
_pattern = pathSpec == null ? null : pathSpec.getDeclaration();
|
|
||||||
|
|
||||||
if (pathSpec instanceof ServletPathSpec && pathInContext != null)
|
if (pathSpec == 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
|
|
||||||
{
|
{
|
||||||
|
_pattern = null;
|
||||||
_mappingMatch = null;
|
_mappingMatch = null;
|
||||||
_matchValue = "";
|
_matchValue = "";
|
||||||
_servletPath = pathInContext;
|
_servletPath = pathInContext;
|
||||||
_pathInfo = null;
|
_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
|
@Override
|
||||||
|
|
|
@ -64,6 +64,7 @@ import org.eclipse.jetty.http.HttpURI;
|
||||||
import org.eclipse.jetty.http.MetaData;
|
import org.eclipse.jetty.http.MetaData;
|
||||||
import org.eclipse.jetty.http.MimeTypes;
|
import org.eclipse.jetty.http.MimeTypes;
|
||||||
import org.eclipse.jetty.http.UriCompliance;
|
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.RegexPathSpec;
|
||||||
import org.eclipse.jetty.http.pathmap.MatchedPath;
|
import org.eclipse.jetty.http.pathmap.MatchedPath;
|
||||||
import org.eclipse.jetty.http.pathmap.RegexPathSpec;
|
import org.eclipse.jetty.http.pathmap.RegexPathSpec;
|
||||||
|
|
Loading…
Reference in New Issue