package io.vertx.core.file.impl;
import io.netty.util.internal.PlatformDependent;
import io.vertx.core.VertxException;
import io.vertx.core.file.FileSystemOptions;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.function.IntPredicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static io.vertx.core.net.impl.URIDecoder.decodeURIComponent;
public class FileResolver {
public static final String DISABLE_FILE_CACHING_PROP_NAME = "vertx.disableFileCaching";
public static final String DISABLE_CP_RESOLVING_PROP_NAME = "vertx.disableFileCPResolving";
public static final String CACHE_DIR_BASE_PROP_NAME = "vertx.cacheDirBase";
private static final String FILE_SEP = System.getProperty("file.separator");
private static final boolean NON_UNIX_FILE_SEP = !FILE_SEP.equals("/");
private final File cwd;
private final boolean enableCaching;
private final boolean closeCache;
private final FileCache cache;
public FileResolver() {
this(new FileSystemOptions());
}
public FileResolver(FileSystemOptions fileSystemOptions) {
this(
fileSystemOptions.isFileCachingEnabled(),
fileSystemOptions.isClassPathResolvingEnabled() ? FileCache.setupCache(fileSystemOptions.getFileCacheDir()) : null,
fileSystemOptions.isClassPathResolvingEnabled());
}
public FileResolver(boolean enableCaching, FileCache cache, boolean closeCache) {
this.enableCaching = enableCaching;
this.cache = cache;
this.closeCache = closeCache;
String cwdOverride = System.getProperty("vertx.cwd");
if (cwdOverride != null) {
cwd = new File(cwdOverride).getAbsoluteFile();
} else {
cwd = null;
}
}
public void close() throws IOException {
if (closeCache) {
cache.close();
}
}
public File resolveFile(String fileName) {
File file = new File(fileName);
if (cwd != null && !file.isAbsolute()) {
file = new File(cwd, fileName);
}
if (this.cache == null) {
return file;
}
synchronized (cache) {
if (!file.exists()) {
File cacheFile = cache.getFile(fileName);
if (this.enableCaching && cacheFile.exists()) {
return cacheFile;
}
ClassLoader cl = getClassLoader();
String parentFileName = file.getParent();
if (parentFileName != null) {
if (NON_UNIX_FILE_SEP) {
parentFileName = parentFileName.replace(FILE_SEP, "/");
}
URL directoryContents = getValidClassLoaderResource(cl, parentFileName);
if (directoryContents != null) {
unpackUrlResource(directoryContents, parentFileName, cl, true);
}
}
if (NON_UNIX_FILE_SEP) {
fileName = fileName.replace(FILE_SEP, "/");
}
URL url = getValidClassLoaderResource(cl, fileName);
if (url != null) {
return unpackUrlResource(url, fileName, cl, false);
}
}
}
return file;
}
private static boolean isValidWindowsCachePath(char c) {
if (c < 32) {
return false;
}
switch (c) {
case '"':
case '*':
case ':':
case '<':
case '>':
case '?':
case '|':
return false;
default:
return true;
}
}
private static boolean isValidCachePath(String fileName) {
if (PlatformDependent.isWindows()) {
int len = fileName.length();
for (int i = 0;i < len;i++) {
char c = fileName.charAt(i);
if (!isValidWindowsCachePath(c)) {
return false;
}
if (c == ' ' && (i + 1 == len || fileName.charAt(i + 1) == '/')) {
return false;
}
}
return true;
} else {
return fileName.indexOf('\u0000') == -1;
}
}
private static URL getValidClassLoaderResource(ClassLoader cl, String fileName) {
URL resource = cl.getResource(fileName);
if (resource != null && !isValidCachePath(fileName)) {
return null;
}
return resource;
}
private File unpackUrlResource(URL url, String fileName, ClassLoader cl, boolean isDir) {
String prot = url.getProtocol();
switch (prot) {
case "file":
return unpackFromFileURL(url, fileName, cl);
case "jar":
return unpackFromJarURL(url, fileName, cl);
case "bundle":
case "bundleentry":
case "bundleresource":
case "resource":
return unpackFromBundleURL(url, isDir);
default:
throw new IllegalStateException("Invalid url protocol: " + prot);
}
}
private File unpackFromFileURL(URL url, String fileName, ClassLoader cl) {
final File resource = new File(decodeURIComponent(url.getPath(), false));
boolean isDirectory = resource.isDirectory();
File cacheFile;
try {
cacheFile = cache.cache(fileName, resource, !enableCaching);
} catch (IOException e) {
throw new VertxException(e);
}
if (isDirectory) {
String[] listing = resource.list();
if (listing != null) {
for (String file: listing) {
String subResource = fileName + "/" + file;
URL url2 = getValidClassLoaderResource(cl, subResource);
if (url2 == null) {
throw new VertxException("Invalid resource: " + subResource);
}
unpackFromFileURL(url2, subResource, cl);
}
}
}
return cacheFile;
}
private File unpackFromJarURL(URL url, String fileName, ClassLoader cl) {
ZipFile zip = null;
try {
String path = url.getPath();
int idx1 = path.lastIndexOf(".jar!");
if (idx1 == -1) {
idx1 = path.lastIndexOf(".zip!");
}
int idx2 = path.lastIndexOf(".jar!", idx1 - 1);
if (idx2 == -1) {
idx2 = path.lastIndexOf(".zip!", idx1 - 1);
}
if (idx2 == -1) {
File file = new File(decodeURIComponent(path.substring(5, idx1 + 4), false));
zip = new ZipFile(file);
} else {
String s = path.substring(idx2 + 6, idx1 + 4);
File file = resolveFile(s);
zip = new ZipFile(file);
}
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (name.startsWith(fileName)) {
if (name.endsWith("/")) {
cache.cacheDir(name);
} else {
try (InputStream is = zip.getInputStream(entry)) {
cache.cacheFile(name, is, !enableCaching);
}
}
}
}
} catch (IOException e) {
throw new VertxException(e);
} finally {
closeQuietly(zip);
}
return cache.getFile(fileName);
}
private void closeQuietly(Closeable zip) {
if (zip != null) {
try {
zip.close();
} catch (IOException e) {
}
}
}
private boolean isBundleUrlDirectory(URL url) {
return url.toExternalForm().endsWith("/") ||
getValidClassLoaderResource(getClassLoader(), url.getPath().substring(1) + "/") != null;
}
private File unpackFromBundleURL(URL url, boolean isDir) {
String file = url.getHost() + File.separator + url.getFile();
try {
if ((getClassLoader() != null && isBundleUrlDirectory(url)) || isDir) {
cache.cacheDir(file);
} else {
try (InputStream is = url.openStream()) {
cache.cacheFile(file, is, !enableCaching);
}
}
} catch (IOException e) {
throw new VertxException(e);
}
return cache.getFile(file);
}
private ClassLoader getClassLoader() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = getClass().getClassLoader();
}
if (cl == null) {
cl = Object.class.getClassLoader();
}
return cl;
}
}