diff options
Diffstat (limited to 'libjava/classpath/java/util/jar/JarFile.java')
-rw-r--r-- | libjava/classpath/java/util/jar/JarFile.java | 981 |
1 files changed, 981 insertions, 0 deletions
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); + } + } + } +} |