| /* | |
| * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package java.util.jar; | |
| import java.util.zip.*; | |
| import java.io.*; | |
| import sun.security.util.ManifestEntryVerifier; | |
| /** | |
| * The {@code JarInputStream} class, which extends {@link ZipInputStream}, | |
| * is used to read the contents of a JAR file from an input stream. | |
| * It provides support for reading an optional | |
| * <a href="{@docRoot}/../specs/jar/jar.html#jar-manifest">Manifest</a> | |
| * entry. The {@code Manifest} can be used to store | |
| * meta-information about the JAR file and its entries. | |
| * <p> | |
| * Unless otherwise noted, passing a {@code null} argument to a constructor | |
| * or method in this class will cause a {@link NullPointerException} to be | |
| * thrown. | |
| * </p> | |
| * <h2>Accessing the Manifest</h2> | |
| * <p> | |
| * The {@link #getManifest() getManifest} method is used to return the | |
| * <a href="{@docRoot}/../specs/jar/jar.html#jar-manifest">Manifest</a> | |
| * from the entry {@code META-INF/MANIFEST.MF} when it is the first entry | |
| * in the stream (or the second entry if the first entry in the stream is | |
| * {@code META-INF/} and the second entry is {@code META-INF/MANIFEST.MF}). | |
| * </p> | |
| * <p> The {@link #getNextJarEntry()} and {@link #getNextEntry()} methods are | |
| * used to read JAR file entries from the stream. These methods skip over the | |
| * Manifest ({@code META-INF/MANIFEST.MF}) when it is at the beginning of the | |
| * stream. In other words, these methods do not return an entry for the Manifest | |
| * when the Manifest is the first entry in the stream. If the first entry is | |
| * {@code META-INF/} and the second entry is the Manifest then both are skipped | |
| * over by these methods. Whether these methods skip over the Manifest when it | |
| * appears later in the stream is not specified. | |
| * </p> | |
| * <h2>Signed JAR Files</h2> | |
| * | |
| * A {@code JarInputStream} verifies the signatures of entries in a | |
| * <a href="{@docRoot}/../specs/jar/jar.html#signed-jar-file">Signed JAR file</a> | |
| * when: | |
| * <ul> | |
| * <li> | |
| * The {@code Manifest} is the first entry in the stream (or the second | |
| * entry if the first entry in the stream is {@code META-INF/} and the | |
| * second entry is {@code META-INF/MANIFEST.MF}). | |
| * </li> | |
| * <li> | |
| * All signature-related entries immediately follow the {@code Manifest} | |
| * </li> | |
| * </ul> | |
| * <p> | |
| * Once the {@code JarEntry} has been completely verified, which is done by | |
| * reading until the end of the entry's input stream, | |
| * {@link JarEntry#getCertificates()} may be called to obtain the certificates | |
| * for this entry and {@link JarEntry#getCodeSigners()} may be called to obtain | |
| * the signers. | |
| * </p> | |
| * <p> | |
| * It is important to note that the verification process does not include validating | |
| * the signer's certificate. A caller should inspect the return value of | |
| * {@link JarEntry#getCodeSigners()} to further determine if the signature | |
| * can be trusted. | |
| * </p> | |
| * @apiNote | |
| * If a {@code JarEntry} is modified after the JAR file is signed, | |
| * a {@link SecurityException} will be thrown when the entry is read. | |
| * | |
| * @author David Connelly | |
| * @see Manifest | |
| * @see java.util.zip.ZipInputStream | |
| * @since 1.2 | |
| */ | |
| public class JarInputStream extends ZipInputStream { | |
| private Manifest man; | |
| private JarEntry first; | |
| private JarVerifier jv; | |
| private ManifestEntryVerifier mev; | |
| private final boolean doVerify; | |
| private boolean tryManifest; | |
| /** | |
| * Creates a new {@code JarInputStream} and reads the optional | |
| * manifest. If a manifest is present, also attempts to verify | |
| * the signatures if the JarInputStream is signed. | |
| * @param in the actual input stream | |
| * @throws IOException if an I/O error has occurred | |
| */ | |
| public JarInputStream(InputStream in) throws IOException { | |
| this(in, true); | |
| } | |
| /** | |
| * Creates a new {@code JarInputStream} and reads the optional | |
| * manifest. If a manifest is present and verify is true, also attempts | |
| * to verify the signatures if the JarInputStream is signed. | |
| * | |
| * @param in the actual input stream | |
| * @param verify whether or not to verify the JarInputStream if | |
| * it is signed. | |
| * @throws IOException if an I/O error has occurred | |
| */ | |
| @SuppressWarnings("this-escape") | |
| public JarInputStream(InputStream in, boolean verify) throws IOException { | |
| super(in); | |
| this.doVerify = verify; | |
| // This implementation assumes the META-INF/MANIFEST.MF entry | |
| // should be either the first or the second entry (when preceded | |
| // by the dir META-INF/). It skips the META-INF/ and then | |
| // "consumes" the MANIFEST.MF to initialize the Manifest object. | |
| JarEntry e = (JarEntry)super.getNextEntry(); | |
| if (e != null && e.getName().equalsIgnoreCase("META-INF/")) | |
| e = (JarEntry)super.getNextEntry(); | |
| first = checkManifest(e); | |
| } | |
| private JarEntry checkManifest(JarEntry e) | |
| throws IOException | |
| { | |
| if (e != null && JarFile.MANIFEST_NAME.equalsIgnoreCase(e.getName())) { | |
| man = new Manifest(); | |
| byte[] bytes = readAllBytes(); | |
| man.read(new ByteArrayInputStream(bytes)); | |
| closeEntry(); | |
| if (doVerify) { | |
| jv = new JarVerifier(e.getName(), bytes); | |
| mev = new ManifestEntryVerifier(man, jv.manifestName); | |
| } | |
| JarEntry nextEntry = (JarEntry)super.getNextEntry(); | |
| if (nextEntry != null && | |
| JarFile.MANIFEST_NAME.equalsIgnoreCase(nextEntry.getName())) { | |
| if (JarVerifier.debug != null) { | |
| JarVerifier.debug.println(JarVerifier.MULTIPLE_MANIFEST_WARNING); | |
| } | |
| jv = null; | |
| mev = null; | |
| } | |
| return nextEntry; | |
| } | |
| return e; | |
| } | |
| /** | |
| * Returns the {@code Manifest} for this JAR file when it is the first entry | |
| * in the stream (or the second entry if the first entry in the stream is | |
| * {@code META-INF/} and the second entry is {@code META-INF/MANIFEST.MF}), or | |
| * {@code null} otherwise. | |
| * | |
| * @return the {@code Manifest} for this JAR file, or | |
| * {@code null} otherwise. | |
| */ | |
| public Manifest getManifest() { | |
| return man; | |
| } | |
| /** | |
| * Reads the next ZIP file entry and positions the stream at the | |
| * beginning of the entry data. If verification has been enabled, | |
| * any invalid signature detected while positioning the stream for | |
| * the next entry will result in an exception. | |
| * @throws ZipException if a ZIP file error has occurred | |
| * @throws IOException if an I/O error has occurred | |
| * @throws SecurityException if any of the jar file entries | |
| * are incorrectly signed. | |
| */ | |
| public ZipEntry getNextEntry() throws IOException { | |
| JarEntry e; | |
| if (first == null) { | |
| e = (JarEntry)super.getNextEntry(); | |
| if (tryManifest) { | |
| e = checkManifest(e); | |
| tryManifest = false; | |
| } | |
| } else { | |
| e = first; | |
| if (first.getName().equalsIgnoreCase(JarFile.INDEX_NAME)) | |
| tryManifest = true; | |
| first = null; | |
| } | |
| if (jv != null && e != null) { | |
| // At this point, we might have parsed all the meta-inf | |
| // entries and have nothing to verify. If we have | |
| // nothing to verify, get rid of the JarVerifier object. | |
| if (jv.nothingToVerify() == true) { | |
| jv = null; | |
| mev = null; | |
| } else { | |
| jv.beginEntry(e, mev); | |
| } | |
| } | |
| return e; | |
| } | |
| /** | |
| * Reads the next JAR file entry and positions the stream at the | |
| * beginning of the entry data. If verification has been enabled, | |
| * any invalid signature detected while positioning the stream for | |
| * the next entry will result in an exception. | |
| * @return the next JAR file entry, or null if there are no more entries | |
| * @throws ZipException if a ZIP file error has occurred | |
| * @throws IOException if an I/O error has occurred | |
| * @throws SecurityException if any of the jar file entries | |
| * are incorrectly signed. | |
| */ | |
| public JarEntry getNextJarEntry() throws IOException { | |
| return (JarEntry)getNextEntry(); | |
| } | |
| /** | |
| * Reads from the current JAR entry into an array of bytes, returning the number of | |
| * inflated bytes. If {@code len} is not zero, the method blocks until some input is | |
| * available; otherwise, no bytes are read and {@code 0} is returned. | |
| * <p> | |
| * If the current entry is compressed and this method returns a nonzero | |
| * integer <i>n</i> then {@code buf[off]} | |
| * through {@code buf[off+}<i>n</i>{@code -1]} contain the uncompressed | |
| * data. The content of elements {@code buf[off+}<i>n</i>{@code ]} through | |
| * {@code buf[off+}<i>len</i>{@code -1]} is undefined, contrary to the | |
| * specification of the {@link java.io.InputStream InputStream} superclass, | |
| * so an implementation is free to modify these elements during the inflate | |
| * operation. If this method returns {@code -1} or throws an exception then | |
| * the content of {@code buf[off]} through {@code buf[off+}<i>len</i>{@code | |
| * -1]} is undefined. | |
| * <p> | |
| * If verification has been enabled, any invalid signature | |
| * on the current entry will be reported at some point before the | |
| * end of the entry is reached. | |
| * @param b the buffer into which the data is read | |
| * @param off the start offset in the destination array {@code b} | |
| * @param len the maximum number of bytes to read | |
| * @return the actual number of bytes read, or -1 if the end of the | |
| * entry is reached | |
| * @throws IndexOutOfBoundsException If {@code off} is negative, | |
| * {@code len} is negative, or {@code len} is greater than | |
| * {@code b.length - off} | |
| * @throws ZipException if a ZIP file error has occurred | |
| * @throws IOException if an I/O error has occurred | |
| * @throws SecurityException if any of the jar file entries | |
| * are incorrectly signed. | |
| */ | |
| public int read(byte[] b, int off, int len) throws IOException { | |
| int n; | |
| if (first == null) { | |
| n = super.read(b, off, len); | |
| } else { | |
| n = -1; | |
| } | |
| if (jv != null) { | |
| jv.update(n, b, off, len, mev); | |
| } | |
| return n; | |
| } | |
| /** | |
| * Creates a new {@code JarEntry} ({@code ZipEntry}) for the | |
| * specified JAR file entry name. The manifest attributes of | |
| * the specified JAR file entry name will be copied to the new | |
| * <CODE>JarEntry</CODE>. | |
| * | |
| * @param name the name of the JAR/ZIP file entry | |
| * @return the {@code JarEntry} object just created | |
| */ | |
| protected ZipEntry createZipEntry(String name) { | |
| JarEntry e = new JarEntry(name); | |
| if (man != null) { | |
| e.attr = man.getAttributes(name); | |
| } | |
| return e; | |
| } | |
| } |