//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.pathmap;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
public class ServletPathSpec extends PathSpec
{
If a servlet or filter path mapping isn't a suffix mapping, ensure
it starts with '/'
Params: - pathSpec – the servlet or filter mapping pattern
Returns: the pathSpec prefixed by '/' if appropriate
/**
* If a servlet or filter path mapping isn't a suffix mapping, ensure
* it starts with '/'
*
* @param pathSpec the servlet or filter mapping pattern
* @return the pathSpec prefixed by '/' if appropriate
*/
public static String normalize(String pathSpec)
{
if (StringUtil.isNotBlank(pathSpec) && !pathSpec.startsWith("/") && !pathSpec.startsWith("*"))
return "/" + pathSpec;
return pathSpec;
}
Params: - pathSpec – the path spec
- path – the path
Returns: true if match.
/**
* @param pathSpec the path spec
* @param path the path
* @return true if match.
*/
public static boolean match(String pathSpec, String path)
{
return match(pathSpec, path, false);
}
Params: - pathSpec – the path spec
- path – the path
- noDefault – true to not handle the default path "/" special, false to allow matcher rules to run
Returns: true if match.
/**
* @param pathSpec the path spec
* @param path the path
* @param noDefault true to not handle the default path "/" special, false to allow matcher rules to run
* @return true if match.
*/
public static boolean match(String pathSpec, String path, boolean noDefault)
{
if (pathSpec.length() == 0)
return "/".equals(path);
char c = pathSpec.charAt(0);
if (c == '/')
{
if (!noDefault && pathSpec.length() == 1 || pathSpec.equals(path))
return true;
if (isPathWildcardMatch(pathSpec, path))
return true;
}
else if (c == '*')
return path.regionMatches(path.length() - pathSpec.length() + 1,
pathSpec, 1, pathSpec.length() - 1);
return false;
}
private static boolean isPathWildcardMatch(String pathSpec, String path)
{
// For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
int cpl = pathSpec.length() - 2;
if (pathSpec.endsWith("/*") && path.regionMatches(0, pathSpec, 0, cpl))
{
if (path.length() == cpl || '/' == path.charAt(cpl))
return true;
}
return false;
}
Return the portion of a path that matches a path spec.
Params: - pathSpec – the path spec
- path – the path
Returns: null if no match at all.
/**
* Return the portion of a path that matches a path spec.
*
* @param pathSpec the path spec
* @param path the path
* @return null if no match at all.
*/
public static String pathMatch(String pathSpec, String path)
{
char c = pathSpec.charAt(0);
if (c == '/')
{
if (pathSpec.length() == 1)
return path;
if (pathSpec.equals(path))
return path;
if (isPathWildcardMatch(pathSpec, path))
return path.substring(0, pathSpec.length() - 2);
}
else if (c == '*')
{
if (path.regionMatches(path.length() - (pathSpec.length() - 1),
pathSpec, 1, pathSpec.length() - 1))
return path;
}
return null;
}
Return the portion of a path that is after a path spec.
Params: - pathSpec – the path spec
- path – the path
Returns: The path info string
/**
* Return the portion of a path that is after a path spec.
*
* @param pathSpec the path spec
* @param path the path
* @return The path info string
*/
public static String pathInfo(String pathSpec, String path)
{
if ("".equals(pathSpec))
return path; //servlet 3 spec sec 12.2 will be '/'
char c = pathSpec.charAt(0);
if (c == '/')
{
if (pathSpec.length() == 1)
return null;
boolean wildcard = isPathWildcardMatch(pathSpec, path);
// handle the case where pathSpec uses a wildcard and path info is "/*"
if (pathSpec.equals(path) && !wildcard)
return null;
if (wildcard)
{
if (path.length() == pathSpec.length() - 2)
return null;
return path.substring(pathSpec.length() - 2);
}
}
return null;
}
Relative path.
Params: - base – The base the path is relative to.
- pathSpec – The spec of the path segment to ignore.
- path – the additional path
Returns: base plus path with pathspec removed
/**
* Relative path.
*
* @param base The base the path is relative to.
* @param pathSpec The spec of the path segment to ignore.
* @param path the additional path
* @return base plus path with pathspec removed
*/
public static String relativePath(String base,
String pathSpec,
String path)
{
String info = pathInfo(pathSpec, path);
if (info == null)
info = path;
if (info.startsWith("./"))
info = info.substring(2);
if (base.endsWith(URIUtil.SLASH))
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;
}
public ServletPathSpec(String servletPathSpec)
{
if (servletPathSpec == null)
servletPathSpec = "";
if (servletPathSpec.startsWith("servlet|"))
servletPathSpec = servletPathSpec.substring("servlet|".length());
assertValidServletPathSpec(servletPathSpec);
// 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;
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;
return;
}
this.specLength = servletPathSpec.length();
super.pathDepth = 0;
char lastChar = servletPathSpec.charAt(specLength - 1);
// prefix based
if ((servletPathSpec.charAt(0) == '/') && (specLength > 1) && (lastChar == '*'))
{
this.group = PathSpecGroup.PREFIX_GLOB;
this.prefix = servletPathSpec.substring(0, specLength - 2);
}
// suffix based
else if (servletPathSpec.charAt(0) == '*')
{
this.group = PathSpecGroup.SUFFIX_GLOB;
this.suffix = servletPathSpec.substring(2, specLength);
}
else
{
this.group = PathSpecGroup.EXACT;
this.prefix = servletPathSpec;
}
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;
default:
break;
}
}
}
super.pathSpec = servletPathSpec;
}
private void assertValidServletPathSpec(String servletPathSpec)
{
if ((servletPathSpec == null) || servletPathSpec.equals(""))
{
return; // empty path spec
}
int len = servletPathSpec.length();
// path spec must either start with '/' or '*.'
if (servletPathSpec.charAt(0) == '/')
{
// Prefix Based
if (len == 1)
{
return; // simple '/' path spec
}
int idx = servletPathSpec.indexOf('*');
if (idx < 0)
{
return; // no hit on glob '*'
}
// only allowed to have '*' at the end of the path spec
if (idx != (len - 1))
{
throw new IllegalArgumentException("Servlet Spec 12.2 violation: glob '*' can only exist at end of prefix based matches: bad spec \"" + servletPathSpec + "\"");
}
if (idx < 1 || servletPathSpec.charAt(idx - 1) != '/')
{
throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix glob '*' can only exist after '/': bad spec \"" + servletPathSpec + "\"");
}
}
else if (servletPathSpec.startsWith("*."))
{
// Suffix Based
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 String getPathInfo(String path)
{
// Path Info only valid for PREFIX_GLOB types
if (group == PathSpecGroup.PREFIX_GLOB)
{
if (path.length() == (specLength - 2))
{
return null;
}
return path.substring(specLength - 2);
}
return null;
}
@Override
public String getPathMatch(String path)
{
switch (group)
{
case EXACT:
if (pathSpec.equals(path))
{
return path;
}
else
{
return null;
}
case PREFIX_GLOB:
if (isWildcardMatch(path))
{
return path.substring(0, specLength - 2);
}
else
{
return null;
}
case SUFFIX_GLOB:
if (path.regionMatches(path.length() - (specLength - 1), pathSpec, 1, specLength - 1))
{
return path;
}
else
{
return null;
}
case DEFAULT:
return path;
default:
return null;
}
}
@Override
public String getRelativePath(String base, String path)
{
String info = getPathInfo(path);
if (info == null)
{
info = path;
}
if (info.startsWith("./"))
{
info = info.substring(2);
}
if (base.endsWith(URIUtil.SLASH))
{
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;
}
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)))
{
if ((path.length() == cpl) || ('/' == path.charAt(cpl)))
{
return true;
}
}
return false;
}
@Override
public boolean matches(String path)
{
switch (group)
{
case EXACT:
return pathSpec.equals(path);
case PREFIX_GLOB:
return isWildcardMatch(path);
case SUFFIX_GLOB:
return path.regionMatches((path.length() - specLength) + 1, pathSpec, 1, specLength - 1);
case ROOT:
// Only "/" matches
return ("/".equals(path));
case DEFAULT:
// If we reached this point, then everything matches
return true;
default:
return false;
}
}
}