| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.myfaces.view.facelets.util; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.JarURLConnection; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.net.URLDecoder; |
| import java.util.Arrays; |
| import java.util.Enumeration; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.Set; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipInputStream; |
| |
| import org.apache.myfaces.util.lang.ClassUtils; |
| import java.nio.ByteBuffer; |
| import java.nio.charset.Charset; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| /** |
| * @author Jacob Hookom |
| * @author Roland Huss |
| * @author Ales Justin (ales.justin@jboss.org) |
| * @version $Id$ |
| */ |
| public final class Classpath |
| { |
| private static final Charset UTF8 = Charset.forName("UTF-8"); |
| private static final Set<String> EXCLUDED_PREFIX_SET = new HashSet<String>(Arrays.asList("rar:", "sar:")); |
| private static final Set<String> EXCLUDED_SUFFIX_SET = new HashSet<String>(Arrays.asList(".rar", ".sar")); |
| |
| private Classpath() |
| { |
| } |
| |
| public static URL[] search(String prefix, String suffix) throws IOException |
| { |
| return search(ClassUtils.getContextClassLoader(), prefix, suffix); |
| } |
| |
| public static URL[] search(ClassLoader loader, String prefix, String suffix) throws IOException |
| { |
| Set<URL> all = new LinkedHashSet<>(); |
| |
| _searchResource(all, loader, prefix, prefix, suffix); |
| _searchResource(all, loader, prefix + "MANIFEST.MF", prefix, suffix); |
| |
| URL[] urlArray = (URL[]) all.toArray(new URL[all.size()]); |
| |
| return urlArray; |
| } |
| |
| private static void _searchResource(Set<URL> result, ClassLoader loader, String resource, String prefix, |
| String suffix) throws IOException |
| { |
| for (Enumeration<URL> urls = loader.getResources(resource); urls.hasMoreElements();) |
| { |
| URL url = urls.nextElement(); |
| URLConnection conn = url.openConnection(); |
| conn.setUseCaches(false); |
| conn.setDefaultUseCaches(false); |
| |
| try (JarFile jar = (conn instanceof JarURLConnection) ? |
| ((JarURLConnection) conn).getJarFile() : _getAlternativeJarFile(url)) |
| { |
| if (jar != null) |
| { |
| _searchJar(loader, result, jar, prefix, suffix); |
| } |
| else |
| { |
| if (!_searchDir(result, new File(URLDecoder.decode(url.getFile(), "UTF-8")), suffix)) |
| { |
| _searchFromURL(result, prefix, suffix, url); |
| } |
| } |
| } |
| catch (Throwable e) |
| { |
| // This can happen if the classloader provided us a URL that it thinks exists |
| // but really doesn't. In particular, if a JAR contains META-INF/MANIFEST.MF |
| // but not META-INF/, some classloaders may incorrectly report that META-INF/ |
| // exists and we'll end up here. Just ignore this case. |
| continue; |
| } |
| } |
| } |
| |
| private static boolean _searchDir(Set<URL> result, File dir, String suffix) throws IOException |
| { |
| boolean dirExists = false; |
| if (System.getSecurityManager() != null) |
| { |
| final File finalDir = dir; |
| dirExists = (Boolean) AccessController.doPrivileged((PrivilegedAction) () -> finalDir.exists()); |
| } |
| else |
| { |
| dirExists = dir.exists(); |
| } |
| |
| if (dirExists && dir.isDirectory()) |
| { |
| File[] dirFiles = dir.listFiles(); |
| if (dirFiles != null) |
| { |
| for (File file : dirFiles) |
| { |
| String path = file.getAbsolutePath(); |
| if (file.isDirectory()) |
| { |
| _searchDir(result, file, suffix); |
| } |
| else if (path.endsWith(suffix)) |
| { |
| result.add(file.toURI().toURL()); |
| } |
| } |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Search from URL. Fall back on prefix tokens if not able to read from original url param. |
| * |
| * @param result |
| * the result urls |
| * @param prefix |
| * the current prefix |
| * @param suffix |
| * the suffix to match |
| * @param url |
| * the current url to start search |
| * @throws IOException |
| * for any error |
| */ |
| private static void _searchFromURL(Set<URL> result, String prefix, String suffix, URL url) throws IOException |
| { |
| boolean done = false; |
| |
| try (InputStream is = _getInputStream(url)) |
| { |
| if (is != null) |
| { |
| try |
| { |
| ZipInputStream zis; |
| if (is instanceof ZipInputStream) |
| { |
| zis = (ZipInputStream) is; |
| } |
| else |
| { |
| zis = new ZipInputStream(is); |
| } |
| |
| try |
| { |
| ZipEntry entry = zis.getNextEntry(); |
| // initial entry should not be null |
| // if we assume this is some inner jar |
| done = entry != null; |
| |
| while (entry != null) |
| { |
| String entryName = entry.getName(); |
| if (entryName.endsWith(suffix)) |
| { |
| result.add(new URL(url.toExternalForm() + entryName)); |
| } |
| |
| entry = zis.getNextEntry(); |
| } |
| } |
| finally |
| { |
| zis.close(); |
| } |
| } |
| catch (Exception ignore) |
| { |
| } |
| } |
| } |
| |
| if (!done && prefix.length() > 0) |
| { |
| // we add '/' at the end since join adds it as well |
| String urlString = url.toExternalForm() + '/'; |
| |
| String[] split = prefix.split("/"); |
| |
| prefix = _join(split, true); |
| |
| String end = _join(split, false); |
| urlString = urlString.substring(0, urlString.lastIndexOf(end)); |
| if (isExcludedPrefix(urlString)) |
| { |
| // excluded URL found, ignore it |
| return; |
| } |
| url = new URL(urlString); |
| |
| _searchFromURL(result, prefix, suffix, url); |
| } |
| } |
| |
| /** |
| * Join tokens, exlude last if param equals true. |
| * |
| * @param tokens |
| * the tokens |
| * @param excludeLast |
| * do we exclude last token |
| * @return joined tokens |
| */ |
| private static String _join(String[] tokens, boolean excludeLast) |
| { |
| StringBuilder join = new StringBuilder(); |
| int length = tokens.length - (excludeLast ? 1 : 0); |
| for (int i = 0; i < length; i++) |
| { |
| join.append(tokens[i]).append('/'); |
| } |
| |
| return join.toString(); |
| } |
| |
| /** |
| * Open input stream from url. Ignore any errors. |
| * |
| * @param url |
| * the url to open |
| * @return input stream or null if not possible |
| */ |
| private static InputStream _getInputStream(URL url) |
| { |
| try |
| { |
| return url.openStream(); |
| } |
| catch (Throwable t) |
| { |
| return null; |
| } |
| } |
| |
| /** |
| * For URLs to JARs that do not use JarURLConnection - allowed by the servlet spec - attempt to produce a JarFile |
| * object all the same. Known servlet engines that function like this include Weblogic and OC4J. This is not a full |
| * solution, since an unpacked WAR or EAR will not have JAR "files" as such. |
| */ |
| private static JarFile _getAlternativeJarFile(URL url) throws IOException |
| { |
| String urlFile = url.getFile(); |
| |
| // Find suffix prefixed by "!/" on Weblogic |
| int wlIndex = urlFile.indexOf("!/"); |
| // Find suffix prefixed by '!' on OC4J |
| int oc4jIndex = urlFile.indexOf('!'); |
| // Take the first found suffix |
| int separatorIndex = wlIndex == -1 && oc4jIndex == -1 ? -1 : wlIndex < oc4jIndex ? wlIndex : oc4jIndex; |
| |
| if (separatorIndex != -1) |
| { |
| String jarFileUrl = urlFile.substring(0, separatorIndex); |
| // And trim off any "file:" prefix. |
| if (jarFileUrl.startsWith("file:")) |
| { |
| jarFileUrl = jarFileUrl.substring("file:".length()); |
| } |
| // make sure this is a valid file system path by removing escaping of white-space characters, etc. |
| jarFileUrl = decodeFilesystemUrl(jarFileUrl); |
| if (isExcludedPrefix(jarFileUrl) || isExcludedSuffix(jarFileUrl)) |
| { |
| // excluded URL found, ignore it |
| return null; |
| } |
| return new JarFile(jarFileUrl); |
| } |
| |
| return null; |
| } |
| |
| private static boolean isExcludedPrefix(String url) |
| { |
| return EXCLUDED_PREFIX_SET.contains(url.substring(0, 4)); |
| } |
| |
| private static boolean isExcludedSuffix(String url) |
| { |
| int length = url.length(); |
| return EXCLUDED_SUFFIX_SET.contains(url.substring(length - 4, length)); |
| } |
| |
| private static void _searchJar(ClassLoader loader, Set<URL> result, JarFile file, String prefix, String suffix) |
| throws IOException |
| { |
| Enumeration<JarEntry> e = file.entries(); |
| while (e.hasMoreElements()) |
| { |
| try |
| { |
| String name = e.nextElement().getName(); |
| if (name.startsWith(prefix) && name.endsWith(suffix)) |
| { |
| Enumeration<URL> e2 = loader.getResources(name); |
| while (e2.hasMoreElements()) |
| { |
| result.add(e2.nextElement()); |
| } |
| } |
| } |
| catch (Throwable t) |
| { |
| // shallow |
| } |
| } |
| } |
| |
| private static String decodeFilesystemUrl(String url) |
| { |
| //borrowed from commons-io FileUtils. |
| String decoded = url; |
| if (url != null && url.indexOf('%') >= 0) |
| { |
| int n = url.length(); |
| StringBuilder buffer = new StringBuilder(); |
| ByteBuffer bytes = ByteBuffer.allocate(n); |
| for (int i = 0; i < n; ) |
| { |
| if (url.charAt(i) == '%') |
| { |
| try |
| { |
| do |
| { |
| byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16); |
| bytes.put(octet); |
| i += 3; |
| } while (i < n && url.charAt(i) == '%'); |
| continue; |
| } |
| catch (RuntimeException e) |
| { |
| // malformed percent-encoded octet, fall through and |
| // append characters literally |
| } |
| finally |
| { |
| if (bytes.position() > 0) |
| { |
| bytes.flip(); |
| buffer.append(UTF8.decode(bytes).toString()); |
| bytes.clear(); |
| } |
| } |
| } |
| buffer.append(url.charAt(i++)); |
| } |
| decoded = buffer.toString(); |
| } |
| return decoded; |
| } |
| |
| } |