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.Loader;
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.ManagedObject;
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
if (contextPath.equalsIgnoreCase("root"))
{
contextPath = URIUtil.SLASH;
contextPath = "/";
}
// handle root with virtual host form
else if (StringUtil.startsWithIgnoreCase(contextPath, "root-"))
@ -489,7 +488,7 @@ public class ContextProvider extends ScanningAppProvider
int dash = contextPath.indexOf('-');
String virtual = contextPath.substring(dash + 1);
context.setVirtualHosts(Arrays.asList(virtual.split(",")));
contextPath = URIUtil.SLASH;
contextPath = "/";
}
// 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.toolchain.test.MavenTestingUtils;
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.Environment;
@ -71,7 +70,7 @@ public class MockAppProvider extends AbstractLifeCycle implements AppProvider
// special case of archive (or dir) named "root" is / context
if (path.equalsIgnoreCase("root") || path.equalsIgnoreCase("root/"))
path = URIUtil.SLASH;
path = "/";
// Ensure "/" is Prepended to all context paths.
if (path.charAt(0) != '/')

View File

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

View File

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

View File

@ -156,7 +156,7 @@ public class ResourceService
// Is this a Range request?
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();
try
@ -552,7 +552,7 @@ public class ResourceService
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)
{

View File

@ -18,7 +18,6 @@ import java.io.IOException;
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)
{
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++)
// 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++)
{
char c = path.charAt(i);
switch (c)
if (c > 0x7F) // 8-bit +
{
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);
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 (bytes != null)
{
for (; i < bytes.length; i++)
{
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);
}
}
}
return buf;
if (needsByteEncoding)
return encodePathBytes(path);
else if (needsEncoding)
return encodePathString(path);
else
return 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)
private static String encodePathString(String path)
{
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.
*
* @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 final class URIUtil
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)
{
if (f == result)
{
ret.append(result);
decoded = true;
break;
}
}
if (decoded)
{
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 final class URIUtil
/**
* 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 @@ public final class URIUtil
{
// 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 @@ public final class URIUtil
* <p>
* 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.
* <p>
* For example the path <code>/fo %2fo/b%61r</code> will be normalized to <code>/fo%20%2Fo/bar</code>,
* whilst {@link #decodePath(String)} would result in the ambiguous and URI illegal <code>/fo /o/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}.
* @return the canonical path or null if it is non-normal
* @see #decodePath(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)
{
@ -989,7 +842,7 @@ public final class URIUtil
/**
* 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 final class URIUtil
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 final class URIUtil
}
else
{
if (p2.startsWith(URIUtil.SLASH))
if (p2.startsWith("/"))
buf.insert(split, p2);
else
{
@ -1043,8 +896,10 @@ public final class URIUtil
/**
* Add two Decoded URI path segments.
* Handles null and empty paths. Path and query params (eg ?a=b or
* ;JSESSIONID=xxx) are not handled
* <p>
* 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 p2 URI path segment (should be decoded)
@ -1061,8 +916,8 @@ public final class URIUtil
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 final class URIUtil
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 '&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)
{
@ -1142,14 +999,16 @@ public final class URIUtil
/**
* Return the parent Path.
* <p>
* Treat a URI like a directory path and return the parent directory.
* </p>
*
* @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 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.
* Null is returned if the path is normalized above its root.
* </p>
@ -1189,7 +1049,6 @@ public final class URIUtil
case '.':
if (slash)
break loop;
slash = false;
break;
case '?':
@ -1267,8 +1126,8 @@ public final class URIUtil
/**
* <p>Check if a path would be normalized within itself. For example,
* <code>/foo/../../bar</code> is normalized above its root and would
* thus return false, whilst <code>/foo/./bar/..</code> 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 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.
* </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.
* @return the normalized path, or null if path traversal above root.
* @see #normalizePathQuery(String)
@ -1306,18 +1165,13 @@ public final class URIUtil
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 final class URIUtil
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 @@ public final class URIUtil
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 @@ public final class URIUtil
/**
* 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 final class URIUtil
*/
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));
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;
default:
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);
}
}
}
// 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;
}
}
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 @@ public final class URIUtil
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 final class URIUtil
// 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 final class URIUtil
}
/**
* 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 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>
* Each part of the input string could be path references (unix or windows style), or string URI references.
* </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>
*
* @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.
*
* </p>
* <p>
* 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.
* @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)
*/
public static URI toJarFileUri(URI uri)
@ -2034,9 +1846,13 @@ public final class URIUtil
}
/**
* <p>
* 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.
* </p>
*
* @param uri the input URI
* @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))
throw new IllegalArgumentException(subUriPath);
if (subUriPath.length() == 0 || URIUtil.SLASH.equals(subUriPath))
if (subUriPath.length() == 0 || "/".equals(subUriPath))
{
return this;
}

View File

@ -182,8 +182,8 @@ public class PathResource extends Resource
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 class PathResource extends Resource
if (URIUtil.isNotNormalWithinSelf(subUriPath))
throw new IllegalArgumentException(subUriPath);
if (URIUtil.SLASH.equals(subUriPath))
if ("/".equals(subUriPath))
return this;
URI uri = getURI();

View File

@ -66,7 +66,9 @@ public class URIUtilTest
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"),
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)
{
// 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 class URIUtilTest
assertThat(actual, is(expected));
}
public static Stream<Arguments> ensureSafeEncodingSource()
public static Stream<Arguments> encodePathSafeEncodingSource()
{
return Stream.of(
Arguments.of("/foo", "/foo"),
@ -589,10 +589,10 @@ public class URIUtilTest
}
@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<Arguments> compactPathSource()
@ -637,139 +637,6 @@ public class URIUtilTest
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()
{
return Stream.of(
@ -811,17 +678,18 @@ public class URIUtilTest
}
@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 class URIUtilTest
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()
{
return Stream.of(
@ -1040,12 +885,12 @@ public class URIUtilTest
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);

View File

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

View File

@ -786,7 +786,7 @@ public class ServletContextHandler extends ContextHandler implements Graceful
*/
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 class ServletContextHandler extends ContextHandler implements Graceful
{
Resource resource = getResource(path);
if (!path.endsWith(URIUtil.SLASH))
path = path + URIUtil.SLASH;
if (!path.endsWith("/"))
path = path + "/";
HashSet<String> set = new HashSet<>();
for (Resource item: resource.list())
@ -2889,9 +2889,9 @@ public class ServletContextHandler extends ContextHandler implements Graceful
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
{

View File

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

View File

@ -378,7 +378,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
@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;

View File

@ -1384,7 +1384,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
*/
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 class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{
Resource resource = getResource(path);
if (!path.endsWith(URIUtil.SLASH))
path = path + URIUtil.SLASH;
if (!path.endsWith("/"))
path = path + "/";
HashSet<String> set = new HashSet<>();
@ -1814,9 +1814,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
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
{

View File

@ -233,7 +233,7 @@ public class ResourceService
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 @@ public class ResourceService
}
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)
{

View File

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

View File

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

View File

@ -387,7 +387,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
@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;