Merge from the pain train
[official-gcc.git] / libjava / java / util / jar / JarFile.java
blobcb2cbf4900c13ab173fe92690f2635c0321273f7
1 /* JarFile.java - Representation of a jar file
2 Copyright (C) 2000, 2003, 2004 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package java.util.jar;
41 import gnu.java.io.Base64InputStream;
42 import gnu.java.security.OID;
43 import gnu.java.security.pkcs.PKCS7SignedData;
44 import gnu.java.security.pkcs.SignerInfo;
46 import java.io.ByteArrayOutputStream;
47 import java.io.File;
48 import java.io.FileNotFoundException;
49 import java.io.FilterInputStream;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.security.InvalidKeyException;
53 import java.security.MessageDigest;
54 import java.security.NoSuchAlgorithmException;
55 import java.security.Signature;
56 import java.security.SignatureException;
57 import java.security.cert.CRLException;
58 import java.security.cert.Certificate;
59 import java.security.cert.CertificateException;
60 import java.security.cert.X509Certificate;
61 import java.util.Arrays;
62 import java.util.Enumeration;
63 import java.util.HashMap;
64 import java.util.HashSet;
65 import java.util.Iterator;
66 import java.util.LinkedList;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Set;
70 import java.util.zip.ZipEntry;
71 import java.util.zip.ZipException;
72 import java.util.zip.ZipFile;
74 /**
75 * Representation of a jar file.
76 * <p>
77 * Note that this class is not a subclass of java.io.File but a subclass of
78 * java.util.zip.ZipFile and you can only read JarFiles with it (although
79 * there are constructors that take a File object).
81 * @since 1.2
82 * @author Mark Wielaard (mark@klomp.org)
83 * @author Casey Marshall (csm@gnu.org) wrote the certificate and entry
84 * verification code.
86 public class JarFile extends ZipFile
88 // Fields
90 /** The name of the manifest entry: META-INF/MANIFEST.MF */
91 public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
93 /** The META-INF directory entry. */
94 private static final String META_INF = "META-INF/";
96 /** The suffix for PKCS7 DSA signature entries. */
97 private static final String PKCS7_DSA_SUFFIX = ".DSA";
99 /** The suffix for PKCS7 RSA signature entries. */
100 private static final String PKCS7_RSA_SUFFIX = ".RSA";
102 /** The suffix for digest attributes. */
103 private static final String DIGEST_KEY_SUFFIX = "-Digest";
105 /** The suffix for signature files. */
106 private static final String SF_SUFFIX = ".SF";
108 // Signature OIDs.
109 private static final OID MD2_OID = new OID("1.2.840.113549.2.2");
110 private static final OID MD4_OID = new OID("1.2.840.113549.2.4");
111 private static final OID MD5_OID = new OID("1.2.840.113549.2.5");
112 private static final OID SHA1_OID = new OID("1.3.14.3.2.26");
113 private static final OID DSA_ENCRYPTION_OID = new OID("1.2.840.10040.4.1");
114 private static final OID RSA_ENCRYPTION_OID = new OID("1.2.840.113549.1.1.1");
117 * The manifest of this file, if any, otherwise null.
118 * Read when first needed.
120 private Manifest manifest;
122 /** Whether to verify the manifest and all entries. */
123 boolean verify;
125 /** Whether the has already been loaded. */
126 private boolean manifestRead = false;
128 /** Whether the signature files have been loaded. */
129 boolean signaturesRead = false;
132 * A map between entry names and booleans, signaling whether or
133 * not that entry has been verified.
134 * Only be accessed with lock on this JarFile*/
135 HashMap verified = new HashMap();
138 * A mapping from entry name to certificates, if any.
139 * Only accessed with lock on this JarFile.
141 HashMap entryCerts;
143 static boolean DEBUG = false;
144 static void debug(Object msg)
146 System.err.print(JarFile.class.getName());
147 System.err.print(" >>> ");
148 System.err.println(msg);
151 // Constructors
154 * Creates a new JarFile. All jar entries are verified (when a Manifest file
155 * for this JarFile exists). You need to actually open and read the complete
156 * jar entry (with <code>getInputStream()</code>) to check its signature.
158 * @param fileName the name of the file to open
159 * @exception FileNotFoundException if the fileName cannot be found
160 * @exception IOException if another IO exception occurs while reading
162 public JarFile(String fileName) throws FileNotFoundException, IOException
164 this(fileName, true);
168 * Creates a new JarFile. If verify is true then all jar entries are
169 * verified (when a Manifest file for this JarFile exists). You need to
170 * actually open and read the complete jar entry
171 * (with <code>getInputStream()</code>) to check its signature.
173 * @param fileName the name of the file to open
174 * @param verify checks manifest and entries when true and a manifest
175 * exists, when false no checks are made
176 * @exception FileNotFoundException if the fileName cannot be found
177 * @exception IOException if another IO exception occurs while reading
179 public JarFile(String fileName, boolean verify) throws
180 FileNotFoundException, IOException
182 super(fileName);
183 if (verify)
185 manifest = readManifest();
186 verify();
191 * Creates a new JarFile. All jar entries are verified (when a Manifest file
192 * for this JarFile exists). You need to actually open and read the complete
193 * jar entry (with <code>getInputStream()</code>) to check its signature.
195 * @param file the file to open as a jar file
196 * @exception FileNotFoundException if the file does not exits
197 * @exception IOException if another IO exception occurs while reading
199 public JarFile(File file) throws FileNotFoundException, IOException
201 this(file, true);
205 * Creates a new JarFile. If verify is true then all jar entries are
206 * verified (when a Manifest file for this JarFile exists). You need to
207 * actually open and read the complete jar entry
208 * (with <code>getInputStream()</code>) to check its signature.
210 * @param file the file to open to open as a jar file
211 * @param verify checks manifest and entries when true and a manifest
212 * exists, when false no checks are made
213 * @exception FileNotFoundException if file does not exist
214 * @exception IOException if another IO exception occurs while reading
216 public JarFile(File file, boolean verify) throws FileNotFoundException,
217 IOException
219 super(file);
220 if (verify)
222 manifest = readManifest();
223 verify();
228 * Creates a new JarFile with the indicated mode. If verify is true then
229 * all jar entries are verified (when a Manifest file for this JarFile
230 * exists). You need to actually open and read the complete jar entry
231 * (with <code>getInputStream()</code>) to check its signature.
232 * manifest and if the manifest exists and verify is true verfies it.
234 * @param file the file to open to open as a jar file
235 * @param verify checks manifest and entries when true and a manifest
236 * exists, when false no checks are made
237 * @param mode either ZipFile.OPEN_READ or
238 * (ZipFile.OPEN_READ | ZipFile.OPEN_DELETE)
239 * @exception FileNotFoundException if the file does not exist
240 * @exception IOException if another IO exception occurs while reading
241 * @exception IllegalArgumentException when given an illegal mode
243 * @since 1.3
245 public JarFile(File file, boolean verify, int mode) throws
246 FileNotFoundException, IOException, IllegalArgumentException
248 super(file, mode);
249 if (verify)
251 manifest = readManifest();
252 verify();
256 // Methods
259 * XXX - should verify the manifest file
261 private void verify()
263 // only check if manifest is not null
264 if (manifest == null)
266 verify = false;
267 return;
270 verify = true;
271 // XXX - verify manifest
275 * Parses and returns the manifest if it exists, otherwise returns null.
277 private Manifest readManifest()
281 ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
282 if (manEntry != null)
284 InputStream in = super.getInputStream(manEntry);
285 manifestRead = true;
286 return new Manifest(in);
288 else
290 manifestRead = true;
291 return null;
294 catch (IOException ioe)
296 manifestRead = true;
297 return null;
302 * Returns a enumeration of all the entries in the JarFile.
303 * Note that also the Jar META-INF entries are returned.
305 * @exception IllegalStateException when the JarFile is already closed
307 public Enumeration entries() throws IllegalStateException
309 return new JarEnumeration(super.entries(), this);
313 * Wraps a given Zip Entries Enumeration. For every zip entry a
314 * JarEntry is created and the corresponding Attributes are looked up.
316 private static class JarEnumeration implements Enumeration
319 private final Enumeration entries;
320 private final JarFile jarfile;
322 JarEnumeration(Enumeration e, JarFile f)
324 entries = e;
325 jarfile = f;
328 public boolean hasMoreElements()
330 return entries.hasMoreElements();
333 public Object nextElement()
335 ZipEntry zip = (ZipEntry) entries.nextElement();
336 JarEntry jar = new JarEntry(zip);
337 Manifest manifest;
340 manifest = jarfile.getManifest();
342 catch (IOException ioe)
344 manifest = null;
347 if (manifest != null)
349 jar.attr = manifest.getAttributes(jar.getName());
352 synchronized(jarfile)
354 if (!jarfile.signaturesRead)
357 jarfile.readSignatures();
359 catch (IOException ioe)
361 if (JarFile.DEBUG)
363 JarFile.debug(ioe);
364 ioe.printStackTrace();
366 jarfile.signaturesRead = true; // fudge it.
369 // Include the certificates only if we have asserted that the
370 // signatures are valid. This means the certificates will not be
371 // available if the entry hasn't been read yet.
372 if (jarfile.entryCerts != null
373 && jarfile.verified.get(zip.getName()) == Boolean.TRUE)
375 Set certs = (Set) jarfile.entryCerts.get(jar.getName());
376 if (certs != null)
377 jar.certs = (Certificate[])
378 certs.toArray(new Certificate[certs.size()]);
381 return jar;
386 * XXX
387 * It actually returns a JarEntry not a zipEntry
388 * @param name XXX
390 public synchronized ZipEntry getEntry(String name)
392 ZipEntry entry = super.getEntry(name);
393 if (entry != null)
395 JarEntry jarEntry = new JarEntry(entry);
396 Manifest manifest;
399 manifest = getManifest();
401 catch (IOException ioe)
403 manifest = null;
406 if (manifest != null)
408 jarEntry.attr = manifest.getAttributes(name);
411 if (!signaturesRead)
414 readSignatures();
416 catch (IOException ioe)
418 if (DEBUG)
420 debug(ioe);
421 ioe.printStackTrace();
423 signaturesRead = true;
425 // See the comments in the JarEnumeration for why we do this
426 // check.
427 if (DEBUG)
428 debug("entryCerts=" + entryCerts + " verified " + name
429 + " ? " + verified.get(name));
430 if (entryCerts != null && verified.get(name) == Boolean.TRUE)
432 Set certs = (Set) entryCerts.get(name);
433 if (certs != null)
434 jarEntry.certs = (Certificate[])
435 certs.toArray(new Certificate[certs.size()]);
437 return jarEntry;
439 return null;
443 * Returns an input stream for the given entry. If configured to
444 * verify entries, the input stream returned will verify them while
445 * the stream is read, but only on the first time.
447 * @param entry The entry to get the input stream for.
448 * @exception ZipException XXX
449 * @exception IOException XXX
451 public synchronized InputStream getInputStream(ZipEntry entry) throws
452 ZipException, IOException
454 // If we haven't verified the hash, do it now.
455 if (!verified.containsKey(entry.getName()) && verify)
457 if (DEBUG)
458 debug("reading and verifying " + entry);
459 return new EntryInputStream(entry, super.getInputStream(entry), this);
461 else
463 if (DEBUG)
464 debug("reading already verified entry " + entry);
465 if (verify && verified.get(entry.getName()) == Boolean.FALSE)
466 throw new ZipException("digest for " + entry + " is invalid");
467 return super.getInputStream(entry);
472 * Returns the JarEntry that belongs to the name if such an entry
473 * exists in the JarFile. Returns null otherwise
474 * Convenience method that just casts the result from <code>getEntry</code>
475 * to a JarEntry.
477 * @param name the jar entry name to look up
478 * @return the JarEntry if it exists, null otherwise
480 public JarEntry getJarEntry(String name)
482 return (JarEntry) getEntry(name);
486 * Returns the manifest for this JarFile or null when the JarFile does not
487 * contain a manifest file.
489 public synchronized Manifest getManifest() throws IOException
491 if (!manifestRead)
492 manifest = readManifest();
494 return manifest;
497 // Only called with lock on this JarFile.
498 private void readSignatures() throws IOException
500 Map pkcs7Dsa = new HashMap();
501 Map pkcs7Rsa = new HashMap();
502 Map sigFiles = new HashMap();
504 // Phase 1: Read all signature files. These contain the user
505 // certificates as well as the signatures themselves.
506 for (Enumeration e = super.entries(); e.hasMoreElements(); )
508 ZipEntry ze = (ZipEntry) e.nextElement();
509 String name = ze.getName();
510 if (name.startsWith(META_INF))
512 String alias = name.substring(META_INF.length());
513 if (alias.lastIndexOf('.') >= 0)
514 alias = alias.substring(0, alias.lastIndexOf('.'));
516 if (name.endsWith(PKCS7_DSA_SUFFIX) || name.endsWith(PKCS7_RSA_SUFFIX))
518 if (DEBUG)
519 debug("reading PKCS7 info from " + name + ", alias=" + alias);
520 PKCS7SignedData sig = null;
523 sig = new PKCS7SignedData(super.getInputStream(ze));
525 catch (CertificateException ce)
527 IOException ioe = new IOException("certificate parsing error");
528 ioe.initCause(ce);
529 throw ioe;
531 catch (CRLException crle)
533 IOException ioe = new IOException("CRL parsing error");
534 ioe.initCause(crle);
535 throw ioe;
537 if (name.endsWith(PKCS7_DSA_SUFFIX))
538 pkcs7Dsa.put(alias, sig);
539 else if (name.endsWith(PKCS7_RSA_SUFFIX))
540 pkcs7Rsa.put(alias, sig);
542 else if (name.endsWith(SF_SUFFIX))
544 if (DEBUG)
545 debug("reading signature file for " + alias + ": " + name);
546 Manifest sf = new Manifest(super.getInputStream(ze));
547 sigFiles.put(alias, sf);
548 if (DEBUG)
549 debug("result: " + sf);
554 // Phase 2: verify the signatures on any signature files.
555 Set validCerts = new HashSet();
556 Map entryCerts = new HashMap();
557 for (Iterator it = sigFiles.entrySet().iterator(); it.hasNext(); )
559 int valid = 0;
560 Map.Entry e = (Map.Entry) it.next();
561 String alias = (String) e.getKey();
563 PKCS7SignedData sig = (PKCS7SignedData) pkcs7Dsa.get(alias);
564 if (sig != null)
566 Certificate[] certs = sig.getCertificates();
567 Set signerInfos = sig.getSignerInfos();
568 for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); )
569 verify(certs, (SignerInfo) it2.next(), alias, validCerts);
572 sig = (PKCS7SignedData) pkcs7Rsa.get(alias);
573 if (sig != null)
575 Certificate[] certs = sig.getCertificates();
576 Set signerInfos = sig.getSignerInfos();
577 for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); )
578 verify(certs, (SignerInfo) it2.next(), alias, validCerts);
581 // It isn't a signature for anything. Punt it.
582 if (validCerts.isEmpty())
584 it.remove();
585 continue;
588 entryCerts.put(e.getValue(), new HashSet(validCerts));
589 validCerts.clear();
592 // Phase 3: verify the signature file signatures against the manifest,
593 // mapping the entry name to the target certificates.
594 this.entryCerts = new HashMap();
595 for (Iterator it = entryCerts.entrySet().iterator(); it.hasNext(); )
597 Map.Entry e = (Map.Entry) it.next();
598 Manifest sigfile = (Manifest) e.getKey();
599 Map entries = sigfile.getEntries();
600 Set certificates = (Set) e.getValue();
602 for (Iterator it2 = entries.entrySet().iterator(); it2.hasNext(); )
604 Map.Entry e2 = (Map.Entry) it2.next();
605 String entryname = String.valueOf(e2.getKey());
606 Attributes attr = (Attributes) e2.getValue();
607 if (verifyHashes(entryname, attr))
609 if (DEBUG)
610 debug("entry " + entryname + " has certificates " + certificates);
611 Set s = (Set) this.entryCerts.get(entryname);
612 if (s != null)
613 s.addAll(certificates);
614 else
615 this.entryCerts.put(entryname, new HashSet(certificates));
620 signaturesRead = true;
624 * Tell if the given signer info is over the given alias's signature file,
625 * given one of the certificates specified.
627 private void verify(Certificate[] certs, SignerInfo signerInfo,
628 String alias, Set validCerts)
630 Signature sig = null;
633 OID alg = signerInfo.getDigestEncryptionAlgorithmId();
634 if (alg.equals(DSA_ENCRYPTION_OID))
636 if (!signerInfo.getDigestAlgorithmId().equals(SHA1_OID))
637 return;
638 sig = Signature.getInstance("SHA1withDSA");
640 else if (alg.equals(RSA_ENCRYPTION_OID))
642 OID hash = signerInfo.getDigestAlgorithmId();
643 if (hash.equals(MD2_OID))
644 sig = Signature.getInstance("md2WithRsaEncryption");
645 else if (hash.equals(MD4_OID))
646 sig = Signature.getInstance("md4WithRsaEncryption");
647 else if (hash.equals(MD5_OID))
648 sig = Signature.getInstance("md5WithRsaEncryption");
649 else if (hash.equals(SHA1_OID))
650 sig = Signature.getInstance("sha1WithRsaEncryption");
651 else
652 return;
654 else
656 if (DEBUG)
657 debug("unsupported signature algorithm: " + alg);
658 return;
661 catch (NoSuchAlgorithmException nsae)
663 if (DEBUG)
665 debug(nsae);
666 nsae.printStackTrace();
668 return;
670 ZipEntry sigFileEntry = super.getEntry(META_INF + alias + SF_SUFFIX);
671 if (sigFileEntry == null)
672 return;
673 for (int i = 0; i < certs.length; i++)
675 if (!(certs[i] instanceof X509Certificate))
676 continue;
677 X509Certificate cert = (X509Certificate) certs[i];
678 if (!cert.getIssuerX500Principal().equals(signerInfo.getIssuer()) ||
679 !cert.getSerialNumber().equals(signerInfo.getSerialNumber()))
680 continue;
683 sig.initVerify(cert.getPublicKey());
684 InputStream in = super.getInputStream(sigFileEntry);
685 if (in == null)
686 continue;
687 byte[] buf = new byte[1024];
688 int len = 0;
689 while ((len = in.read(buf)) != -1)
690 sig.update(buf, 0, len);
691 if (sig.verify(signerInfo.getEncryptedDigest()))
693 if (DEBUG)
694 debug("signature for " + cert.getSubjectDN() + " is good");
695 validCerts.add(cert);
698 catch (IOException ioe)
700 continue;
702 catch (InvalidKeyException ike)
704 continue;
706 catch (SignatureException se)
708 continue;
714 * Verifies that the digest(s) in a signature file were, in fact, made
715 * over the manifest entry for ENTRY.
717 * @param entry The entry name.
718 * @param attr The attributes from the signature file to verify.
720 private boolean verifyHashes(String entry, Attributes attr)
722 int verified = 0;
724 // The bytes for ENTRY's manifest entry, which are signed in the
725 // signature file.
726 byte[] entryBytes = null;
729 ZipEntry e = super.getEntry(entry);
730 if (e == null)
732 if (DEBUG)
733 debug("verifyHashes: no entry '" + entry + "'");
734 return false;
736 entryBytes = readManifestEntry(e);
738 catch (IOException ioe)
740 if (DEBUG)
742 debug(ioe);
743 ioe.printStackTrace();
745 return false;
748 for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
750 Map.Entry e = (Map.Entry) it.next();
751 String key = String.valueOf(e.getKey());
752 if (!key.endsWith(DIGEST_KEY_SUFFIX))
753 continue;
754 String alg = key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length());
757 byte[] hash = Base64InputStream.decode((String) e.getValue());
758 MessageDigest md = MessageDigest.getInstance(alg);
759 md.update(entryBytes);
760 byte[] hash2 = md.digest();
761 if (DEBUG)
762 debug("verifying SF entry " + entry + " alg: " + md.getAlgorithm()
763 + " expect=" + new java.math.BigInteger(hash).toString(16)
764 + " comp=" + new java.math.BigInteger(hash2).toString(16));
765 if (!Arrays.equals(hash, hash2))
766 return false;
767 verified++;
769 catch (IOException ioe)
771 if (DEBUG)
773 debug(ioe);
774 ioe.printStackTrace();
776 return false;
778 catch (NoSuchAlgorithmException nsae)
780 if (DEBUG)
782 debug(nsae);
783 nsae.printStackTrace();
785 return false;
789 // We have to find at least one valid digest.
790 return verified > 0;
794 * Read the raw bytes that comprise a manifest entry. We can't use the
795 * Manifest object itself, because that loses information (such as line
796 * endings, and order of entries).
798 private byte[] readManifestEntry(ZipEntry entry) throws IOException
800 InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME));
801 ByteArrayOutputStream out = new ByteArrayOutputStream();
802 byte[] target = ("Name: " + entry.getName()).getBytes();
803 int t = 0, c, prev = -1, state = 0, l = -1;
805 while ((c = in.read()) != -1)
807 // if (DEBUG)
808 // debug("read "
809 // + (c == '\n' ? "\\n" : (c == '\r' ? "\\r" : String.valueOf((char) c)))
810 // + " state=" + state + " prev="
811 // + (prev == '\n' ? "\\n" : (prev == '\r' ? "\\r" : String.valueOf((char) prev)))
812 // + " t=" + t + (t < target.length ? (" target[t]=" + (char) target[t]) : "")
813 // + " l=" + l);
814 switch (state)
817 // Step 1: read until we find the "target" bytes: the start
818 // of the entry we need to read.
819 case 0:
820 if (((byte) c) != target[t])
821 t = 0;
822 else
824 t++;
825 if (t == target.length)
827 out.write(target);
828 state = 1;
831 break;
833 // Step 2: assert that there is a newline character after
834 // the "target" bytes.
835 case 1:
836 if (c != '\n' && c != '\r')
838 out.reset();
839 t = 0;
840 state = 0;
842 else
844 out.write(c);
845 state = 2;
847 break;
849 // Step 3: read this whole entry, until we reach an empty
850 // line.
851 case 2:
852 if (c == '\n')
854 out.write(c);
855 // NL always terminates a line.
856 if (l == 0 || (l == 1 && prev == '\r'))
857 return out.toByteArray();
858 l = 0;
860 else
862 // Here we see a blank line terminated by a CR,
863 // followed by the next entry. Technically, `c' should
864 // always be 'N' at this point.
865 if (l == 1 && prev == '\r')
866 return out.toByteArray();
867 out.write(c);
868 l++;
870 prev = c;
871 break;
873 default:
874 throw new RuntimeException("this statement should be unreachable");
878 // The last entry, with a single CR terminating the line.
879 if (state == 2 && prev == '\r' && l == 0)
880 return out.toByteArray();
882 // We should not reach this point, we didn't find the entry (or, possibly,
883 // it is the last entry and is malformed).
884 throw new IOException("could not find " + entry + " in manifest");
888 * A utility class that verifies jar entries as they are read.
890 private static class EntryInputStream extends FilterInputStream
892 private final JarFile jarfile;
893 private final long length;
894 private long pos;
895 private final ZipEntry entry;
896 private final byte[][] hashes;
897 private final MessageDigest[] md;
898 private boolean checked;
900 EntryInputStream(final ZipEntry entry,
901 final InputStream in,
902 final JarFile jar)
903 throws IOException
905 super(in);
906 this.entry = entry;
907 this.jarfile = jar;
909 length = entry.getSize();
910 pos = 0;
911 checked = false;
913 Attributes attr;
914 Manifest manifest = jarfile.getManifest();
915 if (manifest != null)
916 attr = manifest.getAttributes(entry.getName());
917 else
918 attr = null;
919 if (DEBUG)
920 debug("verifying entry " + entry + " attr=" + attr);
921 if (attr == null)
923 hashes = new byte[0][];
924 md = new MessageDigest[0];
926 else
928 List hashes = new LinkedList();
929 List md = new LinkedList();
930 for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
932 Map.Entry e = (Map.Entry) it.next();
933 String key = String.valueOf(e.getKey());
934 if (key == null)
935 continue;
936 if (!key.endsWith(DIGEST_KEY_SUFFIX))
937 continue;
938 hashes.add(Base64InputStream.decode((String) e.getValue()));
941 md.add(MessageDigest.getInstance
942 (key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length())));
944 catch (NoSuchAlgorithmException nsae)
946 IOException ioe = new IOException("no such message digest: " + key);
947 ioe.initCause(nsae);
948 throw ioe;
951 if (DEBUG)
952 debug("digests=" + md);
953 this.hashes = (byte[][]) hashes.toArray(new byte[hashes.size()][]);
954 this.md = (MessageDigest[]) md.toArray(new MessageDigest[md.size()]);
958 public boolean markSupported()
960 return false;
963 public void mark(int readLimit)
967 public void reset()
971 public int read() throws IOException
973 int b = super.read();
974 if (b == -1)
976 eof();
977 return -1;
979 for (int i = 0; i < md.length; i++)
980 md[i].update((byte) b);
981 pos++;
982 if (length > 0 && pos >= length)
983 eof();
984 return b;
987 public int read(byte[] buf, int off, int len) throws IOException
989 int count = super.read(buf, off, (int) Math.min(len, (length != 0
990 ? length - pos
991 : Integer.MAX_VALUE)));
992 if (count == -1 || (length > 0 && pos >= length))
994 eof();
995 return -1;
997 for (int i = 0; i < md.length; i++)
998 md[i].update(buf, off, count);
999 pos += count;
1000 if (length != 0 && pos >= length)
1001 eof();
1002 return count;
1005 public int read(byte[] buf) throws IOException
1007 return read(buf, 0, buf.length);
1010 public long skip(long bytes) throws IOException
1012 byte[] b = new byte[1024];
1013 long amount = 0;
1014 while (amount < bytes)
1016 int l = read(b, 0, (int) Math.min(b.length, bytes - amount));
1017 if (l == -1)
1018 break;
1019 amount += l;
1021 return amount;
1024 private void eof() throws IOException
1026 if (checked)
1027 return;
1028 checked = true;
1029 for (int i = 0; i < md.length; i++)
1031 byte[] hash = md[i].digest();
1032 if (DEBUG)
1033 debug("verifying " + md[i].getAlgorithm() + " expect="
1034 + new java.math.BigInteger(hashes[i]).toString(16)
1035 + " comp=" + new java.math.BigInteger(hash).toString(16));
1036 if (!Arrays.equals(hash, hashes[i]))
1038 synchronized(jarfile)
1040 if (DEBUG)
1041 debug(entry + " could NOT be verified");
1042 jarfile.verified.put(entry.getName(), Boolean.FALSE);
1044 return;
1045 // XXX ??? what do we do here?
1046 // throw new ZipException("message digest mismatch");
1050 synchronized(jarfile)
1052 if (DEBUG)
1053 debug(entry + " has been VERIFIED");
1054 jarfile.verified.put(entry.getName(), Boolean.TRUE);