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();
try
@@ -552,7 +552,7 @@ private void sendDirectory(Request request, Response response, HttpContent httpC
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());
if (listing == null)
{
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
index ce4540d0aa60..be9e28f6a2fc 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
@@ -18,7 +18,6 @@
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
-import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -55,17 +54,10 @@ public final class URIUtil
.with("jar:")
.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)}
*/
- public static final boolean[] __uriSupportedCharacters = new boolean[]
+ private static final boolean[] URI_SUPPORTED_CHARACTERS = new boolean[]
{
false, // 0x00 is illegal
false, // 0x01 is illegal
@@ -197,242 +189,102 @@ public final class URIUtil
false, // 0x7f DEL is illegal
};
+ private static final boolean[] ENCODE_PATH_NEEDS_ENCODING;
+
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.
* 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
*/
public static String encodePath(String path)
{
- if (path == null || path.length() == 0)
+ if (StringUtil.isEmpty(path))
return path;
- StringBuilder buf = encodePath(null, path, 0);
- return buf == null ? path : buf.toString();
- }
-
- /**
- * 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.
- */
- 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)
+ // byte encoding always wins and, if encountered, should be used.
+ 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();
+ for (int i = 0; i < length; i++)
{
- loop:
- for (int i = offset; i < path.length(); i++)
+ char c = path.charAt(i);
+ if (c > 0x7F) // 8-bit +
{
- 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;
- }
- }
+ needsByteEncoding = true;
+ break; // have to encode byte by byte now
+ }
+ if (ENCODE_PATH_NEEDS_ENCODING[c])
+ {
+ // could be followed by a byte encoding, so no break
+ needsEncoding = true;
}
- if (buf == null)
- return null;
}
- int i;
+ if (needsByteEncoding)
+ return encodePathBytes(path);
+ else if (needsEncoding)
+ return encodePathString(path);
+ else
+ return path;
+ }
- loop:
- for (i = offset; i < path.length(); i++)
+ private static String encodePathString(String path)
+ {
+ StringBuilder buf = new StringBuilder(path.length() * 2);
+ int length = path.length();
+ for (int i = 0; i < length; i++)
{
char c = path.charAt(i);
- switch (c)
+ if (ENCODE_PATH_NEEDS_ENCODING[c])
{
- 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)
- {
- bytes = path.getBytes(URIUtil.__CHARSET);
- break loop;
- }
- buf.append(c);
+ buf.append('%');
+ TypeUtil.toHex((byte)c, buf);
}
- }
-
- if (bytes != null)
- {
- for (; i < bytes.length; i++)
+ else
{
- byte c = bytes[i];
- switch (c)
- {
- 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);
- }
+ buf.append(c);
}
}
-
- return buf;
+ return buf.toString();
}
- /**
- * 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)
+ private static String encodePathBytes(String path)
{
- return StringUtil.replace(str, " ", "%20");
+ 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 static String encodeSpecific(String str, String charsToEncode)
* Decode a raw String and convert any specific URI encoded sequences into characters.
*
* @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.
*/
@SuppressWarnings("Duplicates")
@@ -510,41 +362,40 @@ public static String decodeSpecific(String str, String charsToDecode)
for (int i = idx; i < len; 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);
- 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;
- break;
- }
- }
- if (decoded)
+ if (f == result)
{
- i += 2;
- }
- else
- {
- ret.append(c);
+ ret.append(result);
+ decoded = true;
+ break;
}
}
+ if (decoded)
+ {
+ i += 2;
+ }
else
{
- throw new IllegalArgumentException("Bad URI % encoding");
+ ret.append(c);
}
- break;
- default:
- ret.append(c);
- break;
+ }
+ else
+ {
+ throw new IllegalArgumentException("Bad URI % encoding");
+ }
+ }
+ else
+ {
+ ret.append(c);
}
}
return ret.toString();
@@ -553,11 +404,12 @@ public static String decodeSpecific(String str, String charsToDecode)
/**
* 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 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.
*/
+ // TODO: remove, only used in URIUtilTest?
public static StringBuilder encodeString(StringBuilder buf,
String path,
String encode)
@@ -730,7 +582,7 @@ private static boolean isSafe(int code)
{
// Allow any 8-bit character (as it's likely unicode).
// 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 @@ private static void appendHexValue(Utf8StringBuilder builder, byte value)
*
* 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.
- * 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.
*
- * For example the path /fo %2fo/b%61r
will be normalized to /fo%20%2Fo/bar
,
- * whilst {@link #decodePath(String)} would result in the ambiguous and URI illegal /fo /o/bar
.
+ * 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}.
* @return the canonical path or null if it is non-normal
* @see #decodePath(String)
* @see #normalizePath(String)
@@ -917,7 +769,8 @@ public static String canonicalPath(String path)
}
}
- /* 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)
{
@@ -989,7 +842,7 @@ private static String decodeISO88591Path(String path, int offset, int length)
/**
* Add two encoded URI path segments.
* 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 p2 URI path segment (should be encoded)
@@ -1019,7 +872,7 @@ public static String addEncodedPaths(String p1, String p2)
if (buf.charAt(split - 1) == '/')
{
- if (p2.startsWith(URIUtil.SLASH))
+ if (p2.startsWith("/"))
{
buf.deleteCharAt(split - 1);
buf.insert(split - 1, p2);
@@ -1029,7 +882,7 @@ public static String addEncodedPaths(String p1, String p2)
}
else
{
- if (p2.startsWith(URIUtil.SLASH))
+ if (p2.startsWith("/"))
buf.insert(split, p2);
else
{
@@ -1043,8 +896,10 @@ public static String addEncodedPaths(String p1, String p2)
/**
* Add two Decoded URI path segments.
- * Handles null and empty paths. Path and query params (eg ?a=b or
- * ;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
+ *
*
* @param p1 URI path segment (should be decoded)
* @param p2 URI path segment (should be decoded)
@@ -1061,8 +916,8 @@ public static String addPaths(String p1, String p2)
if (p2 == null || p2.length() == 0)
return p1;
- boolean p1EndsWithSlash = p1.endsWith(SLASH);
- boolean p2StartsWithSlash = p2.startsWith(SLASH);
+ boolean p1EndsWithSlash = p1.endsWith("/");
+ boolean p2StartsWithSlash = p2.startsWith("/");
if (p1EndsWithSlash && p2StartsWithSlash)
{
@@ -1075,25 +930,27 @@ public static String addPaths(String p1, String p2)
StringBuilder buf = new StringBuilder(p1.length() + p2.length() + 2);
buf.append(p1);
- if (p1.endsWith(SLASH))
+ if (p1.endsWith("/"))
{
- if (p2.startsWith(SLASH))
+ if (p2.startsWith("/"))
buf.setLength(buf.length() - 1);
}
else
{
- if (!p2.startsWith(SLASH))
- buf.append(SLASH);
+ if (!p2.startsWith("/"))
+ buf.append("/");
}
buf.append(p2);
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 query The query string to add (if blank, no query is added)
- * @return The path with any non-blank query added after a '?' or '&' as appropriate.
+ * @return The path with any non-blank query added after a '{@code ?}' or '{@code &}' as appropriate.
*/
public static String addPathQuery(String path, String query)
{
@@ -1142,14 +999,16 @@ public static String getUriLastPathSegment(URI uri)
/**
* Return the parent Path.
+ *
* Treat a URI like a directory path and return the parent directory.
+ *
*
* @param p the path to return a parent reference to
* @return the parent path of the URI
*/
public static String parentPath(String p)
{
- if (p == null || URIUtil.SLASH.equals(p))
+ if (p == null || "/".equals(p))
return null;
int slash = p.lastIndexOf('/', p.length() - 2);
if (slash >= 0)
@@ -1158,7 +1017,8 @@ public static String parentPath(String p)
}
/**
- * Normalize a URI path and query by factoring out all segments of "." and ".."
+ *
+ * Normalize a URI path and query by factoring out all segments of '{@code .}' and '{@code ..}'
* up until any query or fragment.
* Null is returned if the path is normalized above its root.
*
@@ -1189,7 +1049,6 @@ public static String normalizePathQuery(String pathQuery)
case '.':
if (slash)
break loop;
- slash = false;
break;
case '?':
@@ -1267,8 +1126,8 @@ else if (slash)
/**
* Check if a path would be normalized within itself. For example,
- * /foo/../../bar
is normalized above its root and would
- * thus return false, whilst /foo/./bar/..
is normal within itself
+ * {@code /foo/../../bar} is normalized above its root and would
+ * thus return false, whilst {@code /foo/./bar/..} is normal within itself
* and would return true.
* @param path The path to check
* @return True if the normal form of the path is within the root of the path.
@@ -1280,11 +1139,11 @@ public static boolean isNotNormalWithinSelf(String path)
}
/**
- *
Normalize a URI path by factoring out all segments of "." and "..".
+ *
Normalize a URI path by factoring out all segments of {@code .} and {@code ..}.
* Null is returned if the path is normalized above its root.
*
*
- * @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.
* @return the normalized path, or null if path traversal above root.
* @see #normalizePathQuery(String)
@@ -1306,18 +1165,13 @@ public static String normalizePath(String path)
char c = path.charAt(i);
switch (c)
{
- case '/':
- slash = true;
- break;
-
- case '.':
+ case '/' -> slash = true;
+ case '.' ->
+ {
if (slash)
break loop;
- slash = false;
- break;
-
- default:
- slash = false;
+ }
+ default -> slash = false;
}
i++;
@@ -1339,14 +1193,15 @@ public static String normalizePath(String path)
char c = path.charAt(i);
switch (c)
{
- case '/':
+ case '/' ->
+ {
if (doDotsSlash(canonical, dots))
return null;
slash = true;
dots = 0;
- break;
-
- case '.':
+ }
+ case '.' ->
+ {
// Count dots only if they are leading in the segment
if (dots > 0)
dots++;
@@ -1355,15 +1210,16 @@ else if (slash)
else
canonical.append('.');
slash = false;
- break;
-
- default:
+ }
+ default ->
+ {
// Add leading dots to the path
while (dots-- > 0)
canonical.append('.');
canonical.append(c);
dots = 0;
slash = false;
+ }
}
i++;
}
@@ -1420,7 +1276,7 @@ private static boolean doDotsSlash(StringBuilder canonical, int dots)
/**
* 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
* @return the compacted path
@@ -1582,47 +1438,36 @@ public static void appendSchemeHostPort(StringBuilder url, String scheme, String
*/
public static void appendSchemeHostPort(StringBuffer url, String scheme, String server, int port)
{
- synchronized (url)
- {
- url.append(scheme).append("://").append(HostPort.normalizeHost(server));
+ url.append(scheme).append("://").append(HostPort.normalizeHost(server));
- if (port > 0)
+ if (port > 0)
+ {
+ switch (scheme)
{
- switch (scheme)
- {
- case "http":
- if (port != 80)
- url.append(':').append(port);
- break;
-
- case "https":
- if (port != 443)
- url.append(':').append(port);
- break;
+ case "http":
+ if (port != 80)
+ url.append(':').append(port);
+ break;
- default:
+ case "https":
+ if (port != 443)
url.append(':').append(port);
- }
- }
- }
- }
+ break;
- // Only URIUtil is using this method
- static boolean equalsIgnoreEncodings(String uriA, String uriB)
- {
- try
- {
- String safeDecodedUriA = ensureSafeEncoding(uriA);
- String safeDecodedUriB = ensureSafeEncoding(uriB);
- return safeDecodedUriA.equals(safeDecodedUriB);
- }
- catch (IllegalArgumentException e)
- {
- return false;
+ default:
+ url.append(':').append(port);
+ }
}
}
- static String ensureSafeEncoding(String path)
+ /**
+ * Encode characters in a path to ensure they only contain safe encodings suitable for both
+ * {@link URI} and {@link Paths#get(URI)} usage.
+ *
+ * @param path the path to encode
+ * @return the returned path with only safe encodings
+ */
+ public static String encodePathSafeEncoding(String path)
{
if (path == null)
return null;
@@ -1762,42 +1607,6 @@ private static boolean mustBeEncoded(int 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.
*
@@ -1828,7 +1637,7 @@ public static URI addPath(URI uri, String path)
// ensure that the base has a safe encoding suitable for both
// URI and Paths.get(URI) later usage
- path = ensureSafeEncoding(path);
+ path = encodePathSafeEncoding(path);
pathLen = path.length();
if (base.length() == 0)
@@ -1847,8 +1656,8 @@ public static URI addPath(URI uri, String path)
}
/**
- * Combine two query strings into one. Each query string should not contain the beginning '?' character, but
- * may contain multiple parameters separated by the '{@literal &}' character.
+ * Combine two query strings into one. Each query string should not contain the beginning '{@code ?}' character, but
+ * may contain multiple parameters separated by the '{@code &}' character.
* @param query1 the first query string.
* @param query2 the second query string.
* @return the combination of the two query strings.
@@ -1911,12 +1720,12 @@ public static URI correctFileURI(URI uri)
}
/**
- * 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.
*
* Each part of the input string could be path references (unix or windows style), or string URI references.
*
*
- * 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:...!/}
*
*
* @param str the input string of references
@@ -1973,12 +1782,15 @@ public static List split(String str)
}
/**
+ *
* Take an arbitrary URI and provide a URI that is suitable for mounting the URI as a Java FileSystem.
- *
+ *
+ *
* The resulting URI will point to the {@code jar:file://foo.jar!/} said Java Archive (jar, war, or zip)
+ *
*
* @param uri the URI to mutate to a {@code jar:file:...} URI.
- * @return the jar:${uri_to_java_archive}!/${internal-reference}
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)
*/
public static URI toJarFileUri(URI uri)
@@ -2034,9 +1846,13 @@ public static URI toURI(String resource)
}
/**
+ *
* Unwrap a URI to expose its container path reference.
+ *
*
+ *
* Take out the container archive name URI from a {@code jar:file:${container-name}!/} URI.
+ *
*
* @param uri the input URI
* @return the container String if a {@code jar} scheme, or just the URI untouched.
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/CombinedResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/CombinedResource.java
index 896f18fbe3b6..e264e06d9d17 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/CombinedResource.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/CombinedResource.java
@@ -131,7 +131,7 @@ public Resource resolve(String subUriPath)
if (URIUtil.isNotNormalWithinSelf(subUriPath))
throw new IllegalArgumentException(subUriPath);
- if (subUriPath.length() == 0 || URIUtil.SLASH.equals(subUriPath))
+ if (subUriPath.length() == 0 || "/".equals(subUriPath))
{
return this;
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java
index dfeb444212d6..f334d2077294 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java
@@ -182,8 +182,8 @@ public static boolean isSameName(Path pathA, Path pathB)
if (Files.isDirectory(path))
{
String uriString = uri.toASCIIString();
- if (!uriString.endsWith(URIUtil.SLASH))
- uri = URIUtil.correctFileURI(URI.create(uriString + URIUtil.SLASH));
+ if (!uriString.endsWith("/"))
+ uri = URIUtil.correctFileURI(URI.create(uriString + "/"));
}
this.path = path;
@@ -282,7 +282,7 @@ public Resource resolve(String subUriPath)
if (URIUtil.isNotNormalWithinSelf(subUriPath))
throw new IllegalArgumentException(subUriPath);
- if (URIUtil.SLASH.equals(subUriPath))
+ if ("/".equals(subUriPath))
return this;
URI uri = getURI();
diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java
index 5dc2cbbd9355..dfba8cb54eca 100644
--- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java
+++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java
@@ -66,7 +66,9 @@ public static Stream encodePathSource()
Arguments.of("/context/'list'/\"me\"/;",
"/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%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 static Stream encodePathSource()
public void testEncodePath(String rawPath, String expectedEncoded)
{
// test basic encode/decode
- StringBuilder buf = new StringBuilder();
- buf.setLength(0);
- URIUtil.encodePath(buf, rawPath);
- assertEquals(expectedEncoded, buf.toString());
+ String result = URIUtil.encodePath(rawPath);
+ assertEquals(expectedEncoded, result);
}
@Test
@@ -565,7 +565,7 @@ public void testAddPathQuery(String path, String query, String expected)
assertThat(actual, is(expected));
}
- public static Stream ensureSafeEncodingSource()
+ public static Stream encodePathSafeEncodingSource()
{
return Stream.of(
Arguments.of("/foo", "/foo"),
@@ -589,10 +589,10 @@ public static Stream ensureSafeEncodingSource()
}
@ParameterizedTest
- @MethodSource("ensureSafeEncodingSource")
- public void testEnsureSafeEncoding(String input, String expected)
+ @MethodSource("encodePathSafeEncodingSource")
+ public void testEncodePathSafeEncoding(String input, String expected)
{
- assertThat(URIUtil.ensureSafeEncoding(input), is(expected));
+ assertThat(URIUtil.encodePathSafeEncoding(input), is(expected));
}
public static Stream compactPathSource()
@@ -637,139 +637,6 @@ public void testParentPath(String path, String expectedPath)
assertEquals(expectedPath, actual, String.format("parent %s", path));
}
- public static Stream 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 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 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 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 correctBadFileURICases()
{
return Stream.of(
@@ -811,17 +678,18 @@ public void testCorrectFileURI(String input, String expected)
}
@Test
- public void testCorrectBadFileURIActualFile() throws Exception
+ public void testCorrectBadFileURIActualFile(WorkDir workDir) throws Exception
{
- File file = MavenTestingUtils.getTargetFile("testCorrectBadFileURIActualFile.txt");
- FS.touch(file);
+ Path testfile = workDir.getEmptyPathDir().resolve("testCorrectBadFileURIActualFile.txt");
+ FS.touch(testfile);
- URI expectedUri = file.toPath().toUri();
+ URI expectedUri = testfile.toUri(); // correct URI with `://`
assertThat(expectedUri.toASCIIString(), containsString("://"));
- URI fileUri = file.toURI();
- URI fileUrlUri = file.toURL().toURI();
+ File file = testfile.toFile();
+ 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
assertThat(fileUri.toASCIIString(), not(containsString("://")));
@@ -831,29 +699,6 @@ public void testCorrectBadFileURIActualFile() throws Exception
assertThat(URIUtil.correctFileURI(fileUrlUri).toASCIIString(), is(expectedUri.toASCIIString()));
}
- public static Stream 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 encodeSpecific()
{
return Stream.of(
@@ -1040,12 +885,12 @@ public void testEncodeDecodeVisibleOnly()
builder.append(i);
String path = builder.toString();
String encoded = URIUtil.encodePath(path);
- // Check endoded is visible
+ // Check encoded is visible
for (char c : encoded.toCharArray())
{
- assertTrue(c > 0x20 && c < 0x80);
+ assertTrue(c > 0x20 && c < 0x7f);
assertFalse(Character.isWhitespace(c));
- assertFalse(Character.isISOControl(c));
+ assertFalse(Character.isISOControl(c), "isISOControl(0x%2x)".formatted((byte)c));
}
// check decode to original
String decoded = URIUtil.decodePath(encoded);
diff --git a/jetty-ee10/jetty-ee10-openid/src/main/java/org/eclipse/jetty/ee10/security/openid/OpenIdAuthenticator.java b/jetty-ee10/jetty-ee10-openid/src/main/java/org/eclipse/jetty/ee10/security/openid/OpenIdAuthenticator.java
index 22dd8d1cd32d..e34e8111b61c 100644
--- a/jetty-ee10/jetty-ee10-openid/src/main/java/org/eclipse/jetty/ee10/security/openid/OpenIdAuthenticator.java
+++ b/jetty-ee10/jetty-ee10-openid/src/main/java/org/eclipse/jetty/ee10/security/openid/OpenIdAuthenticator.java
@@ -389,7 +389,7 @@ public Authentication validateRequest(Request req, Response res, Callback cb, bo
String uri = request.getRequestURI();
if (uri == null)
- uri = URIUtil.SLASH;
+ uri = "/";
mandatory |= isJSecurityCheck(uri);
if (!mandatory)
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java
index 30a32c7e9316..a124d5c083b4 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java
@@ -786,7 +786,7 @@ public Map getLocaleEncodings()
*/
public Resource getResource(String pathInContext) throws MalformedURLException
{
- if (pathInContext == null || !pathInContext.startsWith(URIUtil.SLASH))
+ if (pathInContext == null || !pathInContext.startsWith("/"))
throw new MalformedURLException(pathInContext);
Resource baseResource = getBaseResource();
@@ -838,8 +838,8 @@ public Set getResourcePaths(String path)
{
Resource resource = getResource(path);
- if (!path.endsWith(URIUtil.SLASH))
- path = path + URIUtil.SLASH;
+ if (!path.endsWith("/"))
+ path = path + "/";
HashSet set = new HashSet<>();
for (Resource item: resource.list())
@@ -2889,9 +2889,9 @@ public String getRealPath(String path)
if (path == null)
return null;
if (path.length() == 0)
- path = URIUtil.SLASH;
+ path = "/";
else if (path.charAt(0) != '/')
- path = URIUtil.SLASH + path;
+ path = "/" + path;
try
{
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/security/authentication/FormAuthenticator.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/security/authentication/FormAuthenticator.java
index 6b10a2f03cd2..8ae24f46a722 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/security/authentication/FormAuthenticator.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/security/authentication/FormAuthenticator.java
@@ -253,7 +253,7 @@ public Authentication validateRequest(Request req, Response res, Callback callba
{
nuri = servletApiRequest.getContextPath();
if (nuri.length() == 0)
- nuri = URIUtil.SLASH;
+ nuri = "/";
}
else
nuri = savedURI.asString();
diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java
index 7ac59b85dbc1..f881706aaddc 100644
--- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java
+++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java
@@ -378,7 +378,7 @@ public ResourceFactory getResourceFactory()
@Override
public Resource getResource(String pathInContext) throws MalformedURLException
{
- if (pathInContext == null || !pathInContext.startsWith(URIUtil.SLASH))
+ if (pathInContext == null || !pathInContext.startsWith("/"))
throw new MalformedURLException(pathInContext);
MalformedURLException mue = null;
diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
index 02ad570627da..54aa0096935d 100644
--- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
+++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
@@ -1384,7 +1384,7 @@ public Map getLocaleEncodings()
*/
public Resource getResource(String pathInContext) throws MalformedURLException
{
- if (pathInContext == null || !pathInContext.startsWith(URIUtil.SLASH))
+ if (pathInContext == null || !pathInContext.startsWith("/"))
throw new MalformedURLException(pathInContext);
Resource baseResource = _coreContextHandler.getBaseResource();
@@ -1476,8 +1476,8 @@ public Set getResourcePaths(String path)
{
Resource resource = getResource(path);
- if (!path.endsWith(URIUtil.SLASH))
- path = path + URIUtil.SLASH;
+ if (!path.endsWith("/"))
+ path = path + "/";
HashSet set = new HashSet<>();
@@ -1814,9 +1814,9 @@ public String getRealPath(String path)
if (path == null)
return null;
if (path.length() == 0)
- path = URIUtil.SLASH;
+ path = "/";
else if (path.charAt(0) != '/')
- path = URIUtil.SLASH + path;
+ path = "/" + path;
try
{
diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceService.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceService.java
index 6a6b97e39aef..9cbc5a23e266 100644
--- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceService.java
+++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceService.java
@@ -233,7 +233,7 @@ public boolean doGet(HttpServletRequest request, HttpServletResponse response)
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;
HttpContent content = null;
@@ -650,7 +650,7 @@ protected void sendDirectory(HttpServletRequest request,
}
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());
if (dir == null)
{
diff --git a/jetty-ee9/jetty-ee9-openid/src/main/java/org/eclipse/jetty/ee9/security/openid/OpenIdAuthenticator.java b/jetty-ee9/jetty-ee9-openid/src/main/java/org/eclipse/jetty/ee9/security/openid/OpenIdAuthenticator.java
index dfcc778a6dc5..2852d0f3212f 100644
--- a/jetty-ee9/jetty-ee9-openid/src/main/java/org/eclipse/jetty/ee9/security/openid/OpenIdAuthenticator.java
+++ b/jetty-ee9/jetty-ee9-openid/src/main/java/org/eclipse/jetty/ee9/security/openid/OpenIdAuthenticator.java
@@ -378,7 +378,7 @@ public Authentication validateRequest(ServletRequest req, ServletResponse res, b
String uri = request.getRequestURI();
if (uri == null)
- uri = URIUtil.SLASH;
+ uri = "/";
mandatory |= isJSecurityCheck(uri);
if (!mandatory)
diff --git a/jetty-ee9/jetty-ee9-security/src/main/java/org/eclipse/jetty/ee9/security/authentication/FormAuthenticator.java b/jetty-ee9/jetty-ee9-security/src/main/java/org/eclipse/jetty/ee9/security/authentication/FormAuthenticator.java
index 5b058821a0a9..45ea2f4ad35f 100644
--- a/jetty-ee9/jetty-ee9-security/src/main/java/org/eclipse/jetty/ee9/security/authentication/FormAuthenticator.java
+++ b/jetty-ee9/jetty-ee9-security/src/main/java/org/eclipse/jetty/ee9/security/authentication/FormAuthenticator.java
@@ -242,7 +242,7 @@ public Authentication validateRequest(ServletRequest req, ServletResponse res, b
String uri = request.getRequestURI();
if (uri == null)
- uri = URIUtil.SLASH;
+ uri = "/";
mandatory |= isJSecurityCheck(uri);
if (!mandatory)
@@ -275,7 +275,7 @@ public Authentication validateRequest(ServletRequest req, ServletResponse res, b
{
nuri = request.getContextPath();
if (nuri.length() == 0)
- nuri = URIUtil.SLASH;
+ nuri = "/";
}
formAuth = new FormAuthentication(getAuthMethod(), user);
}
diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java
index 06c5a80b501e..9d7afbded2d7 100644
--- a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java
+++ b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java
@@ -387,7 +387,7 @@ public ResourceFactory getResourceFactory()
@Override
public Resource getResource(String pathInContext) throws MalformedURLException
{
- if (pathInContext == null || !pathInContext.startsWith(URIUtil.SLASH))
+ if (pathInContext == null || !pathInContext.startsWith("/"))
throw new MalformedURLException(pathInContext);
MalformedURLException mue = null;