Skip to content

Commit

Permalink
fix: do not open FileSystem for unknown schemes (#11235)
Browse files Browse the repository at this point in the history
Don't open a filesystem for unknown schemes
that are probably custom for container and
have no FileSystem Provider implementation added.

Fixes #11230
  • Loading branch information
caalador committed Jun 16, 2021
1 parent e1f9df4 commit 23ce4fc
Show file tree
Hide file tree
Showing 3 changed files with 310 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -142,15 +140,15 @@ private boolean resourceIsDirectory(URL resource) {
if ("jar".equals(resource.getProtocol())) {
// Get the file path in jar
final String pathInJar = resource.getPath()
.substring(resource.getPath().lastIndexOf("!") + 1);
.substring(resource.getPath().indexOf('!') + 1);
try {
FileSystem fileSystem = getFileSystem(resourceURI);
// Get the file path inside the jar.
final Path path = fileSystem.getPath(pathInJar);

return Files.isDirectory(path);
} catch (IOException e) {
getLogger().debug("failed to read zip file", e);
getLogger().debug("failed to read jar file contents", e);
} finally {
closeFileSystem(resourceURI);
}
Expand Down Expand Up @@ -199,6 +197,11 @@ private URI getFileURI(URI resourceURI) {
FileSystem getFileSystem(URI resourceURI) throws IOException {
synchronized (fileSystemLock) {
URI fileURI = getFileURI(resourceURI);
if (!fileURI.getScheme().equals("file")) {
throw new IOException("Can not read scheme '"
+ fileURI.getScheme() + "' for resource " + resourceURI
+ " and will determine this as not a folder");
}

if (openFileSystems.computeIfPresent(fileURI,
(key, value) -> value + 1) != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package com.vaadin.flow;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.security.Permission;

public class WarURLStreamHandlerFactory implements URLStreamHandlerFactory {

private static final String WAR_PROTOCOL = "war";

// Singleton instance
private static volatile WarURLStreamHandlerFactory instance = null;

private final boolean registered;

/**
* Obtain a reference to the singleton instance. It is recommended that
* callers check the value of {@link #isRegistered()} before using the
* returned instance.
*
* @return A reference to the singleton instance
*/
public static WarURLStreamHandlerFactory getInstance() {
getInstanceInternal(true);
return instance;
}

private static WarURLStreamHandlerFactory getInstanceInternal(
boolean register) {
// Double checked locking. OK because instance is volatile.
if (instance == null) {
synchronized (WarURLStreamHandlerFactory.class) {
if (instance == null) {
instance = new WarURLStreamHandlerFactory(register);
}
}
}
return instance;
}

private WarURLStreamHandlerFactory(boolean register) {
// Hide default constructor
// Singleton pattern to ensure there is only one instance of this
// factory
this.registered = register;
if (register) {
URL.setURLStreamHandlerFactory(this);
}
}

public boolean isRegistered() {
return registered;
}

/**
* Register this factory with the JVM. May be called more than once. The
* implementation ensures that registration only occurs once.
*
* @return <code>true</code> if the factory is already registered with the
* JVM or was successfully registered as a result of this call.
* <code>false</code> if the factory was disabled prior to this
* call.
*/
public static boolean register() {
return getInstanceInternal(true).isRegistered();
}

/**
* Prevent this this factory from registering with the JVM. May be called
* more than once.
*
* @return <code>true</code> if the factory is already disabled or was
* successfully disabled as a result of this call.
* <code>false</code> if the factory was already registered prior
* to this call.
*/
public static boolean disable() {
return !getInstanceInternal(false).isRegistered();
}

@Override
public URLStreamHandler createURLStreamHandler(String protocol) {

// Tomcat's handler always takes priority so applications can't override
// it.
if (WAR_PROTOCOL.equals(protocol)) {
return new WarHandler();
}

// Unknown protocol
return null;
}

public static class WarHandler extends URLStreamHandler {

@Override
protected URLConnection openConnection(URL u) throws IOException {
return new WarURLConnection(u);
}

@Override
protected void setURL(URL u, String protocol, String host, int port,
String authority, String userInfo, String path, String query,
String ref) {
if (path.startsWith("file:") && !path.startsWith("file:/")) {
// Work around a problem with the URLs in the security policy file.
// On Windows, the use of ${catalina.[home|base]} in the policy file
// results in codebase URLs of the form file:C:/... when they should
// be file:/C:/...
// For file: and jar: URLs, the JRE compensates for this. It does not
// compensate for this for war:file:... URLs. Therefore, we do that
// here
path = "file:/" + path.substring(5);
}
super.setURL(u, protocol, host, port, authority, userInfo, path,
query, ref);
}

}

public static class WarURLConnection extends URLConnection {

private final URLConnection wrappedJarUrlConnection;
private boolean connected;

protected WarURLConnection(URL url) throws IOException {
super(url);
URL innerJarUrl = warToJar(url);
wrappedJarUrlConnection = innerJarUrl.openConnection();
}

@Override
public void connect() throws IOException {
if (!connected) {
wrappedJarUrlConnection.connect();
connected = true;
}
}

@Override
public InputStream getInputStream() throws IOException {
connect();
return wrappedJarUrlConnection.getInputStream();
}

@Override
public Permission getPermission() throws IOException {
return wrappedJarUrlConnection.getPermission();
}

@Override
public long getLastModified() {
return wrappedJarUrlConnection.getLastModified();
}

@Override
public int getContentLength() {
return wrappedJarUrlConnection.getContentLength();
}

@Override
public long getContentLengthLong() {
return wrappedJarUrlConnection.getContentLengthLong();
}

public static URL warToJar(URL warUrl) throws MalformedURLException {
// Assumes that the spec is absolute and starts war:file:/...
String file = warUrl.getFile();
if (file.contains("*/")) {
file = file.replaceFirst("\\*/", "!/");
} else if (file.contains("^/")) {
file = file.replaceFirst("\\^/", "!/");
}

return new URL("jar", warUrl.getHost(), warUrl.getPort(), file);
}
}
}

0 comments on commit 23ce4fc

Please sign in to comment.