Skip to content

Commit c64936c

Browse files
authored
Issue #13588 - Improve ResourceFactory.split() and URIUtil.toURI to work properly on Windows. (#13652)
* Issue #13588 - Improve ResourceFactory.split() and URIUtil.toURI to work properly on Windows. + New testcases in ResourceFactoryTest.testResourceSplit* + Restore URIUtil.toURI(String) for the purpose of converting a raw string reference to a URI without causing a Resource mount to occur.
1 parent bb111ff commit c64936c

File tree

4 files changed

+208
-134
lines changed

4 files changed

+208
-134
lines changed

jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1901,72 +1901,75 @@ else if (scheme.equalsIgnoreCase("file"))
19011901
}
19021902

19031903
/**
1904-
* <p>Convert a String into a URI suitable for use as a Resource.</p>
1904+
* <p>Convert a String reference into an absolute URI.</p>
19051905
*
1906-
* @param resource If the string starts with one of the ALLOWED_SCHEMES, then it is assumed to be a
1907-
* representation of a {@link URI}, otherwise it is treated as a {@link Path}.
1908-
* @return The {@link URI} form of the resource.
1909-
* @deprecated This method is currently resolving relative paths against the current directory, which is a mechanism
1910-
* that should be implemented by a {@link ResourceFactory}. All calls to this method need to be reviewed.
1906+
* <p>If given a relative reference string to a path, it will convert it to an absolute path
1907+
* using the same techniques present in the JVM itself (eg: {@code Path.of(reference).toAbsolutePath()})</p>
1908+
*
1909+
* <p>Relative URI reference strings (eg: {@code "file:path/dir/"}
1910+
* and {@code "file:path/foo.jar!/"}) are not supported.</p>
1911+
*
1912+
* @param reference the raw String input.
1913+
* @return The absolute {@link URI} form of the reference.
1914+
* @throws IllegalArgumentException if unable to convert the input string.
19111915
*/
1912-
@Deprecated(since = "12.0.8")
1913-
public static URI toURI(String resource)
1916+
public static URI toURI(String reference)
19141917
{
1915-
Objects.requireNonNull(resource);
1918+
Objects.requireNonNull(reference);
19161919

1917-
if (URIUtil.hasScheme(resource))
1920+
try
19181921
{
1919-
try
1922+
/* Perform URI test first.
1923+
* We don't want to perform Path.of(String) first.
1924+
*
1925+
* Example: reference parameter is the String "file:///path/to/dir"
1926+
*
1927+
* On Unix, the Path.of(reference) will result in a relative directory reference
1928+
* that includes the "file:" portion in the path after the current working directory.
1929+
* You'll wind up with something like "file:///home/user/code/jetty/12.1.x/jetty-core/jetty-util/file:///path/to/dir" in this case
1930+
*
1931+
* On Windows, the Path.of(reference) will not allow a Path.of("file:///path/to/dir") to work.
1932+
* This is because there cannot be multi-character drive letters (yes, Windows is limited to only 26 drive letters max)
1933+
*/
1934+
URI uri = new URI(reference);
1935+
if (uri.isAbsolute())
19201936
{
1921-
URI uri = new URI(resource);
1922-
1923-
if (ResourceFactory.isSupported(uri))
1924-
return correctURI(uri);
1925-
1926-
// We don't have a supported URI scheme
1927-
if (uri.getScheme().length() == 1)
1937+
// At this point we have a string detected as a URI.
1938+
// But that could also include Windows paths like "C:\path\to\foo.jar" or "C:/path/to/foo.jar"
1939+
String scheme = uri.getScheme();
1940+
if (scheme.length() == 1 && Character.isLetter(scheme.charAt(0)))
19281941
{
1929-
// Input is a possible Windows path disguised as a URI "D:/path/to/resource.txt".
1930-
try
1931-
{
1932-
return toURI(Paths.get(resource).toUri().toASCIIString());
1933-
}
1934-
catch (InvalidPathException x)
1935-
{
1936-
LOG.trace("ignored", x);
1937-
}
1942+
// Single character schemes are assumed to be windows.
1943+
// Make it a file: URI and process it separately.
1944+
return toURI("file:///" + uri.toASCIIString());
1945+
}
1946+
{
1947+
// Anything else, scheme wise, is acceptable.
1948+
return correctURI(uri);
19381949
}
1939-
1940-
// If we reached this point, that means the input String has a scheme,
1941-
// and is not recognized as supported by the registered schemes in ResourceFactory.
1942-
if (LOG.isDebugEnabled())
1943-
LOG.debug("URI scheme is not registered: {}", uri.toASCIIString());
1944-
throw new IllegalArgumentException("URI scheme not registered: " + uri.getScheme());
1945-
}
1946-
catch (URISyntaxException x)
1947-
{
1948-
// We have an input string that has what looks like a scheme, but isn't a URI.
1949-
// Eg: "C:\path\to\resource.txt"
1950-
LOG.trace("ignored", x);
19511950
}
19521951
}
1952+
catch (URISyntaxException e)
1953+
{
1954+
LOG.trace("IGNORED: Invalid as URI Reference: {}", reference, e);
1955+
}
19531956

1954-
// If we reached this point, we have a String with no valid scheme.
1955-
// Treat it as a Path, as that's all we have left to investigate.
19561957
try
19571958
{
1958-
return toURI(Paths.get(resource).toUri().toASCIIString());
1959+
Path path = Path.of(reference).toAbsolutePath();
1960+
return path.toUri();
19591961
}
1960-
catch (InvalidPathException x)
1962+
catch (InvalidPathException e)
19611963
{
1962-
LOG.trace("ignored", x);
1964+
// Not a path reference.
1965+
LOG.trace("IGNORED: Invalid as Path Reference: {}", reference, e);
19631966
}
19641967

19651968
// If we reached this here, that means the input string cannot be used as
19661969
// a URI or a File Path. The cause is usually due to bad input (eg:
19671970
// characters that are not supported by file system)
19681971
if (LOG.isDebugEnabled())
1969-
LOG.debug("Input string cannot be converted to URI \"{}\"", resource);
1972+
LOG.debug("Input string cannot be converted to URI \"{}\"", reference);
19701973
throw new IllegalArgumentException("Cannot be converted to URI");
19711974
}
19721975

jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ default Resource newResource(String resource)
358358
// Treat it as a Path, as that's all we have left to investigate.
359359
try
360360
{
361-
Path path = Paths.get(resource);
361+
Path path = Paths.get(resource).toAbsolutePath();
362362
URI uri = new URI(path.toUri().toASCIIString());
363363
return new PathResource(path, uri, true);
364364
}
@@ -505,11 +505,13 @@ default List<Resource> split(String str, String delim)
505505
/**
506506
* Split a string of references by provided delims into a List of {@link Resource}.
507507
* <p>
508-
* Each part of the input string could be path references (unix or windows style), string URI references, or even glob references (eg: {@code /path/to/libs/*}).
508+
* Each part of the input string could be path references (unix or windows style),
509+
* string URI references, or even glob references (eg: {@code /path/to/libs/*}).
509510
* Note: that if you use the {@code :} character in your delims, then URI references will be impossible.
510511
* </p>
511512
* <p>
512-
* If the result of processing the input segment is a java archive it will not be automatically mounted, the caller must mount if necessary
513+
* If the result of processing the input segment is a java archive it will not be automatically mounted,
514+
* the caller must mount if necessary
513515
* </p>
514516
*
515517
* @param str the input string of references
@@ -528,40 +530,40 @@ default List<Resource> split(String str, String delims, boolean unwrap)
528530
try
529531
{
530532
// Is this a glob reference?
533+
// Note: a glob reference can be a java.net.URI
531534
if (reference.endsWith("/*") || reference.endsWith("\\*"))
532535
{
533-
Resource dir = newResource(reference.substring(0, reference.length() - 2));
536+
// Get the raw directory reference (without the trailing glob).
537+
// This can be "/path/to/dir/*" (on unix an absolute path, on windows a relative path)
538+
// or "C:/path/to/dir/*" (windows java Path syntax)
539+
// or "C:\path\to\dir\*" (windows native syntax)
540+
// or even a URI like "file:///path/to/dir/*" (always an absolute URI)
541+
// and "file:///C:/path/to/dir/*"
542+
// and "jar:file:///C:/path/to/foo.jar!/deep/*"
543+
String rawDir = reference.substring(0, reference.length() - 2);
544+
// Convert to a URI without loading it as a Resource (yet).
545+
URI uri = URIUtil.toURI(rawDir);
546+
// Load rawDir as a Resource
547+
Resource dir = newResource(uri);
534548
if (dir.isDirectory())
535549
{
550+
// Loop through resource entries for content that will match glob.
536551
List<Resource> expanded = dir.list();
537552
expanded.sort(ResourceCollators.byName(true));
538553
expanded.stream().filter(r -> FileID.isLibArchive(r.getName())).forEach(list::add);
539554
}
540555
}
541556
else
542557
{
543-
// Simple reference. Could have a scheme like jar:file:, or just file:.
544-
// Or it could be a relative reference, in which case we need to
545-
// ensure it is absolute. Otherwise, comparisons between Resources
546-
// that point to the same resource but one is relative and one is absolute
547-
// will fail.
548-
if (URIUtil.hasScheme(reference))
558+
// Simple reference, could be relative, could be windows syntax, could be a URI.
559+
URI uri = URIUtil.toURI(reference);
560+
if ("jar".equals(uri.getScheme()) && unwrap)
549561
{
550-
// Could be a jar:file url, ensure it is unwrapped back to the file
551-
// otherwise it will be a MountedPathResource and consume a mount point
552-
// that might be unnecessary - the caller should always decide whether to mount
553-
URI uri = new URI(reference);
554-
if (unwrap)
555-
list.add(newResource(URIUtil.unwrapContainer(uri)));
556-
else
557-
list.add(newResource(uri.toASCIIString()));
562+
list.add(newResource(URIUtil.unwrapContainer(uri)));
558563
}
559564
else
560565
{
561-
Path p = Paths.get(reference);
562-
if (!p.isAbsolute() && LOG.isDebugEnabled())
563-
LOG.warn("Non-absolute path: {}", reference);
564-
list.add(newResource(p.toAbsolutePath()));
566+
list.add(newResource(uri));
565567
}
566568
}
567569
}

jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,20 +1071,28 @@ public static Stream<Arguments> toURICases()
10711071

10721072
if (OS.WINDOWS.isCurrentOs())
10731073
{
1074+
Path cwd = Path.of(".").toAbsolutePath().normalize();
1075+
String root = cwd.getRoot().toString();
1076+
if (root.endsWith("\\"))
1077+
root = root.substring(0, root.length() - 1);
1078+
10741079
// Windows format (absolute and relative)
10751080
args.add(Arguments.of("C:\\path\\to\\foo.jar", "file:///C:/path/to/foo.jar"));
10761081
args.add(Arguments.of("D:\\path\\to\\bogus.txt", "file:///D:/path/to/bogus.txt"));
1077-
args.add(Arguments.of("\\path\\to\\foo.jar", "file:///C:/path/to/foo.jar"));
1078-
args.add(Arguments.of("\\path\\to\\bogus.txt", "file:///C:/path/to/bogus.txt"));
1079-
// unix format (relative)
1082+
args.add(Arguments.of("\\path\\to\\foo.jar", "file:///%s/path/to/foo.jar".formatted(root)));
1083+
args.add(Arguments.of("\\path\\to\\bogus.txt", "file:///%s/path/to/bogus.txt".formatted(root)));
1084+
// java path format (absolute)
10801085
args.add(Arguments.of("C:/path/to/foo.jar", "file:///C:/path/to/foo.jar"));
10811086
args.add(Arguments.of("D:/path/to/bogus.txt", "file:///D:/path/to/bogus.txt"));
1082-
args.add(Arguments.of("/path/to/foo.jar", "file:///C:/path/to/foo.jar"));
1083-
args.add(Arguments.of("/path/to/bogus.txt", "file:///C:/path/to/bogus.txt"));
1087+
// java path format (relative)
1088+
args.add(Arguments.of("/path/to/foo.jar", "file:///%s/path/to/foo.jar".formatted(root)));
1089+
args.add(Arguments.of("/path/to/bogus.txt", "file:///%s/path/to/bogus.txt".formatted(root)));
10841090
// URI format (absolute)
10851091
args.add(Arguments.of("file:///D:/path/to/zed.jar", "file:///D:/path/to/zed.jar"));
10861092
args.add(Arguments.of("file:/e:/zed/yotta.txt", "file:///e:/zed/yotta.txt"));
10871093
args.add(Arguments.of("jar:file:///E:/path/to/bar.jar", "jar:file:///E:/path/to/bar.jar"));
1094+
// URI format (bad scheme case, but we preserve it)
1095+
args.add(Arguments.of("JAR:FILE:///E:/path/to/bar.jar", "JAR:FILE:///E:/path/to/bar.jar"));
10881096
}
10891097
else
10901098
{
@@ -1094,8 +1102,13 @@ public static Stream<Arguments> toURICases()
10941102
}
10951103
// URI format (absolute)
10961104
args.add(Arguments.of("file:///path/to/zed.jar", "file:///path/to/zed.jar"));
1105+
args.add(Arguments.of("FILE:///path/to/zed.jar", "file:///path/to/zed.jar"));
10971106
args.add(Arguments.of("jar:file:///path/to/bar.jar", "jar:file:///path/to/bar.jar"));
1098-
1107+
// URI format (bad scheme case, but we preserve it)
1108+
args.add(Arguments.of("JAR:FILE:///path/to/bar.jar", "JAR:FILE:///path/to/bar.jar"));
1109+
// URI format (bad URL syntax)
1110+
args.add(Arguments.of("file:/path/to/bad.jar", "file:///path/to/bad.jar"));
1111+
args.add(Arguments.of("jar:file:/path/to/bad.jar!/", "jar:file:///path/to/bad.jar!/"));
10991112
return args.stream();
11001113
}
11011114

0 commit comments

Comments
 (0)