/* * Copyright 1997-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package java.util.jar; import java.io.*; import java.lang.ref.SoftReference; import java.util.*; import java.util.zip.*; import java.security.CodeSigner; import java.security.cert.Certificate; import java.security.AccessController; import sun.security.action.GetPropertyAction; import sun.security.util.ManifestEntryVerifier; import sun.misc.SharedSecrets; /** * The JarFile class is used to read the contents of a jar file * from any file that can be opened with java.io.RandomAccessFile. * It extends the class java.util.zip.ZipFile with support * for reading an optional Manifest entry. The * Manifest can be used to specify meta-information about the * jar file and its entries. * *

Unless otherwise noted, passing a null argument to a constructor * or method in this class will cause a {@link NullPointerException} to be * thrown. * * @author David Connelly * @see Manifest * @see java.util.zip.ZipFile * @see java.util.jar.JarEntry * @since 1.2 */ public class JarFile extends ZipFile { private SoftReference manRef; private JarEntry manEntry; private JarVerifier jv; private boolean jvInitialized; private boolean verify; private boolean computedHasClassPathAttribute; private boolean hasClassPathAttribute; // Set up JavaUtilJarAccess in SharedSecrets static { SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl()); } /** * The JAR manifest file name. */ public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; /** * Creates a new JarFile to read from the specified * file name. The JarFile will be verified if * it is signed. * @param name the name of the jar file to be opened for reading * @throws IOException if an I/O error has occurred * @throws SecurityException if access to the file is denied * by the SecurityManager */ public JarFile(String name) throws IOException { this(new File(name), true, ZipFile.OPEN_READ); } /** * Creates a new JarFile to read from the specified * file name. * @param name the name of the jar file to be opened for reading * @param verify whether or not to verify the jar file if * it is signed. * @throws IOException if an I/O error has occurred * @throws SecurityException if access to the file is denied * by the SecurityManager */ public JarFile(String name, boolean verify) throws IOException { this(new File(name), verify, ZipFile.OPEN_READ); } /** * Creates a new JarFile to read from the specified * File object. The JarFile will be verified if * it is signed. * @param file the jar file to be opened for reading * @throws IOException if an I/O error has occurred * @throws SecurityException if access to the file is denied * by the SecurityManager */ public JarFile(File file) throws IOException { this(file, true, ZipFile.OPEN_READ); } /** * Creates a new JarFile to read from the specified * File object. * @param file the jar file to be opened for reading * @param verify whether or not to verify the jar file if * it is signed. * @throws IOException if an I/O error has occurred * @throws SecurityException if access to the file is denied * by the SecurityManager. */ public JarFile(File file, boolean verify) throws IOException { this(file, verify, ZipFile.OPEN_READ); } /** * Creates a new JarFile to read from the specified * File object in the specified mode. The mode argument * must be either OPEN_READ or OPEN_READ | OPEN_DELETE. * * @param file the jar file to be opened for reading * @param verify whether or not to verify the jar file if * it is signed. * @param mode the mode in which the file is to be opened * @throws IOException if an I/O error has occurred * @throws IllegalArgumentException * if the mode argument is invalid * @throws SecurityException if access to the file is denied * by the SecurityManager * @since 1.3 */ public JarFile(File file, boolean verify, int mode) throws IOException { super(file, mode); this.verify = verify; } /** * Returns the jar file manifest, or null if none. * * @return the jar file manifest, or null if none * * @throws IllegalStateException * may be thrown if the jar file has been closed */ public Manifest getManifest() throws IOException { return getManifestFromReference(); } private Manifest getManifestFromReference() throws IOException { Manifest man = manRef != null ? manRef.get() : null; if (man == null) { JarEntry manEntry = getManEntry(); // If found then load the manifest if (manEntry != null) { if (verify) { byte[] b = getBytes(manEntry); man = new Manifest(new ByteArrayInputStream(b)); if (!jvInitialized) { jv = new JarVerifier(b); } } else { man = new Manifest(super.getInputStream(manEntry)); } manRef = new SoftReference(man); } } return man; } private native String[] getMetaInfEntryNames(); /** * Returns the JarEntry for the given entry name or * null if not found. * * @param name the jar file entry name * @return the JarEntry for the given entry name or * null if not found. * * @throws IllegalStateException * may be thrown if the jar file has been closed * * @see java.util.jar.JarEntry */ public JarEntry getJarEntry(String name) { return (JarEntry)getEntry(name); } /** * Returns the ZipEntry for the given entry name or * null if not found. * * @param name the jar file entry name * @return the ZipEntry for the given entry name or * null if not found * * @throws IllegalStateException * may be thrown if the jar file has been closed * * @see java.util.zip.ZipEntry */ public ZipEntry getEntry(String name) { ZipEntry ze = super.getEntry(name); if (ze != null) { return new JarFileEntry(ze); } return null; } /** * Returns an enumeration of the zip file entries. */ public Enumeration entries() { final Enumeration enum_ = super.entries(); return new Enumeration() { public boolean hasMoreElements() { return enum_.hasMoreElements(); } public JarFileEntry nextElement() { ZipEntry ze = (ZipEntry)enum_.nextElement(); return new JarFileEntry(ze); } }; } private class JarFileEntry extends JarEntry { JarFileEntry(ZipEntry ze) { super(ze); } public Attributes getAttributes() throws IOException { Manifest man = JarFile.this.getManifest(); if (man != null) { return man.getAttributes(getName()); } else { return null; } } public Certificate[] getCertificates() { try { maybeInstantiateVerifier(); } catch (IOException e) { throw new RuntimeException(e); } if (certs == null && jv != null) { certs = jv.getCerts(getName()); } return certs == null ? null : certs.clone(); } public CodeSigner[] getCodeSigners() { try { maybeInstantiateVerifier(); } catch (IOException e) { throw new RuntimeException(e); } if (signers == null && jv != null) { signers = jv.getCodeSigners(getName()); } return signers == null ? null : signers.clone(); } } /* * Ensures that the JarVerifier has been created if one is * necessary (i.e., the jar appears to be signed.) This is done as * a quick check to avoid processing of the manifest for unsigned * jars. */ private void maybeInstantiateVerifier() throws IOException { if (jv != null) { return; } if (verify) { String[] names = getMetaInfEntryNames(); if (names != null) { for (int i = 0; i < names.length; i++) { String name = names[i].toUpperCase(Locale.ENGLISH); if (name.endsWith(".DSA") || name.endsWith(".RSA") || name.endsWith(".EC") || name.endsWith(".SF")) { // Assume since we found a signature-related file // that the jar is signed and that we therefore // need a JarVerifier and Manifest getManifest(); return; } } } // No signature-related files; don't instantiate a // verifier verify = false; } } /* * Initializes the verifier object by reading all the manifest * entries and passing them to the verifier. */ private void initializeVerifier() { ManifestEntryVerifier mev = null; // Verify "META-INF/" entries... try { String[] names = getMetaInfEntryNames(); if (names != null) { for (int i = 0; i < names.length; i++) { JarEntry e = getJarEntry(names[i]); if (!e.isDirectory()) { if (mev == null) { mev = new ManifestEntryVerifier (getManifestFromReference()); } byte[] b = getBytes(e); if (b != null && b.length > 0) { jv.beginEntry(e, mev); jv.update(b.length, b, 0, b.length, mev); jv.update(-1, null, 0, 0, mev); } } } } } catch (IOException ex) { // if we had an error parsing any blocks, just // treat the jar file as being unsigned jv = null; verify = false; } // if after initializing the verifier we have nothing // signed, we null it out. if (jv != null) { jv.doneWithMeta(); if (JarVerifier.debug != null) { JarVerifier.debug.println("done with meta!"); } if (jv.nothingToVerify()) { if (JarVerifier.debug != null) { JarVerifier.debug.println("nothing to verify!"); } jv = null; verify = false; } } } /* * Reads all the bytes for a given entry. Used to process the * META-INF files. */ private byte[] getBytes(ZipEntry ze) throws IOException { byte[] b = new byte[(int)ze.getSize()]; DataInputStream is = new DataInputStream(super.getInputStream(ze)); is.readFully(b, 0, b.length); is.close(); return b; } /** * Returns an input stream for reading the contents of the specified * zip file entry. * @param ze the zip file entry * @return an input stream for reading the contents of the specified * zip file entry * @throws ZipException if a zip file format error has occurred * @throws IOException if an I/O error has occurred * @throws SecurityException if any of the jar file entries * are incorrectly signed. * @throws IllegalStateException * may be thrown if the jar file has been closed */ public synchronized InputStream getInputStream(ZipEntry ze) throws IOException { maybeInstantiateVerifier(); if (jv == null) { return super.getInputStream(ze); } if (!jvInitialized) { initializeVerifier(); jvInitialized = true; // could be set to null after a call to // initializeVerifier if we have nothing to // verify if (jv == null) return super.getInputStream(ze); } // wrap a verifier stream around the real stream return new JarVerifier.VerifierStream( getManifestFromReference(), ze instanceof JarFileEntry ? (JarEntry) ze : getJarEntry(ze.getName()), super.getInputStream(ze), jv); } // Statics for hand-coded Boyer-Moore search in hasClassPathAttribute() // The bad character shift for "class-path" private static int[] lastOcc; // The good suffix shift for "class-path" private static int[] optoSft; // Initialize the shift arrays to search for "class-path" private static char[] src = {'c','l','a','s','s','-','p','a','t','h'}; static { lastOcc = new int[128]; optoSft = new int[10]; lastOcc[(int)'c']=1; lastOcc[(int)'l']=2; lastOcc[(int)'s']=5; lastOcc[(int)'-']=6; lastOcc[(int)'p']=7; lastOcc[(int)'a']=8; lastOcc[(int)'t']=9; lastOcc[(int)'h']=10; for (int i=0; i<9; i++) optoSft[i]=10; optoSft[9]=1; } private JarEntry getManEntry() { if (manEntry == null) { // First look up manifest entry using standard name manEntry = getJarEntry(MANIFEST_NAME); if (manEntry == null) { // If not found, then iterate through all the "META-INF/" // entries to find a match. String[] names = getMetaInfEntryNames(); if (names != null) { for (int i = 0; i < names.length; i++) { if (MANIFEST_NAME.equals( names[i].toUpperCase(Locale.ENGLISH))) { manEntry = getJarEntry(names[i]); break; } } } } } return manEntry; } // Returns true iff this jar file has a manifest with a class path // attribute. Returns false if there is no manifest or the manifest // does not contain a "Class-Path" attribute. Currently exported to // core libraries via sun.misc.SharedSecrets. boolean hasClassPathAttribute() throws IOException { if (computedHasClassPathAttribute) { return hasClassPathAttribute; } hasClassPathAttribute = false; if (!isKnownToNotHaveClassPathAttribute()) { JarEntry manEntry = getManEntry(); if (manEntry != null) { byte[] b = new byte[(int)manEntry.getSize()]; DataInputStream dis = new DataInputStream( super.getInputStream(manEntry)); dis.readFully(b, 0, b.length); dis.close(); int last = b.length - src.length; int i = 0; next: while (i<=last) { for (int j=9; j>=0; j--) { char c = (char) b[i+j]; c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c; if (c != src[j]) { i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]); continue next; } } hasClassPathAttribute = true; break; } } } computedHasClassPathAttribute = true; return hasClassPathAttribute; } private static String javaHome; private static String[] jarNames; private boolean isKnownToNotHaveClassPathAttribute() { // Optimize away even scanning of manifest for jar files we // deliver which don't have a class-path attribute. If one of // these jars is changed to include such an attribute this code // must be changed. if (javaHome == null) { javaHome = AccessController.doPrivileged( new GetPropertyAction("java.home")); } if (jarNames == null) { String[] names = new String[10]; String fileSep = File.separator; int i = 0; names[i++] = fileSep + "rt.jar"; names[i++] = fileSep + "sunrsasign.jar"; names[i++] = fileSep + "jsse.jar"; names[i++] = fileSep + "jce.jar"; names[i++] = fileSep + "charsets.jar"; names[i++] = fileSep + "dnsns.jar"; names[i++] = fileSep + "ldapsec.jar"; names[i++] = fileSep + "localedata.jar"; names[i++] = fileSep + "sunjce_provider.jar"; names[i++] = fileSep + "sunpkcs11.jar"; jarNames = names; } String name = getName(); String localJavaHome = javaHome; if (name.startsWith(localJavaHome)) { String[] names = jarNames; for (int i = 0; i < names.length; i++) { if (name.endsWith(names[i])) { return true; } } } return false; } }