diff options
Diffstat (limited to 'src/java/com/jogamp/common/util/JarUtil.java')
-rw-r--r-- | src/java/com/jogamp/common/util/JarUtil.java | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/src/java/com/jogamp/common/util/JarUtil.java b/src/java/com/jogamp/common/util/JarUtil.java new file mode 100644 index 0000000..a2e6be5 --- /dev/null +++ b/src/java/com/jogamp/common/util/JarUtil.java @@ -0,0 +1,347 @@ +/** + * Copyright 2011 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.common.util; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.security.AccessController; +import java.security.cert.Certificate; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import com.jogamp.common.os.NativeLibrary; + +import jogamp.common.Debug; + +public class JarUtil { + private static final boolean VERBOSE = Debug.isPropertyDefined("jogamp.debug.JARUtil", true, AccessController.getContext()); + + /** + * @param clazzBinName com.jogamp.common.util.cache.TempJarCache + * @param cl + * @return jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/ + * @throws IOException + * @see {@link IOUtil#getClassURL(String, ClassLoader)} + */ + public static URL getJarURL(String clazzBinName, ClassLoader cl) throws IOException { + URL url = IOUtil.getClassURL(clazzBinName, cl); + if(null != url) { + String urlS = url.toExternalForm(); + // from + // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/util/cache/TempJarCache.class + // to + // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/ + urlS = urlS.substring(0, urlS.lastIndexOf('!')+2); // include !/ + return new URL(urlS); + } + return null; + } + + /** + * + * @param jarURL jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/util/cache/TempJarCache.class + * @return file:/usr/local/projects/JOGL/gluegen/build-x86_64/ + * @throws IOException + */ + public static URL getJarURLDirname(URL jarURL) throws IOException { + String urlS = jarURL.toExternalForm(); + // from + // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/util/cache/TempJarCache.class + // to + // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar + urlS = urlS.substring(0, urlS.lastIndexOf('!')); // exclude !/ + + // from + // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar + // to + // file:/usr/local/projects/JOGL/gluegen/build-x86_64/ + urlS = urlS.substring(4, urlS.lastIndexOf('/')+1); // include / exclude jar: + return new URL(urlS); + } + + /** + * + * @param baseUrl file:/usr/local/projects/JOGL/gluegen/build-x86_64/ + * @param jarFileName gluegen-rt.jar + * @return jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/ + * @throws IOException + */ + public static URL getJarURL(URL baseUrl, String jarFileName) throws IOException { + return new URL("jar:"+baseUrl.toExternalForm()+jarFileName+"!/"); + } + + /** + * + * @param clazzBinName com.jogamp.common.util.cache.TempJarCache + * @param cl domain + * @return JarFile containing the named class within the given ClassLoader + * @throws IOException + * @see {@link #getJarURL(String, ClassLoader)} + */ + public static JarFile getJarFile(String clazzBinName, ClassLoader cl) throws IOException { + return getJarFile(getJarURL(clazzBinName, cl), cl); + } + + /** + * + * @param jarURL jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/ + * @param cl domain + * @return JarFile as named by URL within the given ClassLoader + * @throws IOException + */ + public static JarFile getJarFile(URL jarUrl, ClassLoader cl) throws IOException { + if(null != jarUrl) { + URLConnection urlc = jarUrl.openConnection(); + if(urlc instanceof JarURLConnection) { + JarURLConnection jarConnection = (JarURLConnection)jarUrl.openConnection(); + JarFile jarFile = jarConnection.getJarFile(); + return jarFile; + } + } + return null; + } + + /** + * Return a map from native-lib-base-name to entry-name. + */ + public static Map<String, String> getNativeLibNames(JarFile jarFile) { + if (VERBOSE) { + System.err.println("getNativeLibNames: "+jarFile); + } + + Map<String,String> nameMap = new HashMap<String, String>(); + Enumeration<JarEntry> entries = jarFile.entries(); + + while (entries.hasMoreElements()) { + final JarEntry entry = entries.nextElement(); + final String entryName = entry.getName(); + final String baseName = NativeLibrary.isValidNativeLibraryName(entryName, false); + + if(null != baseName) { + nameMap.put(baseName, entryName); + } + } + + return nameMap; + } + + /** + * Extract the files of the given jar file. + * <p> + * If <code>extractNativeLibraries</code> is true, + * native libraries are added to the given <code>nativeLibMap</code> + * with the base name to temp file location.<br> + * A file is identified as a native library, + * if it's name complies with the running platform's native library naming scheme.<br> + * Root entries are favored over non root entries in case of naming collisions.<br> + * Example on a Unix like machine:<br> + * <pre> + * mylib.jar!/sub1/libsour.so -> sour (mapped, unique name) + * mylib.jar!/sub1/libsweet.so (dropped, root entry favored) + * mylib.jar!/libsweet.so -> sweet (mapped, root entry favored) + * mylib.jar!/sweet.dll -> (dropped, not a unix library name) + * </pre> + * </p> + * <p> + * In order to be compatible with Java Web Start, we need + * to extract all root entries from the jar file.<br> + * In this case, set all flags to true <code>extractNativeLibraries </code>. + * <code>extractClassFiles</code>, <code>extractOtherFiles</code>. + * </p> + * + * @param dest + * @param nativeLibMap + * @param jarFile + * @param deepDirectoryTraversal + * @param extractNativeLibraries + * @param extractClassFiles + * @param extractOtherFiles + * @return + * @throws IOException + */ + public static int extract(File dest, Map<String, String> nativeLibMap, + JarFile jarFile, + boolean extractNativeLibraries, + boolean extractClassFiles, + boolean extractOtherFiles) throws IOException { + + if (VERBOSE) { + System.err.println("extractNativeLibs: "+jarFile.getName()+" -> "+dest+ + ", extractNativeLibraries "+extractNativeLibraries+ + ", extractClassFiles"+extractClassFiles+ + ", extractOtherFiles "+extractOtherFiles); + } + int num = 0; + + Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + String entryName = entry.getName(); + + // Match entries with correct prefix and suffix (ignoring case) + final String libBaseName = NativeLibrary.isValidNativeLibraryName(entryName, false); + final boolean isNativeLib = null != libBaseName; + if(isNativeLib && !extractNativeLibraries) { + if (VERBOSE) { + System.err.println("JarEntry : " + entryName + " native-lib skipped"); + } + continue; + } + + final boolean isClassFile = entryName.endsWith(".class"); + if(isClassFile && !extractClassFiles) { + if (VERBOSE) { + System.err.println("JarEntry : " + entryName + " class-file skipped"); + } + continue; + } + + if(!isNativeLib && !isClassFile && !extractOtherFiles) { + if (VERBOSE) { + System.err.println("JarEntry : " + entryName + " other-file skipped"); + } + continue; + } + + boolean isDir = entryName.endsWith("/"); + + boolean isRootEntry = entryName.indexOf('/') == -1 && + entryName.indexOf(File.separatorChar) == -1; + + // strip prefix & suffix + final File destFile = new File(dest, entryName); + if(isDir) { + destFile.mkdir(); + if (VERBOSE) { + System.err.println("MKDIR: " + entryName + " -> " + destFile ); + } + } else { + final InputStream in = new BufferedInputStream(jarFile.getInputStream(entry)); + final OutputStream out = new BufferedOutputStream(new FileOutputStream(destFile)); + int numBytes = -1; + try { + numBytes = IOUtil.copyStream2Stream(in, out, -1); + } finally { + in.close(); + out.close(); + } + boolean addedAsNativeLib = false; + if (numBytes>0) { + num++; + if (isNativeLib && ( isRootEntry || !nativeLibMap.containsKey(libBaseName) ) ) { + nativeLibMap.put(libBaseName, destFile.getAbsolutePath()); + addedAsNativeLib = true; + } + } + if (VERBOSE) { + System.err.println("EXTRACT["+num+"]: [" + libBaseName + " -> ] " + entryName + " -> " + destFile + ": "+numBytes+" bytes, addedAsNativeLib: "+addedAsNativeLib); + } + } + } + return num; + } + + /** + * Validate the certificates for each native Lib in the jar file. + * Throws an IOException if any certificate is not valid. + * <pre> + Certificate[] appletLauncherCerts = Something.class.getProtectionDomain(). + getCodeSource().getCertificates(); + </pre> + */ + public static void validateCertificates(Certificate[] appletLauncherCerts, JarFile jarFile) + throws IOException { + + if (VERBOSE) { + System.err.println("validateCertificates:"); + } + + byte[] buf = new byte[1000]; + Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + String entryName = entry.getName(); + + if (VERBOSE) { + System.err.println("Validate JarEntry : " + entryName); + } + + if (!checkNativeCertificates(appletLauncherCerts, jarFile, entry, buf)) { + throw new IOException("Cannot validate certificate for " + entryName); + } + } + + } + + /** + * Check the certificates with the ones in the jar file + * (all must match). + */ + private static boolean checkNativeCertificates(Certificate[] launchedCerts, + JarFile jar, JarEntry entry, byte[] buf) throws IOException { + + // API states that we must read all of the data from the entry's + // InputStream in order to be able to get its certificates + + InputStream is = jar.getInputStream(entry); + while (is.read(buf) > 0) { } + is.close(); + + if (launchedCerts == null || launchedCerts.length == 0) { + throw new RuntimeException("Null certificates passed"); + } + + // Get the certificates for the JAR entry + Certificate[] nativeCerts = entry.getCertificates(); + if (nativeCerts == null || nativeCerts.length == 0) { + return false; + } + + int checked = 0; + for (int i = 0; i < launchedCerts.length; i++) { + for (int j = 0; j < nativeCerts.length; j++) { + if (nativeCerts[j].equals(launchedCerts[i])){ + checked++; + break; + } + } + } + return (checked == launchedCerts.length); + } +} |