Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 38 additions & 14 deletions oiosaml/src/main/java/dk/gov/oio/saml/util/ResourceUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Enumeration;
import java.util.HashMap;
Expand All @@ -28,21 +29,8 @@ public static File getResourceAsFile(String resourceName) throws InternalExcepti
throw new InternalException(String.format("Unable to load resource file '%s'", resourceName));
}
try {
File file;
URL url = ResourceUtil.getClassLoader().getResource(resourceName);

if (url != null && "jar".equals(url.getProtocol())) {
// To access resources as files inside a jar we need to copy them to disk (use when unable to access as stream)
try (InputStream inputStream = ResourceUtil.getResourceAsStream(resourceName)) {
Path path = Files.createTempFile(resourceName, "tmp");
Comment thread
thomasnymand marked this conversation as resolved.
java.nio.file.Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING);
file = path.toFile();
}
} else if (url != null) {
file = new File(url.toURI());
} else {
file = new File(resourceName);
}
File file = toFile(url, resourceName);

if (!file.exists()) {
throw new InternalException(String.format("Unable to load resource file '%s'", resourceName));
Expand All @@ -55,6 +43,42 @@ public static File getResourceAsFile(String resourceName) throws InternalExcepti
}
}

/**
* Resolve a classpath resource URL to a {@link File}.
* <p>
* A plain {@code file:} URL is used directly. Resources that are not backed by a real
* filesystem file - inside a jar ({@code jar:}), or served by an application server
* virtual file system such as WildFly/JBoss ({@code vfs:}, {@code vfszip:}) - cannot be
* turned into a File with {@code new File(url.toURI())} (that throws
* {@code IllegalArgumentException: URI scheme is not "file"}), so they are copied to a
* temporary file through their stream instead. See issue #80.
*
* @param url URL returned by the classloader, or {@code null} when the resource is not on the classpath
* @param resourceName original resource name, used as the absolute/external path fallback when {@code url} is null
* @return file pointing to the resource
*/
static File toFile(URL url, String resourceName) throws URISyntaxException, IOException {
if (url != null && "file".equals(url.getProtocol())) {
return new File(url.toURI());
}

if (url != null) {
// Not a plain file (jar:, vfs:, vfszip:, ...) - copy to disk so it can be read as a File.
// Use only the last path segment as the temp-file prefix: a resourceName that refers to a
// file in a subdirectory (e.g. "config/oiosaml.properties") contains '/', which
// createTempFile rejects with IllegalArgumentException. The "oiosaml-" prefix also
// guarantees the minimum 3-character length required for the prefix.
try (InputStream inputStream = url.openStream()) {
String baseName = Paths.get(resourceName).getFileName().toString();
Path path = Files.createTempFile("oiosaml-" + baseName, ".tmp");
Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING);
return path.toFile();
}
}

return new File(resourceName);
}

/**
* Returns an input stream for reading the specified resource.
* @param resourceName Resource name from classpath or absolute path to resource
Expand Down
58 changes: 58 additions & 0 deletions oiosaml/src/test/java/dk/gov/oio/saml/util/ResourceUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -44,6 +47,61 @@ void testGetResourceAsFileFromJar() throws InternalException {
Assertions.assertTrue(file.isFile() && file.canRead());
}

@DisplayName("Test GetResourceAsFile handles non-file (WildFly vfs:) URLs - issue #80")
@Test
void testToFileFromVfsUrl() throws Exception {
// Simulate an application-server VFS resource URL (e.g. WildFly 'vfs:') backed by a
// real file. Such URLs are not 'file:' URLs, so the old new File(url.toURI()) approach
// throws; toFile must instead copy the resource to a readable temp file.
URLStreamHandler vfsHandler = new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) {
return new URLConnection(u) {
@Override public void connect() { }
@Override public InputStream getInputStream() throws IOException {
return Files.newInputStream(externalPath);
}
};
}
};
URL vfsUrl = new URL(null, "vfs:/content/app.war/WEB-INF/classes/oiosaml.properties", vfsHandler);

// The naive conversion that caused issue #80 fails for such URLs...
Assertions.assertThrows(IllegalArgumentException.class, () -> new File(vfsUrl.toURI()));

// ...while toFile resolves it to a readable file holding the original content.
File file = ResourceUtil.toFile(vfsUrl, "oiosaml.properties");
Assertions.assertTrue(file.isFile() && file.canRead());
Assertions.assertEquals("this.is.a.test=1234",
new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8));
}

@DisplayName("Test GetResourceAsFile handles non-file URLs whose resource is in a subdirectory - issue #80")
@Test
void testToFileFromVfsUrlInSubdirectory() throws Exception {
// A resource that lives in a subdirectory (e.g. "config/oiosaml.properties") has a
// resourceName containing '/'. createTempFile rejects such a value as a prefix
// (IllegalArgumentException: Invalid prefix or suffix), so toFile must use only the
// last path segment. Regression guard for the subdirectory case of issue #80.
URLStreamHandler vfsHandler = new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) {
return new URLConnection(u) {
@Override public void connect() { }
@Override public InputStream getInputStream() throws IOException {
return Files.newInputStream(externalPath);
}
};
}
};
URL vfsUrl = new URL(null, "vfs:/content/app.war/WEB-INF/classes/config/oiosaml.properties", vfsHandler);

File file = ResourceUtil.toFile(vfsUrl, "config/oiosaml.properties");
Assertions.assertTrue(file.isFile() && file.canRead());
Assertions.assertEquals("this.is.a.test=1234",
new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8));
}

@DisplayName("Test GetResourceAsStream from classpath")
@Test
void testGetResourceAsStreamFromClassPath() throws IOException, InternalException {
Expand Down
Loading