diff options
Diffstat (limited to 'libjava/classpath/java/util/jar')
-rw-r--r-- | libjava/classpath/java/util/jar/Attributes.java | 629 | ||||
-rw-r--r-- | libjava/classpath/java/util/jar/JarEntry.java | 172 | ||||
-rw-r--r-- | libjava/classpath/java/util/jar/JarException.java | 77 | ||||
-rw-r--r-- | libjava/classpath/java/util/jar/JarFile.java | 981 | ||||
-rw-r--r-- | libjava/classpath/java/util/jar/JarInputStream.java | 200 | ||||
-rw-r--r-- | libjava/classpath/java/util/jar/JarOutputStream.java | 113 | ||||
-rw-r--r-- | libjava/classpath/java/util/jar/Manifest.java | 213 | ||||
-rw-r--r-- | libjava/classpath/java/util/jar/package.html | 47 |
8 files changed, 2432 insertions, 0 deletions
diff --git a/libjava/classpath/java/util/jar/Attributes.java b/libjava/classpath/java/util/jar/Attributes.java new file mode 100644 index 000000000..88800294c --- /dev/null +++ b/libjava/classpath/java/util/jar/Attributes.java @@ -0,0 +1,629 @@ +/* Attributes.java -- Represents attribute name/value pairs from a Manifest + Copyright (C) 2000, 2002, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.util.jar; + +import gnu.java.util.jar.JarUtils; + +import java.util.Collection; +import java.util.Hashtable; +import java.util.Map; +import java.util.Set; + +/** + * Represents attribute name/value pairs from a Manifest as a Map. + * The names of an attribute are represented by the + * <code>Attributes.Name</code> class and should confirm to the restrictions + * described in that class. Note that the Map interface that Attributes + * implements allows you to put names and values into the attribute that don't + * follow these restriction (and are not really Atrribute.Names, but if you do + * that it might cause undefined behaviour later). + * <p> + * If you use the constants defined in the inner class Name then you can be + * sure that you always access the right attribute names. This makes + * manipulating the Attributes more or less type safe. + * <p> + * Most of the methods are wrappers to implement the Map interface. The really + * useful and often used methods are <code>getValue(Name)</code> and + * <code>getValue(String)</code>. If you actually want to set attributes you + * may want to use the <code>putValue(String, String)</code> method + * (sorry there is no public type safe <code>putValue(Name, String)</code> + * method). + * + * @see java.util.jar.Attributes.Name + * @author Mark Wielaard (mark@klomp.org) + */ +public class Attributes + implements Cloneable, Map<Object, Object> +{ + + // Fields + + /** + * The map that holds all the attribute name/value pairs. In this + * implementation it is actually a Hashtable, but that can be different in + * other implementations. + */ + protected Map<Object, Object> map; + + // Inner class + + /** + * Represents a name of a Manifest Attribute. Defines a couple of well + * know names for the general main attributes, stand alone application + * attributes, applet attributes, extension identification attributes, + * package versioning and sealing attributes, file contents attributes, + * bean objects attribute and signing attributes. See the + * + * <p>The characters of a Name must obey the following restrictions:</p> + * + * <ul> + * <li>Must contain at least one character</li> + * <li>The first character must be alphanumeric (a-z, A-Z, 0-9)</li> + * <li>All other characters must be alphanumeric, a '-' or a '_'</li> + * </ul> + * + * <p>When comparing Names (with <code>equals</code>) all characters are + * converted to lowercase. But you can get the original case sensitive + * string with the <code>toString()</code> method.</p> + * + * <p>Most important attributes have a constant defined in this + * class. Some other attributes used in Manifest files are: + * <ul> + * <li> "Created-By" - General main attribute, tool and version + * that created this Manifest file.</li> + * <li> "Java-Bean" - Bean objects attribute, whether the entry is a Bean. + * Value is either "true" or "false".</li> + * <li> "Magic" - Signing attribute, application specific signing attribute. + * Must be understood by the manifest parser when present to validate the + * jar (entry).</li> + * </ul> + * + * @since 1.2 + * @author Mark Wielaard (mark@klomp.org) + */ + public static class Name + { + // General Main Attributes + + /** + * General main attribute - + * the version of this Manifest file. + */ + public static final Name MANIFEST_VERSION = new Name(JarUtils.MANIFEST_VERSION); + + /** + * General main attribute - + * the version of the jar file signature. + */ + public static final Name SIGNATURE_VERSION = new Name(JarUtils.SIGNATURE_VERSION); + + /** + * General main attribute - + * (relative) file paths of the libraries/classpaths that the Classes in + * this jar file depend on. Paths are separated by spaces. + */ + public static final Name CLASS_PATH = new Name("Class-Path"); + + /** + * Stand alone application attribute - + * the entry (without the .class ending) that is the main + * class of this jar file. + */ + public static final Name MAIN_CLASS = new Name("Main-Class"); + + /** + * Applet attribute - + * a list of extension libraries that the applet in this + * jar file depends on. + * For every named extension there should be some Attributes in the + * Manifest manifest file with the following Names: + * <ul> + * <li> <extension>-Extension-Name: + * unique name of the extension</li> + * <li> <extension>-Specification-Version: + * minimum specification version</li> + * <li> <extension>-Implementation-Version: + * minimum implementation version</li> + * <li> <extension>-Implementation-Vendor-Id: + * unique id of implementation vendor</li> + * <li> <extension>-Implementation-URL: + * where the latest version of the extension library can be found</li> + * </ul> + */ + public static final Name EXTENSION_LIST = new Name("Extension-List"); + + /** + * Extension identification attribute - + * the name if the extension library contained in the jar. + */ + public static final Name EXTENSION_NAME = new Name("Extension-Name"); + + /** + * Extension identification attribute - + * synonym for <code>EXTENSTION_NAME</code>. + */ + public static final Name EXTENSION_INSTALLATION = EXTENSION_NAME; + + // Package versioning and sealing attributes + + /** + * Package versioning - + * name of extension library contained in this jar. + */ + public static final Name IMPLEMENTATION_TITLE + = new Name("Implementation-Title"); + + /** + * Package versioning - + * version of the extension library contained in this jar. + */ + public static final Name IMPLEMENTATION_VERSION + = new Name("Implementation-Version"); + + /** + * Package versioning - + * name of extension library creator contained in this jar. + */ + public static final Name IMPLEMENTATION_VENDOR + = new Name("Implementation-Vendor"); + + /** + * Package versioning - + * unique id of extension library creator. + */ + public static final Name IMPLEMENTATION_VENDOR_ID + = new Name("Implementation-Vendor-Id"); + + /** + * Package versioning - + * location where this implementation can be downloaded. + */ + public static final Name IMPLEMENTATION_URL + = new Name("Implementation-URL"); + + /** + * Package versioning - + * title of the specification contained in this jar. + */ + public static final Name SPECIFICATION_TITLE + = new Name("Specification-Title"); + + /** + * Package versioning - + * version of the specification contained in this jar. + */ + public static final Name SPECIFICATION_VERSION + = new Name("Specification-Version"); + + /** + * Package versioning - + * organisation that maintains the specification contains in this + * jar. + */ + public static final Name SPECIFICATION_VENDOR + = new Name("Specification-Vendor"); + + /** + * Package sealing - + * whether (all) package(s) is(/are) sealed. Value is either "true" + * or "false". + */ + public static final Name SEALED = new Name("Sealed"); + + /** + * File contents attribute - + * Mime type and subtype for the jar entry. + */ + public static final Name CONTENT_TYPE = new Name("Content-Type"); + + /** The (lowercase) String representation of this Name */ + private final String name; + + /** The original String given to the constructor */ + private final String origName; + + // Constructor + + /** + * Creates a new Name from the given String. + * Throws an IllegalArgumentException if the given String is empty or + * contains any illegal Name characters. + * + * @param name the name of the new Name + * @exception IllegalArgumentException if name isn't a valid String + * representation of a Name + * @exception NullPointerException if name is null + */ + public Name(String name) throws IllegalArgumentException, + NullPointerException + { + // name must not be null + // this will throw a NullPointerException if it is + char chars[] = name.toCharArray(); + + // there must be at least one character + if (chars.length == 0) + throw new + IllegalArgumentException + ("There must be at least one character in a name"); + + // first character must be alphanum + char c = chars[0]; + if (!((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))) + throw new + IllegalArgumentException("First character must be alphanum"); + + // all other characters must be alphanums, '-' or '_' + for (int i = 1; i < chars.length; i++) + { + c = chars[i]; + if (!((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || (c == '-') || (c == '_'))) + throw new + IllegalArgumentException + ("Characters must be alphanums, '-' or '_'"); + } + + // Still here? Then convert to lower case and be done. + // Store the original name for toString(); + this.origName = name; + this.name = name.toLowerCase(); + } + + /** + * Returns the hash code of the (lowercase) String representation of + * this Name. + */ + public int hashCode() + { + return name.hashCode(); + } + + /** + * Checks if another object is equal to this Name object. + * Another object is equal to this Name object if it is an instance of + * Name and the (lowercase) string representation of the name is equal. + */ + public boolean equals(Object o) + { + // Quick and dirty check + if (name == o) + return true; + + try + { + // Note that the constructor already converts the strings to + // lowercase. + String otherName = ((Name) o).name; + return name.equals(otherName); + } + catch (ClassCastException cce) + { + return false; + } + catch (NullPointerException npe) + { + return false; + } + } + + /** + * Returns the string representation of this Name as given to the + * constructor (not neccesarily the lower case representation). + */ + public String toString() + { + return origName; + } + } + + // Constructors + + /** + * Creates an empty Attributes map. + */ + public Attributes() + { + map = new Hashtable(); + } + + /** + * Creates an empty Attributes map with the given initial size. + * @param size the initial size of the underlying map + */ + public Attributes(int size) + { + map = new Hashtable(size); + } + + /** + * Creates an Attributes map with the initial values taken from another + * Attributes map. + * @param attr Attributes map to take the initial values from + */ + public Attributes(Attributes attr) + { + map = new Hashtable(attr.map); + } + + // Methods + + /** + * Gets the value of an attribute name given as a String. + * + * @param name a String describing the Name to look for + * @return the value gotten from the map of null when not found + */ + public String getValue(String name) + { + return (String) get(new Name(name)); + } + + /** + * Gets the value of the given attribute name. + * + * @param name the Name to look for + * @return the value gotten from the map of null when not found + */ + public String getValue(Name name) + { + return (String) get(name); + } + + /** + * Stores an attribute name (represented by a String) and value in this + * Attributes map. + * When the (case insensitive string) name already exists the value is + * replaced and the old value is returned. + * + * @param name a (case insensitive) String representation of the attribite + * name to add/replace + * @param value the (new) value of the attribute name + * @returns the old value of the attribute name or null if it didn't exist + * yet + */ + public String putValue(String name, String value) + { + return putValue(new Name(name), value); + } + + /** + * Stores an attribute name (represented by a String) and value in this + * Attributes map. + * When the name already exists the value is replaced and the old value + * is returned. + * + * @param name the attribite name to add/replace + * @param value the (new) value of the attribute name + * @returns the old value of the attribute name or null if it didn't exist + * yet + */ + private String putValue(Name name, String value) + { + return (String) put(name, value); + } + + // Methods from Cloneable interface + + /** + * Return a clone of this attribute map. + */ + public Object clone() + { + return new Attributes(this); + } + + // Methods from Map interface + + /** + * Removes all attributes. + */ + public void clear() + { + map.clear(); + } + + /** + * Checks to see if there is an attribute with the specified name. + * XXX - what if the object is a String? + * + * @param attrName the name of the attribute to check + * @return true if there is an attribute with the specified name, false + * otherwise + */ + public boolean containsKey(Object attrName) + { + return map.containsKey(attrName); + } + + /** + * Checks to see if there is an attribute name with the specified value. + * + * @param attrValue the value of a attribute to check + * @return true if there is an attribute name with the specified value, + * false otherwise + */ + public boolean containsValue(Object attrValue) + { + return map.containsValue(attrValue); + } + + /** + * Gives a Set of attribute name and values pairs as MapEntries. + * @see java.util.Map.Entry + * @see java.util.Map#entrySet() + * + * @return a set of attribute name value pairs + */ + public Set<Map.Entry<Object, Object>> entrySet() + { + return map.entrySet(); + } + + /** + * Checks to see if two Attributes are equal. The supplied object must be + * a real instance of Attributes and contain the same attribute name/value + * pairs. + * + * @param o another Attribute object which should be checked for equality + * @return true if the object is an instance of Attributes and contains the + * same name/value pairs, false otherwise + */ + public boolean equals(Object o) + { + // quick and dirty check + if (this == o) + return true; + + try + { + return map.equals(((Attributes) o).map); + } + catch (ClassCastException cce) + { + return false; + } + catch (NullPointerException npe) + { + return false; + } + } + + /** + * Gets the value of a specified attribute name. + * XXX - what if the object is a String? + * + * @param attrName the name of the attribute we want the value of + * @return the value of the specified attribute name or null when there is + * no such attribute name + */ + public Object get(Object attrName) + { + return map.get(attrName); + } + + /** + * Returns the hashcode of the attribute name/value map. + */ + public int hashCode() + { + return map.hashCode(); + } + + /** + * Returns true if there are no attributes set, false otherwise. + */ + public boolean isEmpty() + { + return map.isEmpty(); + } + + /** + * Gives a Set of all the values of defined attribute names. + */ + public Set<Object> keySet() + { + return map.keySet(); + } + + /** + * Adds or replaces a attribute name/value pair. + * XXX - What if the name is a string? What if the name is neither a Name + * nor a String? What if the value is not a string? + * + * @param name the name of the attribute + * @param value the (new) value of the attribute + * @return the old value of the attribute or null when there was no old + * attribute with this name + */ + public Object put(Object name, Object value) + { + return map.put(name, value); + } + + /** + * Adds or replaces all attribute name/value pairs from another + * Attributes object to this one. The supplied Map must be an instance of + * Attributes. + * + * @param attr the Attributes object to merge with this one + * @exception ClassCastException if the supplied map is not an instance of + * Attributes + */ + public void putAll(Map<?, ?> attr) + { + if (!(attr instanceof Attributes)) + { + throw new + ClassCastException("Supplied Map is not an instance of Attributes"); + } + map.putAll(attr); + } + + /** + * Remove a attribute name/value pair. + * XXX - What if the name is a String? + * + * @param name the name of the attribute name/value pair to remove + * @return the old value of the attribute or null if the attribute didn't + * exist + */ + public Object remove(Object name) + { + return map.remove(name); + } + + /** + * Returns the number of defined attribute name/value pairs. + */ + public int size() + { + return map.size(); + } + + /** + * Returns all the values of the defined attribute name/value pairs as a + * Collection. + */ + public Collection<Object> values() + { + return map.values(); + } +} diff --git a/libjava/classpath/java/util/jar/JarEntry.java b/libjava/classpath/java/util/jar/JarEntry.java new file mode 100644 index 000000000..52cb2c31c --- /dev/null +++ b/libjava/classpath/java/util/jar/JarEntry.java @@ -0,0 +1,172 @@ +/* JarEntry.java - Represents an entry in a jar file + Copyright (C) 2000, 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.util.jar; + +import java.io.IOException; +import java.security.cert.Certificate; +import java.util.Set; +import java.util.zip.ZipEntry; + +/** + * Extension to a ZipEntry that contains manifest attributes and certificates. + * Both the Atrributes and the Certificates can be null when not set. + * Note that the <code>getCertificates()</code> method only returns a + * valid value after all of the data of the entry has been read. + * <p> + * There are no public methods to set the attributes or certificate of an + * Entru. Only JarEntries created by the classes in <code>java.util.jar</code> + * will have these properties set. + * + * @since 1.2 + * @author Mark Wielaard (mark@klomp.org) + */ + +public class JarEntry extends ZipEntry +{ + // (Package local) fields + + Attributes attr; + JarFile jarfile; + + // Constructors + + /** + * Creates a new JarEntry with the specified name and no attributes or + * or certificates. Calls <code>super(name)</code> so all other (zip)entry + * fields are null or -1. + * + * @param name the name of the new jar entry + * @exception NullPointerException when the supplied name is null + * @exception IllegalArgumentException when the supplied name is longer + * than 65535 bytes + */ + public JarEntry(String name) throws NullPointerException, + IllegalArgumentException + { + super(name); + attr = null; + jarfile = null; + } + + /** + * Creates a new JarEntry with the specified ZipEntry as template for + * all properties of the entry. Both attributes and certificates will be + * null. + * + * @param entry the ZipEntry whose fields should be copied + */ + public JarEntry(ZipEntry entry) + { + super(entry); + attr = null; + jarfile = null; + } + + /** + * Creates a new JarEntry with the specified JarEntry as template for + * all properties of the entry. + * + * @param entry the jarEntry whose fields should be copied + */ + public JarEntry(JarEntry entry) + { + super(entry); + try + { + attr = entry.getAttributes(); + } + catch (IOException _) + { + } + jarfile = entry.jarfile; + } + + // Methods + + /** + * Returns a copy of the Attributes set for this entry. + * When no Attributes are set in the manifest null is returned. + * + * @return a copy of the Attributes set for this entry + * @exception IOException This will never be thrown. It is here for + * binary compatibility. + */ + public Attributes getAttributes() throws IOException + { + if (attr != null) + { + return (Attributes) attr.clone(); + } + else + { + return null; + } + } + + /** + * Returns a copy of the certificates set for this entry. + * When no certificates are set or when not all data of this entry has + * been read null is returned. + * <p> + * To make sure that this call returns a valid value you must read all + * data from the JarInputStream for this entry. + * When you don't need the data for an entry but want to know the + * certificates that are set for the entry then you can skip all data by + * calling <code>skip(entry.getSize())</code> on the JarInputStream for + * the entry. + * + * @return a copy of the certificates set for this entry + */ + public Certificate[] getCertificates() + { + if (jarfile != null) + { + synchronized (jarfile) + { + if (jarfile.entryCerts != null) + { + Set certs = (Set) jarfile.entryCerts.get(getName()); + if (certs != null + && jarfile.verified.get(getName()) == Boolean.TRUE) + return (Certificate[]) certs.toArray(new Certificate[certs.size()]); + } + } + } + return null; + } +} diff --git a/libjava/classpath/java/util/jar/JarException.java b/libjava/classpath/java/util/jar/JarException.java new file mode 100644 index 000000000..d6f0634fe --- /dev/null +++ b/libjava/classpath/java/util/jar/JarException.java @@ -0,0 +1,77 @@ +/* JarException.java -- thrown to indicate an problem with a jar file + Copyright (C) 2000, 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.util.jar; + +import java.util.zip.ZipException; + +/** + * This exception is thrown to indicate an problem with a jar file. + * Note that none of the methods in the java.util.jar package actually declare + * to throw this exception, most just declare that they throw an IOException + * which is super class of JarException. + * + * @author Mark Wielaard (mark@klomp.org) + * @since 1.2 + */ +public class JarException extends ZipException +{ + /** + * Compatible with JDK 1.2+. + */ + private static final long serialVersionUID = 7159778400963954473L; + + /** + * Create a new JarException without a descriptive error message. + */ + public JarException() + { + } + + /** + * Create a new JarException with a descriptive error message indicating + * what went wrong. This message can later be retrieved by calling the + * <code>getMessage()</code> method. + * + * @param message The descriptive error message + * @see #getMessage() + */ + public JarException(String message) + { + super(message); + } +} diff --git a/libjava/classpath/java/util/jar/JarFile.java b/libjava/classpath/java/util/jar/JarFile.java new file mode 100644 index 000000000..b67c95346 --- /dev/null +++ b/libjava/classpath/java/util/jar/JarFile.java @@ -0,0 +1,981 @@ +/* JarFile.java - Representation of a jar file + Copyright (C) 2000, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.jar; + +import gnu.java.io.Base64InputStream; +import gnu.java.security.OID; +import gnu.java.security.pkcs.PKCS7SignedData; +import gnu.java.security.pkcs.SignerInfo; +import gnu.java.security.provider.Gnu; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.CRLException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +/** + * Representation of a jar file. + * <p> + * Note that this class is not a subclass of java.io.File but a subclass of + * java.util.zip.ZipFile and you can only read JarFiles with it (although + * there are constructors that take a File object). + * + * @since 1.2 + * @author Mark Wielaard (mark@klomp.org) + * @author Casey Marshall (csm@gnu.org) wrote the certificate and entry + * verification code. + */ +public class JarFile extends ZipFile +{ + // Fields + + /** The name of the manifest entry: META-INF/MANIFEST.MF */ + public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; + + /** The META-INF directory entry. */ + private static final String META_INF = "META-INF/"; + + /** The suffix for PKCS7 DSA signature entries. */ + private static final String PKCS7_DSA_SUFFIX = ".DSA"; + + /** The suffix for PKCS7 RSA signature entries. */ + private static final String PKCS7_RSA_SUFFIX = ".RSA"; + + /** The suffix for digest attributes. */ + private static final String DIGEST_KEY_SUFFIX = "-Digest"; + + /** The suffix for signature files. */ + private static final String SF_SUFFIX = ".SF"; + + /** + * The security provider to use for signature verification. + * We need a known fallback to be able to read any signed jar file + * (which might contain the user selected security provider). + * This is package-private to avoid accessor methods for inner classes. + */ + static final Gnu provider = new Gnu(); + + // Signature OIDs. + private static final OID MD2_OID = new OID("1.2.840.113549.2.2"); + private static final OID MD4_OID = new OID("1.2.840.113549.2.4"); + private static final OID MD5_OID = new OID("1.2.840.113549.2.5"); + private static final OID SHA1_OID = new OID("1.3.14.3.2.26"); + private static final OID DSA_ENCRYPTION_OID = new OID("1.2.840.10040.4.1"); + private static final OID RSA_ENCRYPTION_OID = new OID("1.2.840.113549.1.1.1"); + + /** + * The manifest of this file, if any, otherwise null. + * Read when first needed. + */ + private Manifest manifest; + + /** Whether to verify the manifest and all entries. */ + boolean verify; + + /** Whether the has already been loaded. */ + private boolean manifestRead = false; + + /** Whether the signature files have been loaded. */ + boolean signaturesRead = false; + + /** + * A map between entry names and booleans, signaling whether or + * not that entry has been verified. + * Only be accessed with lock on this JarFile*/ + HashMap verified = new HashMap(); + + /** + * A mapping from entry name to certificates, if any. + * Only accessed with lock on this JarFile. + */ + HashMap entryCerts; + + /** + * A {@link Map} of message digest algorithm names to their implementation. + * Used to reduce object (algorithm implementation) instantiation. + */ + private HashMap digestAlgorithms = new HashMap(); + + static boolean DEBUG = false; + static void debug(Object msg) + { + System.err.print(JarFile.class.getName()); + System.err.print(" >>> "); + System.err.println(msg); + } + + // Constructors + + /** + * Creates a new JarFile. All jar entries are verified (when a Manifest file + * for this JarFile exists). You need to actually open and read the complete + * jar entry (with <code>getInputStream()</code>) to check its signature. + * + * @param fileName the name of the file to open + * @exception FileNotFoundException if the fileName cannot be found + * @exception IOException if another IO exception occurs while reading + */ + public JarFile(String fileName) throws FileNotFoundException, IOException + { + this(fileName, true); + } + + /** + * Creates a new JarFile. If verify is true then all jar entries are + * verified (when a Manifest file for this JarFile exists). You need to + * actually open and read the complete jar entry + * (with <code>getInputStream()</code>) to check its signature. + * + * @param fileName the name of the file to open + * @param verify checks manifest and entries when true and a manifest + * exists, when false no checks are made + * @exception FileNotFoundException if the fileName cannot be found + * @exception IOException if another IO exception occurs while reading + */ + public JarFile(String fileName, boolean verify) throws + FileNotFoundException, IOException + { + super(fileName); + if (verify) + { + manifest = readManifest(); + verify(); + } + } + + /** + * Creates a new JarFile. All jar entries are verified (when a Manifest file + * for this JarFile exists). You need to actually open and read the complete + * jar entry (with <code>getInputStream()</code>) to check its signature. + * + * @param file the file to open as a jar file + * @exception FileNotFoundException if the file does not exits + * @exception IOException if another IO exception occurs while reading + */ + public JarFile(File file) throws FileNotFoundException, IOException + { + this(file, true); + } + + /** + * Creates a new JarFile. If verify is true then all jar entries are + * verified (when a Manifest file for this JarFile exists). You need to + * actually open and read the complete jar entry + * (with <code>getInputStream()</code>) to check its signature. + * + * @param file the file to open to open as a jar file + * @param verify checks manifest and entries when true and a manifest + * exists, when false no checks are made + * @exception FileNotFoundException if file does not exist + * @exception IOException if another IO exception occurs while reading + */ + public JarFile(File file, boolean verify) throws FileNotFoundException, + IOException + { + super(file); + if (verify) + { + manifest = readManifest(); + verify(); + } + } + + /** + * Creates a new JarFile with the indicated mode. If verify is true then + * all jar entries are verified (when a Manifest file for this JarFile + * exists). You need to actually open and read the complete jar entry + * (with <code>getInputStream()</code>) to check its signature. + * manifest and if the manifest exists and verify is true verfies it. + * + * @param file the file to open to open as a jar file + * @param verify checks manifest and entries when true and a manifest + * exists, when false no checks are made + * @param mode either ZipFile.OPEN_READ or + * (ZipFile.OPEN_READ | ZipFile.OPEN_DELETE) + * @exception FileNotFoundException if the file does not exist + * @exception IOException if another IO exception occurs while reading + * @exception IllegalArgumentException when given an illegal mode + * + * @since 1.3 + */ + public JarFile(File file, boolean verify, int mode) throws + FileNotFoundException, IOException, IllegalArgumentException + { + super(file, mode); + if (verify) + { + manifest = readManifest(); + verify(); + } + } + + // Methods + + /** + * XXX - should verify the manifest file + */ + private void verify() + { + // only check if manifest is not null + if (manifest == null) + { + verify = false; + return; + } + + verify = true; + // XXX - verify manifest + } + + /** + * Parses and returns the manifest if it exists, otherwise returns null. + */ + private Manifest readManifest() + { + try + { + ZipEntry manEntry = super.getEntry(MANIFEST_NAME); + if (manEntry != null) + { + InputStream in = super.getInputStream(manEntry); + manifestRead = true; + return new Manifest(in); + } + else + { + manifestRead = true; + return null; + } + } + catch (IOException ioe) + { + manifestRead = true; + return null; + } + } + + /** + * Returns a enumeration of all the entries in the JarFile. + * Note that also the Jar META-INF entries are returned. + * + * @exception IllegalStateException when the JarFile is already closed + */ + public Enumeration<JarEntry> entries() throws IllegalStateException + { + return new JarEnumeration(super.entries(), this); + } + + /** + * Wraps a given Zip Entries Enumeration. For every zip entry a + * JarEntry is created and the corresponding Attributes are looked up. + */ + private static class JarEnumeration implements Enumeration<JarEntry> + { + + private final Enumeration<? extends ZipEntry> entries; + private final JarFile jarfile; + + JarEnumeration(Enumeration<? extends ZipEntry> e, JarFile f) + { + entries = e; + jarfile = f; + } + + public boolean hasMoreElements() + { + return entries.hasMoreElements(); + } + + public JarEntry nextElement() + { + ZipEntry zip = (ZipEntry) entries.nextElement(); + JarEntry jar = new JarEntry(zip); + Manifest manifest; + try + { + manifest = jarfile.getManifest(); + } + catch (IOException ioe) + { + manifest = null; + } + + if (manifest != null) + { + jar.attr = manifest.getAttributes(jar.getName()); + } + + synchronized(jarfile) + { + if (jarfile.verify && !jarfile.signaturesRead) + try + { + jarfile.readSignatures(); + } + catch (IOException ioe) + { + if (JarFile.DEBUG) + { + JarFile.debug(ioe); + ioe.printStackTrace(); + } + jarfile.signaturesRead = true; // fudge it. + } + } + jar.jarfile = jarfile; + return jar; + } + } + + /** + * XXX + * It actually returns a JarEntry not a zipEntry + * @param name XXX + */ + public synchronized ZipEntry getEntry(String name) + { + ZipEntry entry = super.getEntry(name); + if (entry != null) + { + JarEntry jarEntry = new JarEntry(entry); + Manifest manifest; + try + { + manifest = getManifest(); + } + catch (IOException ioe) + { + manifest = null; + } + + if (manifest != null) + { + jarEntry.attr = manifest.getAttributes(name); + } + + if (verify && !signaturesRead) + try + { + readSignatures(); + } + catch (IOException ioe) + { + if (DEBUG) + { + debug(ioe); + ioe.printStackTrace(); + } + signaturesRead = true; + } + jarEntry.jarfile = this; + return jarEntry; + } + return null; + } + + /** + * Returns an input stream for the given entry. If configured to + * verify entries, the input stream returned will verify them while + * the stream is read, but only on the first time. + * + * @param entry The entry to get the input stream for. + * @exception ZipException XXX + * @exception IOException XXX + */ + public synchronized InputStream getInputStream(ZipEntry entry) throws + ZipException, IOException + { + // If we haven't verified the hash, do it now. + if (!verified.containsKey(entry.getName()) && verify) + { + if (DEBUG) + debug("reading and verifying " + entry); + return new EntryInputStream(entry, super.getInputStream(entry), this); + } + else + { + if (DEBUG) + debug("reading already verified entry " + entry); + if (verify && verified.get(entry.getName()) == Boolean.FALSE) + throw new ZipException("digest for " + entry + " is invalid"); + return super.getInputStream(entry); + } + } + + /** + * Returns the JarEntry that belongs to the name if such an entry + * exists in the JarFile. Returns null otherwise + * Convenience method that just casts the result from <code>getEntry</code> + * to a JarEntry. + * + * @param name the jar entry name to look up + * @return the JarEntry if it exists, null otherwise + */ + public JarEntry getJarEntry(String name) + { + return (JarEntry) getEntry(name); + } + + /** + * Returns the manifest for this JarFile or null when the JarFile does not + * contain a manifest file. + */ + public synchronized Manifest getManifest() throws IOException + { + if (!manifestRead) + manifest = readManifest(); + + return manifest; + } + + // Only called with lock on this JarFile. + // Package private for use in inner classes. + void readSignatures() throws IOException + { + Map pkcs7Dsa = new HashMap(); + Map pkcs7Rsa = new HashMap(); + Map sigFiles = new HashMap(); + + // Phase 1: Read all signature files. These contain the user + // certificates as well as the signatures themselves. + for (Enumeration e = super.entries(); e.hasMoreElements(); ) + { + ZipEntry ze = (ZipEntry) e.nextElement(); + String name = ze.getName(); + if (name.startsWith(META_INF)) + { + String alias = name.substring(META_INF.length()); + if (alias.lastIndexOf('.') >= 0) + alias = alias.substring(0, alias.lastIndexOf('.')); + + if (name.endsWith(PKCS7_DSA_SUFFIX) || name.endsWith(PKCS7_RSA_SUFFIX)) + { + if (DEBUG) + debug("reading PKCS7 info from " + name + ", alias=" + alias); + PKCS7SignedData sig = null; + try + { + sig = new PKCS7SignedData(super.getInputStream(ze)); + } + catch (CertificateException ce) + { + IOException ioe = new IOException("certificate parsing error"); + ioe.initCause(ce); + throw ioe; + } + catch (CRLException crle) + { + IOException ioe = new IOException("CRL parsing error"); + ioe.initCause(crle); + throw ioe; + } + if (name.endsWith(PKCS7_DSA_SUFFIX)) + pkcs7Dsa.put(alias, sig); + else if (name.endsWith(PKCS7_RSA_SUFFIX)) + pkcs7Rsa.put(alias, sig); + } + else if (name.endsWith(SF_SUFFIX)) + { + if (DEBUG) + debug("reading signature file for " + alias + ": " + name); + Manifest sf = new Manifest(super.getInputStream(ze)); + sigFiles.put(alias, sf); + if (DEBUG) + debug("result: " + sf); + } + } + } + + // Phase 2: verify the signatures on any signature files. + Set validCerts = new HashSet(); + Map entryCerts = new HashMap(); + for (Iterator it = sigFiles.entrySet().iterator(); it.hasNext(); ) + { + int valid = 0; + Map.Entry e = (Map.Entry) it.next(); + String alias = (String) e.getKey(); + + PKCS7SignedData sig = (PKCS7SignedData) pkcs7Dsa.get(alias); + if (sig != null) + { + Certificate[] certs = sig.getCertificates(); + Set signerInfos = sig.getSignerInfos(); + for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); ) + verify(certs, (SignerInfo) it2.next(), alias, validCerts); + } + + sig = (PKCS7SignedData) pkcs7Rsa.get(alias); + if (sig != null) + { + Certificate[] certs = sig.getCertificates(); + Set signerInfos = sig.getSignerInfos(); + for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); ) + verify(certs, (SignerInfo) it2.next(), alias, validCerts); + } + + // It isn't a signature for anything. Punt it. + if (validCerts.isEmpty()) + { + it.remove(); + continue; + } + + entryCerts.put(e.getValue(), new HashSet(validCerts)); + validCerts.clear(); + } + + // Read the manifest into a HashMap (String fileName, String entry) + // The fileName might be split into multiple lines in the manifest. + // Such additional lines will start with a space. + InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME)); + ByteArrayOutputStream baStream = new ByteArrayOutputStream(); + byte[] ba = new byte[1024]; + while (true) + { + int len = in.read(ba); + if (len < 0) + break; + baStream.write(ba, 0, len); + } + in.close(); + + HashMap hmManifestEntries = new HashMap(); + Pattern p = Pattern.compile("Name: (.+?\r?\n(?: .+?\r?\n)*)" + + ".+?-Digest: .+?\r?\n\r?\n"); + Matcher m = p.matcher(baStream.toString()); + while (m.find()) + { + String fileName = m.group(1).replaceAll("\r?\n ?", ""); + hmManifestEntries.put(fileName, m.group()); + } + + // Phase 3: verify the signature file signatures against the manifest, + // mapping the entry name to the target certificates. + this.entryCerts = new HashMap(); + for (Iterator it = entryCerts.entrySet().iterator(); it.hasNext(); ) + { + Map.Entry e = (Map.Entry) it.next(); + Manifest sigfile = (Manifest) e.getKey(); + Map entries = sigfile.getEntries(); + Set certificates = (Set) e.getValue(); + + for (Iterator it2 = entries.entrySet().iterator(); it2.hasNext(); ) + { + Map.Entry e2 = (Map.Entry) it2.next(); + String entryname = String.valueOf(e2.getKey()); + Attributes attr = (Attributes) e2.getValue(); + if (verifyHashes(entryname, attr, hmManifestEntries)) + { + if (DEBUG) + debug("entry " + entryname + " has certificates " + certificates); + Set s = (Set) this.entryCerts.get(entryname); + if (s != null) + s.addAll(certificates); + else + this.entryCerts.put(entryname, new HashSet(certificates)); + } + } + } + + signaturesRead = true; + } + + /** + * Tell if the given signer info is over the given alias's signature file, + * given one of the certificates specified. + */ + private void verify(Certificate[] certs, SignerInfo signerInfo, + String alias, Set validCerts) + { + Signature sig = null; + try + { + OID alg = signerInfo.getDigestEncryptionAlgorithmId(); + if (alg.equals(DSA_ENCRYPTION_OID)) + { + if (!signerInfo.getDigestAlgorithmId().equals(SHA1_OID)) + return; + sig = Signature.getInstance("SHA1withDSA", provider); + } + else if (alg.equals(RSA_ENCRYPTION_OID)) + { + OID hash = signerInfo.getDigestAlgorithmId(); + if (hash.equals(MD2_OID)) + sig = Signature.getInstance("md2WithRsaEncryption", provider); + else if (hash.equals(MD4_OID)) + sig = Signature.getInstance("md4WithRsaEncryption", provider); + else if (hash.equals(MD5_OID)) + sig = Signature.getInstance("md5WithRsaEncryption", provider); + else if (hash.equals(SHA1_OID)) + sig = Signature.getInstance("sha1WithRsaEncryption", provider); + else + return; + } + else + { + if (DEBUG) + debug("unsupported signature algorithm: " + alg); + return; + } + } + catch (NoSuchAlgorithmException nsae) + { + if (DEBUG) + { + debug(nsae); + nsae.printStackTrace(); + } + return; + } + ZipEntry sigFileEntry = super.getEntry(META_INF + alias + SF_SUFFIX); + if (sigFileEntry == null) + return; + for (int i = 0; i < certs.length; i++) + { + if (!(certs[i] instanceof X509Certificate)) + continue; + X509Certificate cert = (X509Certificate) certs[i]; + if (!cert.getIssuerX500Principal().equals(signerInfo.getIssuer()) || + !cert.getSerialNumber().equals(signerInfo.getSerialNumber())) + continue; + try + { + sig.initVerify(cert.getPublicKey()); + InputStream in = super.getInputStream(sigFileEntry); + if (in == null) + continue; + byte[] buf = new byte[1024]; + int len = 0; + while ((len = in.read(buf)) != -1) + sig.update(buf, 0, len); + if (sig.verify(signerInfo.getEncryptedDigest())) + { + if (DEBUG) + debug("signature for " + cert.getSubjectDN() + " is good"); + validCerts.add(cert); + } + } + catch (IOException ioe) + { + continue; + } + catch (InvalidKeyException ike) + { + continue; + } + catch (SignatureException se) + { + continue; + } + } + } + + /** + * Verifies that the digest(s) in a signature file were, in fact, made over + * the manifest entry for ENTRY. + * + * @param entry The entry name. + * @param attr The attributes from the signature file to verify. + * @param hmManifestEntries Mappings of Jar file entry names to their manifest + * entry text; i.e. the base-64 encoding of their + */ + private boolean verifyHashes(String entry, Attributes attr, + HashMap hmManifestEntries) + { + int verified = 0; + + String stringEntry = (String) hmManifestEntries.get(entry); + if (stringEntry == null) + { + if (DEBUG) + debug("could not find " + entry + " in manifest"); + return false; + } + // The bytes for ENTRY's manifest entry, which are signed in the + // signature file. + byte[] entryBytes = stringEntry.getBytes(); + + for (Iterator it = attr.entrySet().iterator(); it.hasNext(); ) + { + Map.Entry e = (Map.Entry) it.next(); + String key = String.valueOf(e.getKey()); + if (!key.endsWith(DIGEST_KEY_SUFFIX)) + continue; + String alg = key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length()); + try + { + byte[] hash = Base64InputStream.decode((String) e.getValue()); + MessageDigest md = (MessageDigest) digestAlgorithms.get(alg); + if (md == null) + { + md = MessageDigest.getInstance(alg, provider); + digestAlgorithms.put(alg, md); + } + md.reset(); + byte[] hash2 = md.digest(entryBytes); + if (DEBUG) + debug("verifying SF entry " + entry + " alg: " + md.getAlgorithm() + + " expect=" + new java.math.BigInteger(hash).toString(16) + + " comp=" + new java.math.BigInteger(hash2).toString(16)); + if (!Arrays.equals(hash, hash2)) + return false; + verified++; + } + catch (IOException ioe) + { + if (DEBUG) + { + debug(ioe); + ioe.printStackTrace(); + } + return false; + } + catch (NoSuchAlgorithmException nsae) + { + if (DEBUG) + { + debug(nsae); + nsae.printStackTrace(); + } + return false; + } + } + + // We have to find at least one valid digest. + return verified > 0; + } + + /** + * A utility class that verifies jar entries as they are read. + */ + private static class EntryInputStream extends FilterInputStream + { + private final JarFile jarfile; + private final long length; + private long pos; + private final ZipEntry entry; + private final byte[][] hashes; + private final MessageDigest[] md; + private boolean checked; + + EntryInputStream(final ZipEntry entry, + final InputStream in, + final JarFile jar) + throws IOException + { + super(in); + this.entry = entry; + this.jarfile = jar; + + length = entry.getSize(); + pos = 0; + checked = false; + + Attributes attr; + Manifest manifest = jarfile.getManifest(); + if (manifest != null) + attr = manifest.getAttributes(entry.getName()); + else + attr = null; + if (DEBUG) + debug("verifying entry " + entry + " attr=" + attr); + if (attr == null) + { + hashes = new byte[0][]; + md = new MessageDigest[0]; + } + else + { + List hashes = new LinkedList(); + List md = new LinkedList(); + for (Iterator it = attr.entrySet().iterator(); it.hasNext(); ) + { + Map.Entry e = (Map.Entry) it.next(); + String key = String.valueOf(e.getKey()); + if (key == null) + continue; + if (!key.endsWith(DIGEST_KEY_SUFFIX)) + continue; + hashes.add(Base64InputStream.decode((String) e.getValue())); + try + { + int length = key.length() - DIGEST_KEY_SUFFIX.length(); + String alg = key.substring(0, length); + md.add(MessageDigest.getInstance(alg, provider)); + } + catch (NoSuchAlgorithmException nsae) + { + IOException ioe = new IOException("no such message digest: " + key); + ioe.initCause(nsae); + throw ioe; + } + } + if (DEBUG) + debug("digests=" + md); + this.hashes = (byte[][]) hashes.toArray(new byte[hashes.size()][]); + this.md = (MessageDigest[]) md.toArray(new MessageDigest[md.size()]); + } + } + + public boolean markSupported() + { + return false; + } + + public void mark(int readLimit) + { + } + + public void reset() + { + } + + public int read() throws IOException + { + int b = super.read(); + if (b == -1) + { + eof(); + return -1; + } + for (int i = 0; i < md.length; i++) + md[i].update((byte) b); + pos++; + if (length > 0 && pos >= length) + eof(); + return b; + } + + public int read(byte[] buf, int off, int len) throws IOException + { + int count = super.read(buf, off, (int) Math.min(len, (length != 0 + ? length - pos + : Integer.MAX_VALUE))); + if (count == -1 || (length > 0 && pos >= length)) + { + eof(); + return -1; + } + for (int i = 0; i < md.length; i++) + md[i].update(buf, off, count); + pos += count; + if (length != 0 && pos >= length) + eof(); + return count; + } + + public int read(byte[] buf) throws IOException + { + return read(buf, 0, buf.length); + } + + public long skip(long bytes) throws IOException + { + byte[] b = new byte[1024]; + long amount = 0; + while (amount < bytes) + { + int l = read(b, 0, (int) Math.min(b.length, bytes - amount)); + if (l == -1) + break; + amount += l; + } + return amount; + } + + private void eof() throws IOException + { + if (checked) + return; + checked = true; + for (int i = 0; i < md.length; i++) + { + byte[] hash = md[i].digest(); + if (DEBUG) + debug("verifying " + md[i].getAlgorithm() + " expect=" + + new java.math.BigInteger(hashes[i]).toString(16) + + " comp=" + new java.math.BigInteger(hash).toString(16)); + if (!Arrays.equals(hash, hashes[i])) + { + synchronized(jarfile) + { + if (DEBUG) + debug(entry + " could NOT be verified"); + jarfile.verified.put(entry.getName(), Boolean.FALSE); + } + return; + // XXX ??? what do we do here? + // throw new ZipException("message digest mismatch"); + } + } + + synchronized(jarfile) + { + if (DEBUG) + debug(entry + " has been VERIFIED"); + jarfile.verified.put(entry.getName(), Boolean.TRUE); + } + } + } +} diff --git a/libjava/classpath/java/util/jar/JarInputStream.java b/libjava/classpath/java/util/jar/JarInputStream.java new file mode 100644 index 000000000..4de6609a5 --- /dev/null +++ b/libjava/classpath/java/util/jar/JarInputStream.java @@ -0,0 +1,200 @@ +/* JarInputStream.java - InputStream for reading jar files + Copyright (C) 2000, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.util.jar; + +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * InputStream for reading jar files. + * XXX - verification of the signatures in the Manifest file is not yet + * implemented. + * + * @since 1.2 + * @author Mark Wielaard (mark@klomp.org) + */ + +public class JarInputStream extends ZipInputStream +{ + // Fields + + /** The manifest for this file or null when there was no manifest. */ + private Manifest manifest; + + /** The first real JarEntry for this file. Used by readManifest() to store + an entry that isn't the manifest but that should be returned by + getNextEntry next time it is called. Null when no firstEntry was read + while searching for the manifest entry, or when it has already been + returned by getNextEntry(). */ + private JarEntry firstEntry; + + // Constructors + + /** + * Creates a new JarInputStream and tries to read the manifest. + * If such a manifest is present the JarInputStream tries to verify all + * the entry signatures while reading. + * + * @param in InputStream to read the jar from + * @exception IOException when an error occurs when opening or reading + */ + public JarInputStream(InputStream in) throws IOException + { + this(in, true); + } + + /** + * Creates a new JarInputStream and tries to read the manifest. + * If such a manifest is present and verify is true, the JarInputStream + * tries to verify all the entry signatures while reading. + * + * @param in InputStream to read the jar from + * @param verify whether or not to verify the manifest entries + * @exception IOException when an error occurs when opening or reading + */ + public JarInputStream(InputStream in, boolean verify) throws IOException + { + super(in); + readManifest(verify); + } + + // Methods + + /** + * Set the manifest if found. Skips all entries that start with "META-INF/" + * + * @param verify when true (and a Manifest is found) checks the Manifest, + * when false no check is performed + * @exception IOException if an error occurs while reading + */ + private void readManifest(boolean verify) throws IOException + { + firstEntry = (JarEntry) super.getNextEntry(); + while ((firstEntry != null) && + firstEntry.getName().startsWith("META-INF/")) + { + if (firstEntry.getName().equals(JarFile.MANIFEST_NAME)) + { + manifest = new Manifest(this); + } + firstEntry = (JarEntry) super.getNextEntry(); + } + + if (verify) + { + // XXX + } + } + + /** + * Creates a JarEntry for a particular name and consults the manifest + * for the Attributes of the entry. + * Used by <code>ZipEntry.getNextEntry()</code> + * + * @param name the name of the new entry + */ + protected ZipEntry createZipEntry(String name) + { + ZipEntry zipEntry = super.createZipEntry(name); + JarEntry jarEntry = new JarEntry(zipEntry); + if (manifest != null) + { + jarEntry.attr = manifest.getAttributes(name); + } + return jarEntry; + } + + /** + * Returns the Manifest for the jar file or null if there was no Manifest. + */ + public Manifest getManifest() + { + return manifest; + } + + /** + * Returns the next entry or null when there are no more entries. + * Does actually return a JarEntry, if you don't want to cast it yourself + * use <code>getNextJarEntry()</code>. Does not return any entries found + * at the beginning of the ZipFile that are special + * (those that start with "META-INF/"). + * + * @exception IOException if an IO error occurs when reading the entry + */ + public ZipEntry getNextEntry() throws IOException + { + ZipEntry entry; + if (firstEntry != null) + { + entry = firstEntry; + firstEntry = null; + } + else + { + entry = super.getNextEntry(); + } + return entry; + } + + /** + * Returns the next jar entry or null when there are no more entries. + * + * @exception IOException if an IO error occurs when reading the entry + */ + public JarEntry getNextJarEntry() throws IOException + { + return (JarEntry) getNextEntry(); + } + + /** + * XXX + * + * @param buf XXX + * @param off XXX + * @param len XXX + * @return XXX + * @exception IOException XXX + */ + public int read(byte[]buf, int off, int len) throws IOException + { + // XXX if (verify) {} + return super.read(buf, off, len); + } +} diff --git a/libjava/classpath/java/util/jar/JarOutputStream.java b/libjava/classpath/java/util/jar/JarOutputStream.java new file mode 100644 index 000000000..0ba6002cb --- /dev/null +++ b/libjava/classpath/java/util/jar/JarOutputStream.java @@ -0,0 +1,113 @@ +/* JarOutputStream.java - OutputStream for writing jar files + Copyright (C) 2000, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.util.jar; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * OutputStream for writing jar files. + * A special ZipOutputStream that can take JarEntries and can have a optional + * Manifest as first entry. + * + * @author Mark Wielaard (mark@klomp.org) + */ + +public class JarOutputStream extends ZipOutputStream +{ + // Constructors + + /** + * Creates a new JarOutputStream without a manifest entry. + * + * @param out the stream to create the new jar on + * @exception IOException if something unexpected happend + */ + public JarOutputStream(OutputStream out) throws IOException + { + this(out, null); + } + + /** + * Creates a new JarOutputStream with a manifest entry. + * The manifest will be the first entry in the jar. + * + * @param out the stream to create the new jar on + * @param man the manifest that should be put in the jar file or null + * for no manifest entry + * @exception IOException if something unexpected happend + */ + public JarOutputStream(OutputStream out, Manifest man) throws IOException + { + super(out); + if (man != null) + writeManifest(man); + } + + // Methods + + /** + * Writes the manifest to a new JarEntry in this JarOutputStream with as + * name JarFile.MANIFEST_NAME. + * + * @param manifest the non null manifest to be written + * @exception IOException if something unexpected happend + */ + private void writeManifest(Manifest manifest) throws IOException + { + // Create a new Jar Entry for the Manifest + JarEntry entry = new JarEntry(JarFile.MANIFEST_NAME); + putNextEntry(entry); + manifest.write(this); + closeEntry(); + } + + /** + * Prepares the JarOutputStream for writing the next entry. + * This implementation just calls <code>super.putNextEntry()</code>. + * + * @param entry The information for the next entry + * @exception IOException when some unexpected I/O exception occurred + */ + public void putNextEntry(ZipEntry entry) throws IOException + { + super.putNextEntry(entry); // XXX + } +} diff --git a/libjava/classpath/java/util/jar/Manifest.java b/libjava/classpath/java/util/jar/Manifest.java new file mode 100644 index 000000000..f266d823e --- /dev/null +++ b/libjava/classpath/java/util/jar/Manifest.java @@ -0,0 +1,213 @@ +/* Manifest.java -- Reads, writes and manipulates jar manifest files + Copyright (C) 2000, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.util.jar; + +import gnu.java.util.jar.JarUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Hashtable; +import java.util.Map; + +/** + * Reads, writes and manipulaties jar manifest files. + * XXX + * + * @since 1.2 + * @author Mark Wielaard (mark@klomp.org) + */ +public class Manifest implements Cloneable +{ + // Fields + + /** The main attributes of the manifest (jar file). */ + private final Attributes mainAttr; + + /** A map of atrributes for all entries described in this Manifest. */ + private final Map<String, Attributes> entries; + + // Constructors + + /** + * Creates a new empty Manifest. + */ + public Manifest() + { + mainAttr = new Attributes(); + entries = new Hashtable<String, Attributes>(); + } + + /** + * Creates a Manifest from the supplied input stream. + * + * @see #read(InputStream) + * @see #write(OutputStream) + * + * @param in the input stream to read the manifest from + * @exception IOException when an i/o exception occurs or the input stream + * does not describe a valid manifest + */ + public Manifest(InputStream in) throws IOException + { + this(); + read(in); + } + + /** + * Creates a Manifest from another Manifest. + * Makes a deep copy of the main attributes, but a shallow copy of + * the other entries. This means that you can freely add, change or remove + * the main attributes or the entries of the new manifest without effecting + * the original manifest, but adding, changing or removing attributes from + * a particular entry also changes the attributes of that entry in the + * original manifest. + * + * @see #clone() + * @param man the Manifest to copy from + */ + public Manifest(Manifest man) + { + mainAttr = new Attributes(man.getMainAttributes()); + entries = new Hashtable<String, Attributes>(man.getEntries()); + } + + // Methods + + /** + * Gets the main attributes of this Manifest. + */ + public Attributes getMainAttributes() + { + return mainAttr; + } + + /** + * Gets a map of entry Strings to Attributes for all the entries described + * in this manifest. Adding, changing or removing from this entries map + * changes the entries of this manifest. + */ + public Map<String, Attributes> getEntries() + { + return entries; + } + + /** + * Returns the Attributes associated with the Entry. + * <p> + * Implemented as: + * <code>return (Attributes)getEntries().get(entryName)</code> + * + * @param entryName the name of the entry to look up + * @return the attributes associated with the entry or null when none + */ + public Attributes getAttributes(String entryName) + { + return getEntries().get(entryName); + } + + /** + * Clears the main attributes and removes all the entries from the + * manifest. + */ + public void clear() + { + mainAttr.clear(); + entries.clear(); + } + + /** + * Read and merge a <code>Manifest</code> from the designated input stream. + * + * @param in the input stream to read from. + * @throws IOException if an I/O related exception occurs during the process. + */ + public void read(InputStream in) throws IOException + { + JarUtils.readMFManifest(getMainAttributes(), getEntries(), in); + } + + /** + * Writes the contents of this <code>Manifest</code> to the designated + * output stream. Line-endings are platform-independent and consist of the + * 2-codepoint sequence <code>0x0D</code> and <code>0x0A</code>. + * + * @param out the output stream to write this <code>Manifest</code> to. + * @throws IOException if an I/O related exception occurs during the process. + */ + public void write(OutputStream out) throws IOException + { + JarUtils.writeMFManifest(getMainAttributes(), getEntries(), out); + } + + /** + * Makes a deep copy of the main attributes, but a shallow copy of + * the other entries. This means that you can freely add, change or remove + * the main attributes or the entries of the new manifest without effecting + * the original manifest, but adding, changing or removing attributes from + * a particular entry also changes the attributes of that entry in the + * original manifest. Calls <CODE>new Manifest(this)</CODE>. + */ + public Object clone() + { + return new Manifest(this); + } + + /** + * Checks if another object is equal to this Manifest object. + * Another Object is equal to this Manifest object if it is an instance of + * Manifest and the main attributes and the entries of the other manifest + * are equal to this one. + */ + public boolean equals(Object o) + { + return (o instanceof Manifest) && + (mainAttr.equals(((Manifest) o).mainAttr)) && + (entries.equals(((Manifest) o).entries)); + } + + /** + * Calculates the hash code of the manifest. Implemented by a xor of the + * hash code of the main attributes with the hash code of the entries map. + */ + public int hashCode() + { + return mainAttr.hashCode() ^ entries.hashCode(); + } + +} diff --git a/libjava/classpath/java/util/jar/package.html b/libjava/classpath/java/util/jar/package.html new file mode 100644 index 000000000..7fd87878d --- /dev/null +++ b/libjava/classpath/java/util/jar/package.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- package.html - describes classes in java.util.jar package. + Copyright (C) 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. --> + +<html> +<head><title>GNU Classpath - java.util.jar</title></head> + +<body> +<p>Utility classes for manipulating java archives +(zip files with a manifest file with attributes).</p> + +</body> +</html> |