Skip to content

Commit

Permalink
Fix Windows path handling for nested jars
Browse files Browse the repository at this point in the history
Update `Path` creation for nested locations to allow both UNC and classic
file references to be used. This commit attempts to align our URL
handling with that of standard file URLs. The `NestedLocation` class
no longer attempts to remove leading all `\` characters and instead
only removes the first `\` when the second char is `:`. This duplicates
the logic found in Java's own internal `WindowsUriSupport` class which
is used when calling `Path.of(url)` with a `file:` URL.

Fixes gh-40549
  • Loading branch information
philwebb committed May 8, 2024
1 parent ed42ed7 commit 7708ec7
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,7 @@
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.Set;
Expand Down Expand Up @@ -107,11 +108,10 @@ static Archive create(Class<?> target) throws Exception {
static Archive create(ProtectionDomain protectionDomain) throws Exception {
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
String path = (location != null) ? location.getSchemeSpecificPart() : null;
if (path == null) {
if (location == null) {
throw new IllegalStateException("Unable to determine code source archive");
}
return create(new File(path));
return create(Path.of(location).toFile());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@
*/
public record NestedLocation(Path path, String nestedEntryName) {

private static final Map<String, NestedLocation> cache = new ConcurrentHashMap<>();
private static final Map<String, NestedLocation> locationCache = new ConcurrentHashMap<>();

private static final Map<String, Path> pathCache = new ConcurrentHashMap<>();

public NestedLocation(Path path, String nestedEntryName) {
if (path == null) {
Expand Down Expand Up @@ -89,35 +91,37 @@ public static NestedLocation fromUri(URI uri) {
return parse(uri.getSchemeSpecificPart());
}

static NestedLocation parse(String path) {
if (path == null || path.isEmpty()) {
throw new IllegalArgumentException("'path' must not be empty");
static NestedLocation parse(String location) {
if (location == null || location.isEmpty()) {
throw new IllegalArgumentException("'location' must not be empty");
}
int index = path.lastIndexOf("/!");
return cache.computeIfAbsent(path, (l) -> create(index, l));
return locationCache.computeIfAbsent(location, (key) -> create(location));
}

private static NestedLocation create(int index, String location) {
private static NestedLocation create(String location) {
int index = location.lastIndexOf("/!");
String locationPath = (index != -1) ? location.substring(0, index) : location;
if (isWindows() && !isUncPath(location)) {
while (locationPath.startsWith("/")) {
locationPath = locationPath.substring(1, locationPath.length());
}
}
String nestedEntryName = (index != -1) ? location.substring(index + 2) : null;
return new NestedLocation((!locationPath.isEmpty()) ? Path.of(locationPath) : null, nestedEntryName);
return new NestedLocation((!locationPath.isEmpty()) ? asPath(locationPath) : null, nestedEntryName);
}

private static boolean isWindows() {
return File.separatorChar == '\\';
private static Path asPath(String locationPath) {
return pathCache.computeIfAbsent(locationPath, (key) -> {
if (isWindows() && locationPath.length() > 2 && locationPath.charAt(2) == ':') {
// Use the same logic as Java's internal WindowsUriSupport class
return Path.of(locationPath.substring(1));
}
return Path.of(locationPath);
});
}

private static boolean isUncPath(String input) {
return !input.contains(":");
private static boolean isWindows() {
return File.separatorChar == '\\';
}

static void clearCache() {
cache.clear();
locationCache.clear();
pathCache.clear();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import java.util.Map;
import java.util.Map.Entry;

import org.springframework.boot.loader.net.util.UrlDecoder;
import org.springframework.boot.loader.ref.Cleaner;

/**
Expand Down Expand Up @@ -77,7 +76,7 @@ class NestedUrlConnection extends URLConnection {

private NestedLocation parseNestedLocation(URL url) throws MalformedURLException {
try {
return NestedLocation.parse(UrlDecoder.decode(url.getPath()));
return NestedLocation.fromUrl(url);
}
catch (IllegalArgumentException ex) {
throw new MalformedURLException(ex.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ void fromUrlWhenNotNestedProtocolThrowsException() {
@Test
void fromUrlWhenNoPathThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> NestedLocation.fromUrl(new URL("nested:")))
.withMessageContaining("'path' must not be empty");
.withMessageContaining("'location' must not be empty");
}

@Test
Expand Down

0 comments on commit 7708ec7

Please sign in to comment.