Jetty 12 - General cleanup of URIUtil (#8861)

+ Removal of __CHARSET
+ Removal of unused methods
+ Using new switch/case concepts
+ cleanup of URIUtil constants
+ cleanup of URIUtil methods
+ collapse separate methods
+ simplify encodePath()
* Javadoc updates
* equalsIgnoreEncoding cleanup (no longer used by Resource layer)
This commit is contained in:
Joakim Erdfelt 2022-11-04 14:28:53 -05:00 committed by GitHub
parent 41e9842921
commit b3505ae687
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 240 additions and 581 deletions

View File

@ -36,7 +36,6 @@ import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.FileID; import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Environment; import org.eclipse.jetty.util.component.Environment;
@ -481,7 +480,7 @@ public class ContextProvider extends ScanningAppProvider
// special case of archive (or dir) named "root" is / context // special case of archive (or dir) named "root" is / context
if (contextPath.equalsIgnoreCase("root")) if (contextPath.equalsIgnoreCase("root"))
{ {
contextPath = URIUtil.SLASH; contextPath = "/";
} }
// handle root with virtual host form // handle root with virtual host form
else if (StringUtil.startsWithIgnoreCase(contextPath, "root-")) else if (StringUtil.startsWithIgnoreCase(contextPath, "root-"))
@ -489,7 +488,7 @@ public class ContextProvider extends ScanningAppProvider
int dash = contextPath.indexOf('-'); int dash = contextPath.indexOf('-');
String virtual = contextPath.substring(dash + 1); String virtual = contextPath.substring(dash + 1);
context.setVirtualHosts(Arrays.asList(virtual.split(","))); context.setVirtualHosts(Arrays.asList(virtual.split(",")));
contextPath = URIUtil.SLASH; contextPath = "/";
} }
// Ensure "/" is Prepended to all context paths. // Ensure "/" is Prepended to all context paths.

View File

@ -18,7 +18,6 @@ import java.nio.file.Path;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.FileID; import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.Environment; import org.eclipse.jetty.util.component.Environment;
@ -71,7 +70,7 @@ public class MockAppProvider extends AbstractLifeCycle implements AppProvider
// special case of archive (or dir) named "root" is / context // special case of archive (or dir) named "root" is / context
if (path.equalsIgnoreCase("root") || path.equalsIgnoreCase("root/")) if (path.equalsIgnoreCase("root") || path.equalsIgnoreCase("root/"))
path = URIUtil.SLASH; path = "/";
// Ensure "/" is Prepended to all context paths. // Ensure "/" is Prepended to all context paths.
if (path.charAt(0) != '/') if (path.charAt(0) != '/')

View File

@ -177,15 +177,15 @@ public class ServletPathSpec extends AbstractPathSpec
if (info.startsWith("./")) if (info.startsWith("./"))
info = info.substring(2); info = info.substring(2);
if (base.endsWith(URIUtil.SLASH)) if (base.endsWith("/"))
if (info.startsWith(URIUtil.SLASH)) if (info.startsWith("/"))
path = base + info.substring(1); path = base + info.substring(1);
else else
path = base + info; path = base + info;
else if (info.startsWith(URIUtil.SLASH)) else if (info.startsWith("/"))
path = base + info; path = base + info;
else else
path = base + URIUtil.SLASH + info; path = base + "/" + info;
return path; return path;
} }

View File

@ -225,7 +225,7 @@ public class ResourceListing
// Ensure name has a slash if it's a directory // Ensure name has a slash if it's a directory
if (item.isDirectory() && !name.endsWith("/")) if (item.isDirectory() && !name.endsWith("/"))
name += URIUtil.SLASH; name += "/";
// Name // Name
buf.append("<tr><td class=\"name\"><a href=\""); buf.append("<tr><td class=\"name\"><a href=\"");

View File

@ -156,7 +156,7 @@ public class ResourceService
// Is this a Range request? // Is this a Range request?
List<String> reqRanges = request.getHeaders().getValuesList(HttpHeader.RANGE.asString()); List<String> reqRanges = request.getHeaders().getValuesList(HttpHeader.RANGE.asString());
boolean endsWithSlash = pathInContext.endsWith(URIUtil.SLASH); boolean endsWithSlash = pathInContext.endsWith("/");
boolean checkPrecompressedVariants = _precompressedFormats.size() > 0 && !endsWithSlash && reqRanges.isEmpty(); boolean checkPrecompressedVariants = _precompressedFormats.size() > 0 && !endsWithSlash && reqRanges.isEmpty();
try try
@ -552,7 +552,7 @@ public class ResourceService
return; return;
} }
String base = URIUtil.addEncodedPaths(request.getHttpURI().getPath(), URIUtil.SLASH); String base = URIUtil.addEncodedPaths(request.getHttpURI().getPath(), "/");
String listing = ResourceListing.getAsXHTML(httpContent.getResource(), base, pathInContext.length() > 1, request.getHttpURI().getQuery()); String listing = ResourceListing.getAsXHTML(httpContent.getResource(), base, pathInContext.length() > 1, request.getHttpURI().getQuery());
if (listing == null) if (listing == null)
{ {

View File

@ -18,7 +18,6 @@ import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -55,17 +54,10 @@ public final class URIUtil
.with("jar:") .with("jar:")
.build(); .build();
public static final String SLASH = "/";
public static final String HTTP = "http";
public static final String HTTPS = "https";
// Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars
public static final Charset __CHARSET = StandardCharsets.UTF_8;
/** /**
* The characters that are supported by the URI class and that can be decoded by {@link #canonicalPath(String)} * The characters that are supported by the URI class and that can be decoded by {@link #canonicalPath(String)}
*/ */
public static final boolean[] __uriSupportedCharacters = new boolean[] private static final boolean[] URI_SUPPORTED_CHARACTERS = new boolean[]
{ {
false, // 0x00 is illegal false, // 0x00 is illegal
false, // 0x01 is illegal false, // 0x01 is illegal
@ -197,242 +189,102 @@ public final class URIUtil
false, // 0x7f DEL is illegal false, // 0x7f DEL is illegal
}; };
private static final boolean[] ENCODE_PATH_NEEDS_ENCODING;
private URIUtil() private URIUtil()
{ {
} }
static
{
ENCODE_PATH_NEEDS_ENCODING = new boolean[128];
// Special characters
for (char c: "%?;#\"'<> [\\]^`{|}".toCharArray())
ENCODE_PATH_NEEDS_ENCODING[c] = true;
// control characters
ENCODE_PATH_NEEDS_ENCODING[0x7f] = true;
for (int i = 0; i < 0x20; i++)
ENCODE_PATH_NEEDS_ENCODING[i] = true;
}
/** /**
* Encode a URI path. * Encode a URI path.
* This is the same encoding offered by URLEncoder, except that * This is the same encoding offered by URLEncoder, except that
* the '/' character is not encoded. * the '{@code /}' character is not encoded.
* *
* @param path The path the encode * @param path The path to encode
* @return The encoded path * @return The encoded path
*/ */
public static String encodePath(String path) public static String encodePath(String path)
{ {
if (path == null || path.length() == 0) if (StringUtil.isEmpty(path))
return path; return path;
StringBuilder buf = encodePath(null, path, 0); // byte encoding always wins and, if encountered, should be used.
return buf == null ? path : buf.toString(); boolean needsByteEncoding = false;
} // string (char-by-char) encoding, but it could be followed by a need for byte encoding instead
boolean needsEncoding = false;
/** int length = path.length();
* Encode a URI path. for (int i = 0; i < length; i++)
*
* @param path The path the encode
* @param buf StringBuilder to encode path into (or null)
* @return The StringBuilder or null if no substitutions required.
*/
public static StringBuilder encodePath(StringBuilder buf, String path)
{
return encodePath(buf, path, 0);
}
/**
* Encode a URI path.
*
* @param path The path the encode
* @param buf StringBuilder to encode path into (or null)
* @return The StringBuilder or null if no substitutions required.
*/
private static StringBuilder encodePath(StringBuilder buf, String path, int offset)
{
byte[] bytes = null;
if (buf == null)
{
loop:
for (int i = offset; i < path.length(); i++)
{
char c = path.charAt(i);
switch (c)
{
case '%':
case '?':
case ';':
case '#':
case '"':
case '\'':
case '<':
case '>':
case ' ':
case '[':
case '\\':
case ']':
case '^':
case '`':
case '{':
case '|':
case '}':
buf = new StringBuilder(path.length() * 2);
break loop;
default:
if (c < 0x20 || c >= 0x7f)
{
bytes = path.getBytes(URIUtil.__CHARSET);
buf = new StringBuilder(path.length() * 2);
break loop;
}
}
}
if (buf == null)
return null;
}
int i;
loop:
for (i = offset; i < path.length(); i++)
{ {
char c = path.charAt(i); char c = path.charAt(i);
switch (c) if (c > 0x7F) // 8-bit +
{ {
case '%': needsByteEncoding = true;
buf.append("%25"); break; // have to encode byte by byte now
continue; }
case '?': if (ENCODE_PATH_NEEDS_ENCODING[c])
buf.append("%3F"); {
continue; // could be followed by a byte encoding, so no break
case ';': needsEncoding = true;
buf.append("%3B");
continue;
case '#':
buf.append("%23");
continue;
case '"':
buf.append("%22");
continue;
case '\'':
buf.append("%27");
continue;
case '<':
buf.append("%3C");
continue;
case '>':
buf.append("%3E");
continue;
case ' ':
buf.append("%20");
continue;
case '[':
buf.append("%5B");
continue;
case '\\':
buf.append("%5C");
continue;
case ']':
buf.append("%5D");
continue;
case '^':
buf.append("%5E");
continue;
case '`':
buf.append("%60");
continue;
case '{':
buf.append("%7B");
continue;
case '|':
buf.append("%7C");
continue;
case '}':
buf.append("%7D");
continue;
default:
if (c < 0x20 || c >= 0x7f)
{
bytes = path.getBytes(URIUtil.__CHARSET);
break loop;
}
buf.append(c);
} }
} }
if (bytes != null) if (needsByteEncoding)
{ return encodePathBytes(path);
for (; i < bytes.length; i++) else if (needsEncoding)
{ return encodePathString(path);
byte c = bytes[i]; else
switch (c) return path;
{
case '%':
buf.append("%25");
continue;
case '?':
buf.append("%3F");
continue;
case ';':
buf.append("%3B");
continue;
case '#':
buf.append("%23");
continue;
case '"':
buf.append("%22");
continue;
case '\'':
buf.append("%27");
continue;
case '<':
buf.append("%3C");
continue;
case '>':
buf.append("%3E");
continue;
case ' ':
buf.append("%20");
continue;
case '[':
buf.append("%5B");
continue;
case '\\':
buf.append("%5C");
continue;
case ']':
buf.append("%5D");
continue;
case '^':
buf.append("%5E");
continue;
case '`':
buf.append("%60");
continue;
case '{':
buf.append("%7B");
continue;
case '|':
buf.append("%7C");
continue;
case '}':
buf.append("%7D");
continue;
default:
if (c < 0x20 || c >= 0x7f)
{
buf.append('%');
TypeUtil.toHex(c, buf);
}
else
buf.append((char)c);
}
}
}
return buf;
} }
/** private static String encodePathString(String path)
* Encode a raw URI String and convert any raw spaces to
* their "%20" equivalent.
*
* @param str input raw string
* @return output with spaces converted to "%20"
*/
public static String encodeSpaces(String str)
{ {
return StringUtil.replace(str, " ", "%20"); StringBuilder buf = new StringBuilder(path.length() * 2);
int length = path.length();
for (int i = 0; i < length; i++)
{
char c = path.charAt(i);
if (ENCODE_PATH_NEEDS_ENCODING[c])
{
buf.append('%');
TypeUtil.toHex((byte)c, buf);
}
else
{
buf.append(c);
}
}
return buf.toString();
}
private static String encodePathBytes(String path)
{
StringBuilder buf = new StringBuilder(path.length() * 2);
byte[] pathBytes = path.getBytes(StandardCharsets.UTF_8);
for (byte b : pathBytes)
{
if (b < 0 || ENCODE_PATH_NEEDS_ENCODING[b])
{
buf.append('%');
TypeUtil.toHex(b, buf);
}
else
{
buf.append((char)b);
}
}
return buf.toString();
} }
/** /**
@ -483,7 +335,7 @@ public final class URIUtil
* Decode a raw String and convert any specific URI encoded sequences into characters. * Decode a raw String and convert any specific URI encoded sequences into characters.
* *
* @param str input raw string * @param str input raw string
* @param charsToDecode the list of raw characters that need to be decoded (if encountered), leaving all other encoded sequences alone. * @param charsToDecode the list of raw characters that need to be decoded (if encountered), leaving all the other encoded sequences alone.
* @return output with specified characters decoded. * @return output with specified characters decoded.
*/ */
@SuppressWarnings("Duplicates") @SuppressWarnings("Duplicates")
@ -510,41 +362,40 @@ public final class URIUtil
for (int i = idx; i < len; i++) for (int i = idx; i < len; i++)
{ {
char c = str.charAt(i); char c = str.charAt(i);
switch (c) if (c == '%')
{ {
case '%': if ((i + 2) < len)
if ((i + 2) < len) {
char u = str.charAt(i + 1);
char l = str.charAt(i + 2);
char result = (char)(0xff & (TypeUtil.convertHexDigit(u) * 16 + TypeUtil.convertHexDigit(l)));
boolean decoded = false;
for (char f : find)
{ {
char u = str.charAt(i + 1); if (f == result)
char l = str.charAt(i + 2);
char result = (char)(0xff & (TypeUtil.convertHexDigit(u) * 16 + TypeUtil.convertHexDigit(l)));
boolean decoded = false;
for (char f : find)
{ {
if (f == result) ret.append(result);
{ decoded = true;
ret.append(result); break;
decoded = true;
break;
}
}
if (decoded)
{
i += 2;
}
else
{
ret.append(c);
} }
} }
if (decoded)
{
i += 2;
}
else else
{ {
throw new IllegalArgumentException("Bad URI % encoding"); ret.append(c);
} }
break; }
default: else
ret.append(c); {
break; throw new IllegalArgumentException("Bad URI % encoding");
}
}
else
{
ret.append(c);
} }
} }
return ret.toString(); return ret.toString();
@ -553,11 +404,12 @@ public final class URIUtil
/** /**
* Encode a URI path. * Encode a URI path.
* *
* @param path The path the encode * @param path The path to encode
* @param buf StringBuilder to encode path into (or null) * @param buf StringBuilder to encode path into (or null)
* @param encode String of characters to encode. % is always encoded. * @param encode String of characters to encode. '{@code %}' is always encoded.
* @return The StringBuilder or null if no substitutions required. * @return The StringBuilder or null if no substitutions required.
*/ */
// TODO: remove, only used in URIUtilTest?
public static StringBuilder encodeString(StringBuilder buf, public static StringBuilder encodeString(StringBuilder buf,
String path, String path,
String encode) String encode)
@ -730,7 +582,7 @@ public final class URIUtil
{ {
// Allow any 8-bit character (as it's likely unicode). // Allow any 8-bit character (as it's likely unicode).
// or any character labeled with true in __uriSupportedCharacters static // or any character labeled with true in __uriSupportedCharacters static
return (code >= __uriSupportedCharacters.length || __uriSupportedCharacters[code]); return (code >= URI_SUPPORTED_CHARACTERS.length || URI_SUPPORTED_CHARACTERS[code]);
} }
/** /**
@ -784,11 +636,11 @@ public final class URIUtil
* <p> * <p>
* Decode only the safe characters in a URI path and strip parameters of UTF-8 path. * Decode only the safe characters in a URI path and strip parameters of UTF-8 path.
* Safe characters are ones that are not special delimiters and that can be passed to the JVM {@link URI} class. * Safe characters are ones that are not special delimiters and that can be passed to the JVM {@link URI} class.
* Unsafe characters, other than '/' will be encoded. Encodings will be uppercase hex. * Unsafe characters, other than '{@code /}' will be encoded. Encodings will be uppercase hex.
* Canonical paths are also normalized and may be used in string comparisons with other canonical paths. * Canonical paths are also normalized and may be used in string comparisons with other canonical paths.
* <p> * <p>
* For example the path <code>/fo %2fo/b%61r</code> will be normalized to <code>/fo%20%2Fo/bar</code>, * For example the path {@code /fo %2fo/b%61r} will be normalized to {@code /fo%20%2Fo/bar},
* whilst {@link #decodePath(String)} would result in the ambiguous and URI illegal <code>/fo /o/bar</code>. * whilst {@link #decodePath(String)} would result in the ambiguous and URI illegal {@code /fo /o/bar}.
* @return the canonical path or null if it is non-normal * @return the canonical path or null if it is non-normal
* @see #decodePath(String) * @see #decodePath(String)
* @see #normalizePath(String) * @see #normalizePath(String)
@ -917,7 +769,8 @@ public final class URIUtil
} }
} }
/* Decode a URI path and strip parameters of ISO-8859-1 path /**
* Decode a URI path and strip parameters of ISO-8859-1 path
*/ */
private static String decodeISO88591Path(String path, int offset, int length) private static String decodeISO88591Path(String path, int offset, int length)
{ {
@ -989,7 +842,7 @@ public final class URIUtil
/** /**
* Add two encoded URI path segments. * Add two encoded URI path segments.
* Handles null and empty paths, path and query params * Handles null and empty paths, path and query params
* (eg ?a=b or ;JSESSIONID=xxx) and avoids duplicate '/' * (e.g. {@code ?a=b} or {@code ;JSESSIONID=xxx}) and avoids duplicate '{@code /}'
* *
* @param p1 URI path segment (should be encoded) * @param p1 URI path segment (should be encoded)
* @param p2 URI path segment (should be encoded) * @param p2 URI path segment (should be encoded)
@ -1019,7 +872,7 @@ public final class URIUtil
if (buf.charAt(split - 1) == '/') if (buf.charAt(split - 1) == '/')
{ {
if (p2.startsWith(URIUtil.SLASH)) if (p2.startsWith("/"))
{ {
buf.deleteCharAt(split - 1); buf.deleteCharAt(split - 1);
buf.insert(split - 1, p2); buf.insert(split - 1, p2);
@ -1029,7 +882,7 @@ public final class URIUtil
} }
else else
{ {
if (p2.startsWith(URIUtil.SLASH)) if (p2.startsWith("/"))
buf.insert(split, p2); buf.insert(split, p2);
else else
{ {
@ -1043,8 +896,10 @@ public final class URIUtil
/** /**
* Add two Decoded URI path segments. * Add two Decoded URI path segments.
* Handles null and empty paths. Path and query params (eg ?a=b or * <p>
* ;JSESSIONID=xxx) are not handled * Handles null and empty paths.
* Path and query params (e.g. {@code ?a=b} or {@code ;JSESSIONID=xxx}) are not handled
* </p>
* *
* @param p1 URI path segment (should be decoded) * @param p1 URI path segment (should be decoded)
* @param p2 URI path segment (should be decoded) * @param p2 URI path segment (should be decoded)
@ -1061,8 +916,8 @@ public final class URIUtil
if (p2 == null || p2.length() == 0) if (p2 == null || p2.length() == 0)
return p1; return p1;
boolean p1EndsWithSlash = p1.endsWith(SLASH); boolean p1EndsWithSlash = p1.endsWith("/");
boolean p2StartsWithSlash = p2.startsWith(SLASH); boolean p2StartsWithSlash = p2.startsWith("/");
if (p1EndsWithSlash && p2StartsWithSlash) if (p1EndsWithSlash && p2StartsWithSlash)
{ {
@ -1075,25 +930,27 @@ public final class URIUtil
StringBuilder buf = new StringBuilder(p1.length() + p2.length() + 2); StringBuilder buf = new StringBuilder(p1.length() + p2.length() + 2);
buf.append(p1); buf.append(p1);
if (p1.endsWith(SLASH)) if (p1.endsWith("/"))
{ {
if (p2.startsWith(SLASH)) if (p2.startsWith("/"))
buf.setLength(buf.length() - 1); buf.setLength(buf.length() - 1);
} }
else else
{ {
if (!p2.startsWith(SLASH)) if (!p2.startsWith("/"))
buf.append(SLASH); buf.append("/");
} }
buf.append(p2); buf.append(p2);
return buf.toString(); return buf.toString();
} }
/** Add a path and a query string /**
* Add a path and a query string
*
* @param path The path which may already contain a query * @param path The path which may already contain a query
* @param query The query string to add (if blank, no query is added) * @param query The query string to add (if blank, no query is added)
* @return The path with any non-blank query added after a '?' or '&amp;' as appropriate. * @return The path with any non-blank query added after a '{@code ?}' or '{@code &amp;}' as appropriate.
*/ */
public static String addPathQuery(String path, String query) public static String addPathQuery(String path, String query)
{ {
@ -1142,14 +999,16 @@ public final class URIUtil
/** /**
* Return the parent Path. * Return the parent Path.
* <p>
* Treat a URI like a directory path and return the parent directory. * Treat a URI like a directory path and return the parent directory.
* </p>
* *
* @param p the path to return a parent reference to * @param p the path to return a parent reference to
* @return the parent path of the URI * @return the parent path of the URI
*/ */
public static String parentPath(String p) public static String parentPath(String p)
{ {
if (p == null || URIUtil.SLASH.equals(p)) if (p == null || "/".equals(p))
return null; return null;
int slash = p.lastIndexOf('/', p.length() - 2); int slash = p.lastIndexOf('/', p.length() - 2);
if (slash >= 0) if (slash >= 0)
@ -1158,7 +1017,8 @@ public final class URIUtil
} }
/** /**
* <p>Normalize a URI path and query by factoring out all segments of "." and ".." * <p>
* Normalize a URI path and query by factoring out all segments of '{@code .}' and '{@code ..}'
* up until any query or fragment. * up until any query or fragment.
* Null is returned if the path is normalized above its root. * Null is returned if the path is normalized above its root.
* </p> * </p>
@ -1189,7 +1049,6 @@ public final class URIUtil
case '.': case '.':
if (slash) if (slash)
break loop; break loop;
slash = false;
break; break;
case '?': case '?':
@ -1267,8 +1126,8 @@ public final class URIUtil
/** /**
* <p>Check if a path would be normalized within itself. For example, * <p>Check if a path would be normalized within itself. For example,
* <code>/foo/../../bar</code> is normalized above its root and would * {@code /foo/../../bar} is normalized above its root and would
* thus return false, whilst <code>/foo/./bar/..</code> is normal within itself * thus return false, whilst {@code /foo/./bar/..} is normal within itself
* and would return true. * and would return true.
* @param path The path to check * @param path The path to check
* @return True if the normal form of the path is within the root of the path. * @return True if the normal form of the path is within the root of the path.
@ -1280,11 +1139,11 @@ public final class URIUtil
} }
/** /**
* <p>Normalize a URI path by factoring out all segments of "." and "..". * <p>Normalize a URI path by factoring out all segments of {@code .} and {@code ..}.
* Null is returned if the path is normalized above its root. * Null is returned if the path is normalized above its root.
* </p> * </p>
* *
* @param path the decoded URI path to convert. Any special characters (e.g. '?', "#") are assumed to be part of * @param path the decoded URI path to convert. Any special characters (e.g. {@code ?}, {@code #}) are assumed to be part of
* the path segments. * the path segments.
* @return the normalized path, or null if path traversal above root. * @return the normalized path, or null if path traversal above root.
* @see #normalizePathQuery(String) * @see #normalizePathQuery(String)
@ -1306,18 +1165,13 @@ public final class URIUtil
char c = path.charAt(i); char c = path.charAt(i);
switch (c) switch (c)
{ {
case '/': case '/' -> slash = true;
slash = true; case '.' ->
break; {
case '.':
if (slash) if (slash)
break loop; break loop;
slash = false; }
break; default -> slash = false;
default:
slash = false;
} }
i++; i++;
@ -1339,14 +1193,15 @@ public final class URIUtil
char c = path.charAt(i); char c = path.charAt(i);
switch (c) switch (c)
{ {
case '/': case '/' ->
{
if (doDotsSlash(canonical, dots)) if (doDotsSlash(canonical, dots))
return null; return null;
slash = true; slash = true;
dots = 0; dots = 0;
break; }
case '.' ->
case '.': {
// Count dots only if they are leading in the segment // Count dots only if they are leading in the segment
if (dots > 0) if (dots > 0)
dots++; dots++;
@ -1355,15 +1210,16 @@ public final class URIUtil
else else
canonical.append('.'); canonical.append('.');
slash = false; slash = false;
break; }
default ->
default: {
// Add leading dots to the path // Add leading dots to the path
while (dots-- > 0) while (dots-- > 0)
canonical.append('.'); canonical.append('.');
canonical.append(c); canonical.append(c);
dots = 0; dots = 0;
slash = false; slash = false;
}
} }
i++; i++;
} }
@ -1420,7 +1276,7 @@ public final class URIUtil
/** /**
* Convert a path to a compact form. * Convert a path to a compact form.
* All instances of "//" and "///" etc. are factored out to single "/" * All instances of {@code //} and {@code ///} etc. are factored out to single {@code /}
* *
* @param path the path to compact * @param path the path to compact
* @return the compacted path * @return the compacted path
@ -1582,47 +1438,36 @@ public final class URIUtil
*/ */
public static void appendSchemeHostPort(StringBuffer url, String scheme, String server, int port) public static void appendSchemeHostPort(StringBuffer url, String scheme, String server, int port)
{ {
synchronized (url) url.append(scheme).append("://").append(HostPort.normalizeHost(server));
if (port > 0)
{ {
url.append(scheme).append("://").append(HostPort.normalizeHost(server)); switch (scheme)
if (port > 0)
{ {
switch (scheme) case "http":
{ if (port != 80)
case "http":
if (port != 80)
url.append(':').append(port);
break;
case "https":
if (port != 443)
url.append(':').append(port);
break;
default:
url.append(':').append(port); url.append(':').append(port);
} break;
case "https":
if (port != 443)
url.append(':').append(port);
break;
default:
url.append(':').append(port);
} }
} }
} }
// Only URIUtil is using this method /**
static boolean equalsIgnoreEncodings(String uriA, String uriB) * Encode characters in a path to ensure they only contain safe encodings suitable for both
{ * {@link URI} and {@link Paths#get(URI)} usage.
try *
{ * @param path the path to encode
String safeDecodedUriA = ensureSafeEncoding(uriA); * @return the returned path with only safe encodings
String safeDecodedUriB = ensureSafeEncoding(uriB); */
return safeDecodedUriA.equals(safeDecodedUriB); public static String encodePathSafeEncoding(String path)
}
catch (IllegalArgumentException e)
{
return false;
}
}
static String ensureSafeEncoding(String path)
{ {
if (path == null) if (path == null)
return null; return null;
@ -1762,42 +1607,6 @@ public final class URIUtil
return ((codepoint == '?') || (codepoint == '#')); return ((codepoint == '?') || (codepoint == '#'));
} }
public static boolean equalsIgnoreEncodings(URI uriA, URI uriB)
{
if (uriA.equals(uriB))
return true;
if (uriA.toASCIIString().equals(uriB.toASCIIString()))
return true;
// TODO: this check occurs in uriA.equals(uriB)
if (uriA.getScheme() == null)
{
if (uriB.getScheme() != null)
return false;
}
else if (!uriA.getScheme().equalsIgnoreCase(uriB.getScheme()))
return false;
if ("jar".equalsIgnoreCase(uriA.getScheme()))
{
// at this point we know that both uri's are "jar:"
URI uriAssp = URI.create(uriA.getRawSchemeSpecificPart());
URI uriBssp = URI.create(uriB.getRawSchemeSpecificPart());
return equalsIgnoreEncodings(uriAssp, uriBssp);
}
if (uriA.getAuthority() == null)
{
if (uriB.getAuthority() != null)
return false;
}
else if (!uriA.getAuthority().equals(uriB.getAuthority()))
return false;
return equalsIgnoreEncodings(uriA.getRawPath(), uriB.getRawPath());
}
/** /**
* Add a sub path to an existing URI. * Add a sub path to an existing URI.
* *
@ -1828,7 +1637,7 @@ public final class URIUtil
// ensure that the base has a safe encoding suitable for both // ensure that the base has a safe encoding suitable for both
// URI and Paths.get(URI) later usage // URI and Paths.get(URI) later usage
path = ensureSafeEncoding(path); path = encodePathSafeEncoding(path);
pathLen = path.length(); pathLen = path.length();
if (base.length() == 0) if (base.length() == 0)
@ -1847,8 +1656,8 @@ public final class URIUtil
} }
/** /**
* Combine two query strings into one. Each query string should not contain the beginning '?' character, but * Combine two query strings into one. Each query string should not contain the beginning '{@code ?}' character, but
* may contain multiple parameters separated by the '{@literal &}' character. * may contain multiple parameters separated by the '{@code &}' character.
* @param query1 the first query string. * @param query1 the first query string.
* @param query2 the second query string. * @param query2 the second query string.
* @return the combination of the two query strings. * @return the combination of the two query strings.
@ -1911,12 +1720,12 @@ public final class URIUtil
} }
/** /**
* Split a string of references, that may be split with {@code ,}, or {@code ;}, or {@code |} into URIs. * Split a string of references, that may be split with '{@code ,}', or '{@code ;}', or '{@code |}' into URIs.
* <p> * <p>
* Each part of the input string could be path references (unix or windows style), or string URI references. * Each part of the input string could be path references (unix or windows style), or string URI references.
* </p> * </p>
* <p> * <p>
* If the result of processing the input segment is a java archive, then its resulting URI will be a mountable URI as `jar:file:...!/`. * If the result of processing the input segment is a java archive, then its resulting URI will be a mountable URI as {@code jar:file:...!/}
* </p> * </p>
* *
* @param str the input string of references * @param str the input string of references
@ -1973,12 +1782,15 @@ public final class URIUtil
} }
/** /**
* <p>
* Take an arbitrary URI and provide a URI that is suitable for mounting the URI as a Java FileSystem. * Take an arbitrary URI and provide a URI that is suitable for mounting the URI as a Java FileSystem.
* * </p>
* <p>
* The resulting URI will point to the {@code jar:file://foo.jar!/} said Java Archive (jar, war, or zip) * The resulting URI will point to the {@code jar:file://foo.jar!/} said Java Archive (jar, war, or zip)
* </p>
* *
* @param uri the URI to mutate to a {@code jar:file:...} URI. * @param uri the URI to mutate to a {@code jar:file:...} URI.
* @return the <code>jar:${uri_to_java_archive}!/${internal-reference}</code> URI or the unchanged URI if not a Java Archive. * @return the {@code jar:${uri_to_java_archive}!/${internal-reference}} URI or the unchanged URI if not a Java Archive.
* @see FileID#isArchive(URI) * @see FileID#isArchive(URI)
*/ */
public static URI toJarFileUri(URI uri) public static URI toJarFileUri(URI uri)
@ -2034,9 +1846,13 @@ public final class URIUtil
} }
/** /**
* <p>
* Unwrap a URI to expose its container path reference. * Unwrap a URI to expose its container path reference.
* </p>
* *
* <p>
* Take out the container archive name URI from a {@code jar:file:${container-name}!/} URI. * Take out the container archive name URI from a {@code jar:file:${container-name}!/} URI.
* </p>
* *
* @param uri the input URI * @param uri the input URI
* @return the container String if a {@code jar} scheme, or just the URI untouched. * @return the container String if a {@code jar} scheme, or just the URI untouched.

View File

@ -131,7 +131,7 @@ public class CombinedResource extends Resource
if (URIUtil.isNotNormalWithinSelf(subUriPath)) if (URIUtil.isNotNormalWithinSelf(subUriPath))
throw new IllegalArgumentException(subUriPath); throw new IllegalArgumentException(subUriPath);
if (subUriPath.length() == 0 || URIUtil.SLASH.equals(subUriPath)) if (subUriPath.length() == 0 || "/".equals(subUriPath))
{ {
return this; return this;
} }

View File

@ -182,8 +182,8 @@ public class PathResource extends Resource
if (Files.isDirectory(path)) if (Files.isDirectory(path))
{ {
String uriString = uri.toASCIIString(); String uriString = uri.toASCIIString();
if (!uriString.endsWith(URIUtil.SLASH)) if (!uriString.endsWith("/"))
uri = URIUtil.correctFileURI(URI.create(uriString + URIUtil.SLASH)); uri = URIUtil.correctFileURI(URI.create(uriString + "/"));
} }
this.path = path; this.path = path;
@ -282,7 +282,7 @@ public class PathResource extends Resource
if (URIUtil.isNotNormalWithinSelf(subUriPath)) if (URIUtil.isNotNormalWithinSelf(subUriPath))
throw new IllegalArgumentException(subUriPath); throw new IllegalArgumentException(subUriPath);
if (URIUtil.SLASH.equals(subUriPath)) if ("/".equals(subUriPath))
return this; return this;
URI uri = getURI(); URI uri = getURI();

View File

@ -66,7 +66,9 @@ public class URIUtilTest
Arguments.of("/context/'list'/\"me\"/;<script>window.alert('xss');</script>", Arguments.of("/context/'list'/\"me\"/;<script>window.alert('xss');</script>",
"/context/%27list%27/%22me%22/%3B%3Cscript%3Ewindow.alert(%27xss%27)%3B%3C/script%3E"), "/context/%27list%27/%22me%22/%3B%3Cscript%3Ewindow.alert(%27xss%27)%3B%3C/script%3E"),
Arguments.of("test\u00f6?\u00f6:\u00df", "test%C3%B6%3F%C3%B6:%C3%9F"), Arguments.of("test\u00f6?\u00f6:\u00df", "test%C3%B6%3F%C3%B6:%C3%9F"),
Arguments.of("test?\u00f6?\u00f6:\u00df", "test%3F%C3%B6%3F%C3%B6:%C3%9F") Arguments.of("test?\u00f6?\u00f6:\u00df", "test%3F%C3%B6%3F%C3%B6:%C3%9F"),
Arguments.of("/test space/", "/test%20space/"),
Arguments.of("/test\u007fdel/", "/test%7Fdel/")
); );
} }
@ -75,10 +77,8 @@ public class URIUtilTest
public void testEncodePath(String rawPath, String expectedEncoded) public void testEncodePath(String rawPath, String expectedEncoded)
{ {
// test basic encode/decode // test basic encode/decode
StringBuilder buf = new StringBuilder(); String result = URIUtil.encodePath(rawPath);
buf.setLength(0); assertEquals(expectedEncoded, result);
URIUtil.encodePath(buf, rawPath);
assertEquals(expectedEncoded, buf.toString());
} }
@Test @Test
@ -565,7 +565,7 @@ public class URIUtilTest
assertThat(actual, is(expected)); assertThat(actual, is(expected));
} }
public static Stream<Arguments> ensureSafeEncodingSource() public static Stream<Arguments> encodePathSafeEncodingSource()
{ {
return Stream.of( return Stream.of(
Arguments.of("/foo", "/foo"), Arguments.of("/foo", "/foo"),
@ -589,10 +589,10 @@ public class URIUtilTest
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("ensureSafeEncodingSource") @MethodSource("encodePathSafeEncodingSource")
public void testEnsureSafeEncoding(String input, String expected) public void testEncodePathSafeEncoding(String input, String expected)
{ {
assertThat(URIUtil.ensureSafeEncoding(input), is(expected)); assertThat(URIUtil.encodePathSafeEncoding(input), is(expected));
} }
public static Stream<Arguments> compactPathSource() public static Stream<Arguments> compactPathSource()
@ -637,139 +637,6 @@ public class URIUtilTest
assertEquals(expectedPath, actual, String.format("parent %s", path)); assertEquals(expectedPath, actual, String.format("parent %s", path));
} }
public static Stream<Arguments> equalsIgnoreEncodingStringTrueSource()
{
return Stream.of(
Arguments.of("http://example.com/foo/bar", "http://example.com/foo/bar"),
Arguments.of("/barry%27s", "/barry%27s"),
Arguments.of("/b rry%27s", "/b%20rry%27s"),
Arguments.of("/barry's", "/barry%27s"),
Arguments.of("/barry%27s", "/barry's"),
Arguments.of("/b rry's", "/b%20rry%27s"),
Arguments.of("/b rry%27s", "/b%20rry's"),
Arguments.of("/re bar", "/re%20bar"),
Arguments.of("/foo%2fbar", "/foo%2fbar"),
Arguments.of("/foo%2fbar", "/foo%2Fbar"),
// encoded vs not-encode ("%" symbol is encoded as "%25")
Arguments.of("/abc%25xyz", "/abc%xyz"),
Arguments.of("/abc%25xy", "/abc%xy"),
Arguments.of("/abc%25x", "/abc%x"),
Arguments.of("/zzz%25", "/zzz%"),
// unicode encoded vs not-encoded
Arguments.of("/path/to/bä€ãm/", "/path/to/b%C3%A4%E2%82%AC%C3%A3m/")
);
}
@ParameterizedTest
@MethodSource("equalsIgnoreEncodingStringTrueSource")
public void testEqualsIgnoreEncodingStringTrue(String uriA, String uriB)
{
assertTrue(URIUtil.equalsIgnoreEncodings(uriA, uriB));
}
public static Stream<Arguments> equalsIgnoreEncodingStringFalseSource()
{
return Stream.of(
// case difference
Arguments.of("ABC", "abc"),
// Encoding difference ("'" is "%27")
Arguments.of("/barry's", "/barry%26s"),
// Never match on "%2f" differences - only intested in filename / directory name differences
// This could be a directory called "foo" with a file called "bar" on the left, and just a file "foo%2fbar" on the right
Arguments.of("/foo/bar", "/foo%2fbar"),
// not actually encoded
Arguments.of("/foo2fbar", "/foo/bar"),
// path params
Arguments.of("/path;a=b/to;x=y/foo/", "/path/to/foo"),
// encoded vs not-encode ("%" symbol is encoded as "%25")
Arguments.of("/yyy%25zzz", "/aaa%xxx"),
Arguments.of("/zzz%25", "/aaa%"),
// %2F then multi-byte unicode
Arguments.of("/path/to/bãm/", "/path%2Fto/b%C3%A3m/"),
// multi-byte unicode then %2F
Arguments.of("/path/bãm/or/bust", "/path/b%C3%A3m/or%2Fbust"),
// mix of %2F and multiple consecutive multi-byte unicode
Arguments.of("/path/to/bä€ãm/", "/path%2Fto/b%C3%A4%E2%82%AC%C3%A3m/")
);
}
@ParameterizedTest
@MethodSource("equalsIgnoreEncodingStringFalseSource")
public void testEqualsIgnoreEncodingStringFalse(String uriA, String uriB)
{
assertFalse(URIUtil.equalsIgnoreEncodings(uriA, uriB));
}
public static Stream<Arguments> equalsIgnoreEncodingURITrueSource()
{
return Stream.of(
Arguments.of(
URI.create("HTTP:/foo/b%61r"),
URI.create("http:/f%6Fo/bar")
),
Arguments.of(
URI.create("jar:file:/path/to/main.jar!/META-INF/versions/"),
URI.create("jar:file:/path/to/main.jar!/META-INF/%76ersions/")
),
Arguments.of(
URI.create("JAR:FILE:/path/to/main.jar!/META-INF/versions/"),
URI.create("jar:file:/path/to/main.jar!/META-INF/versions/")
),
// unicode in opaque jar:file: URI
Arguments.of(
URI.create("jar:file:///path/to/test.jar!/bãm/"),
URI.create("jar:file:///path/to/test.jar!/b%C3%A3m/")
),
// multiple consecutive unicode
Arguments.of(
URI.create("file:///path/to/bä€ãm/"),
URI.create("file:///path/to/b%C3%A4%E2%82%AC%C3%A3m/")
)
);
}
@ParameterizedTest
@MethodSource("equalsIgnoreEncodingURITrueSource")
public void testEqualsIgnoreEncodingURITrue(URI uriA, URI uriB)
{
assertTrue(URIUtil.equalsIgnoreEncodings(uriA, uriB));
}
public static Stream<Arguments> equalsIgnoreEncodingURIFalseSource()
{
return Stream.of(
Arguments.of(
URI.create("/foo%2Fbar"),
URI.create("/foo/bar")
),
// %2F then unicode
Arguments.of(
URI.create("file:///path/to/bãm/"),
URI.create("file:///path%2Fto/b%C3%A3m/")
),
// unicode then %2F
Arguments.of(
URI.create("file:///path/bãm/or/bust"),
URI.create("file:///path/b%C3%A3m/or%2Fbust")
),
// mix of %2F and multiple consecutive unicode
Arguments.of(
URI.create("file:///path/to/bä€ãm/"),
URI.create("file:///path%2Fto/b%C3%A4%E2%82%AC%C3%A3m/")
)
);
}
@ParameterizedTest
@MethodSource("equalsIgnoreEncodingURIFalseSource")
public void testEqualsIgnoreEncodingURIFalse(URI uriA, URI uriB)
{
assertFalse(URIUtil.equalsIgnoreEncodings(uriA, uriB));
}
public static Stream<Arguments> correctBadFileURICases() public static Stream<Arguments> correctBadFileURICases()
{ {
return Stream.of( return Stream.of(
@ -811,17 +678,18 @@ public class URIUtilTest
} }
@Test @Test
public void testCorrectBadFileURIActualFile() throws Exception public void testCorrectBadFileURIActualFile(WorkDir workDir) throws Exception
{ {
File file = MavenTestingUtils.getTargetFile("testCorrectBadFileURIActualFile.txt"); Path testfile = workDir.getEmptyPathDir().resolve("testCorrectBadFileURIActualFile.txt");
FS.touch(file); FS.touch(testfile);
URI expectedUri = file.toPath().toUri(); URI expectedUri = testfile.toUri(); // correct URI with `://`
assertThat(expectedUri.toASCIIString(), containsString("://")); assertThat(expectedUri.toASCIIString(), containsString("://"));
URI fileUri = file.toURI(); File file = testfile.toFile();
URI fileUrlUri = file.toURL().toURI(); URI fileUri = file.toURI(); // java produced bad format with only `:/` (not `://`)
URI fileUrlUri = file.toURL().toURI(); // java produced bad format with only `:/` (not `://`)
// If these 2 tests start failing, that means Java itself has been fixed // If these 2 tests start failing, that means Java itself has been fixed
assertThat(fileUri.toASCIIString(), not(containsString("://"))); assertThat(fileUri.toASCIIString(), not(containsString("://")));
@ -831,29 +699,6 @@ public class URIUtilTest
assertThat(URIUtil.correctFileURI(fileUrlUri).toASCIIString(), is(expectedUri.toASCIIString())); assertThat(URIUtil.correctFileURI(fileUrlUri).toASCIIString(), is(expectedUri.toASCIIString()));
} }
public static Stream<Arguments> encodeSpacesSource()
{
return Stream.of(
// null
Arguments.of(null, null),
// no spaces
Arguments.of("abc", "abc"),
// match
Arguments.of("a c", "a%20c"),
Arguments.of(" ", "%20%20%20"),
Arguments.of("a%20space", "a%20space")
);
}
@ParameterizedTest
@MethodSource("encodeSpacesSource")
public void testEncodeSpaces(String raw, String expected)
{
assertThat(URIUtil.encodeSpaces(raw), is(expected));
}
public static Stream<Arguments> encodeSpecific() public static Stream<Arguments> encodeSpecific()
{ {
return Stream.of( return Stream.of(
@ -1040,12 +885,12 @@ public class URIUtilTest
builder.append(i); builder.append(i);
String path = builder.toString(); String path = builder.toString();
String encoded = URIUtil.encodePath(path); String encoded = URIUtil.encodePath(path);
// Check endoded is visible // Check encoded is visible
for (char c : encoded.toCharArray()) for (char c : encoded.toCharArray())
{ {
assertTrue(c > 0x20 && c < 0x80); assertTrue(c > 0x20 && c < 0x7f);
assertFalse(Character.isWhitespace(c)); assertFalse(Character.isWhitespace(c));
assertFalse(Character.isISOControl(c)); assertFalse(Character.isISOControl(c), "isISOControl(0x%2x)".formatted((byte)c));
} }
// check decode to original // check decode to original
String decoded = URIUtil.decodePath(encoded); String decoded = URIUtil.decodePath(encoded);

View File

@ -389,7 +389,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
String uri = request.getRequestURI(); String uri = request.getRequestURI();
if (uri == null) if (uri == null)
uri = URIUtil.SLASH; uri = "/";
mandatory |= isJSecurityCheck(uri); mandatory |= isJSecurityCheck(uri);
if (!mandatory) if (!mandatory)

View File

@ -786,7 +786,7 @@ public class ServletContextHandler extends ContextHandler implements Graceful
*/ */
public Resource getResource(String pathInContext) throws MalformedURLException public Resource getResource(String pathInContext) throws MalformedURLException
{ {
if (pathInContext == null || !pathInContext.startsWith(URIUtil.SLASH)) if (pathInContext == null || !pathInContext.startsWith("/"))
throw new MalformedURLException(pathInContext); throw new MalformedURLException(pathInContext);
Resource baseResource = getBaseResource(); Resource baseResource = getBaseResource();
@ -838,8 +838,8 @@ public class ServletContextHandler extends ContextHandler implements Graceful
{ {
Resource resource = getResource(path); Resource resource = getResource(path);
if (!path.endsWith(URIUtil.SLASH)) if (!path.endsWith("/"))
path = path + URIUtil.SLASH; path = path + "/";
HashSet<String> set = new HashSet<>(); HashSet<String> set = new HashSet<>();
for (Resource item: resource.list()) for (Resource item: resource.list())
@ -2889,9 +2889,9 @@ public class ServletContextHandler extends ContextHandler implements Graceful
if (path == null) if (path == null)
return null; return null;
if (path.length() == 0) if (path.length() == 0)
path = URIUtil.SLASH; path = "/";
else if (path.charAt(0) != '/') else if (path.charAt(0) != '/')
path = URIUtil.SLASH + path; path = "/" + path;
try try
{ {

View File

@ -253,7 +253,7 @@ public class FormAuthenticator extends LoginAuthenticator
{ {
nuri = servletApiRequest.getContextPath(); nuri = servletApiRequest.getContextPath();
if (nuri.length() == 0) if (nuri.length() == 0)
nuri = URIUtil.SLASH; nuri = "/";
} }
else else
nuri = savedURI.asString(); nuri = savedURI.asString();

View File

@ -378,7 +378,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
@Override @Override
public Resource getResource(String pathInContext) throws MalformedURLException public Resource getResource(String pathInContext) throws MalformedURLException
{ {
if (pathInContext == null || !pathInContext.startsWith(URIUtil.SLASH)) if (pathInContext == null || !pathInContext.startsWith("/"))
throw new MalformedURLException(pathInContext); throw new MalformedURLException(pathInContext);
MalformedURLException mue = null; MalformedURLException mue = null;

View File

@ -1384,7 +1384,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
*/ */
public Resource getResource(String pathInContext) throws MalformedURLException public Resource getResource(String pathInContext) throws MalformedURLException
{ {
if (pathInContext == null || !pathInContext.startsWith(URIUtil.SLASH)) if (pathInContext == null || !pathInContext.startsWith("/"))
throw new MalformedURLException(pathInContext); throw new MalformedURLException(pathInContext);
Resource baseResource = _coreContextHandler.getBaseResource(); Resource baseResource = _coreContextHandler.getBaseResource();
@ -1476,8 +1476,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{ {
Resource resource = getResource(path); Resource resource = getResource(path);
if (!path.endsWith(URIUtil.SLASH)) if (!path.endsWith("/"))
path = path + URIUtil.SLASH; path = path + "/";
HashSet<String> set = new HashSet<>(); HashSet<String> set = new HashSet<>();
@ -1814,9 +1814,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (path == null) if (path == null)
return null; return null;
if (path.length() == 0) if (path.length() == 0)
path = URIUtil.SLASH; path = "/";
else if (path.charAt(0) != '/') else if (path.charAt(0) != '/')
path = URIUtil.SLASH + path; path = "/" + path;
try try
{ {

View File

@ -233,7 +233,7 @@ public class ResourceService
String pathInContext = URIUtil.addPaths(servletPath, pathInfo); String pathInContext = URIUtil.addPaths(servletPath, pathInfo);
boolean endsWithSlash = (pathInfo == null ? (_pathInfoOnly ? "" : servletPath) : pathInfo).endsWith(URIUtil.SLASH); boolean endsWithSlash = (pathInfo == null ? (_pathInfoOnly ? "" : servletPath) : pathInfo).endsWith("/");
boolean checkPrecompressedVariants = _precompressedFormats.length > 0 && !endsWithSlash && !included && reqRanges == null; boolean checkPrecompressedVariants = _precompressedFormats.length > 0 && !endsWithSlash && !included && reqRanges == null;
HttpContent content = null; HttpContent content = null;
@ -650,7 +650,7 @@ public class ResourceService
} }
byte[] data = null; byte[] data = null;
String base = URIUtil.addEncodedPaths(request.getRequestURI(), URIUtil.SLASH); String base = URIUtil.addEncodedPaths(request.getRequestURI(), "/");
String dir = ResourceListing.getAsXHTML(resource, base, pathInContext.length() > 1, request.getQueryString()); String dir = ResourceListing.getAsXHTML(resource, base, pathInContext.length() > 1, request.getQueryString());
if (dir == null) if (dir == null)
{ {

View File

@ -378,7 +378,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
String uri = request.getRequestURI(); String uri = request.getRequestURI();
if (uri == null) if (uri == null)
uri = URIUtil.SLASH; uri = "/";
mandatory |= isJSecurityCheck(uri); mandatory |= isJSecurityCheck(uri);
if (!mandatory) if (!mandatory)

View File

@ -242,7 +242,7 @@ public class FormAuthenticator extends LoginAuthenticator
String uri = request.getRequestURI(); String uri = request.getRequestURI();
if (uri == null) if (uri == null)
uri = URIUtil.SLASH; uri = "/";
mandatory |= isJSecurityCheck(uri); mandatory |= isJSecurityCheck(uri);
if (!mandatory) if (!mandatory)
@ -275,7 +275,7 @@ public class FormAuthenticator extends LoginAuthenticator
{ {
nuri = request.getContextPath(); nuri = request.getContextPath();
if (nuri.length() == 0) if (nuri.length() == 0)
nuri = URIUtil.SLASH; nuri = "/";
} }
formAuth = new FormAuthentication(getAuthMethod(), user); formAuth = new FormAuthentication(getAuthMethod(), user);
} }

View File

@ -387,7 +387,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
@Override @Override
public Resource getResource(String pathInContext) throws MalformedURLException public Resource getResource(String pathInContext) throws MalformedURLException
{ {
if (pathInContext == null || !pathInContext.startsWith(URIUtil.SLASH)) if (pathInContext == null || !pathInContext.startsWith("/"))
throw new MalformedURLException(pathInContext); throw new MalformedURLException(pathInContext);
MalformedURLException mue = null; MalformedURLException mue = null;