/* * Copyright (c) 1997, 2020, 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 jdk.internal.access.SharedSecrets; import jdk.internal.access.JavaUtilZipFileAccess; import sun.security.action.GetPropertyAction; import sun.security.util.ManifestEntryVerifier; import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.net.URL; import java.security.CodeSigner; import java.security.CodeSource; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Locale; import java.util.NoSuchElementException; import java.util.Objects; import java.util.function.Function; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; /** * The {@code JarFile} class is used to read the contents of a jar file * from any file that can be opened with {@code java.io.RandomAccessFile}. * It extends the class {@code java.util.zip.ZipFile} with support * for reading an optional {@code Manifest} entry, and support for * processing multi-release jar files. The {@code Manifest} can be used * to specify meta-information about the jar file and its entries. * *
A multi-release jar file is a jar file that * contains a manifest with a main attribute named "Multi-Release", * a set of "base" entries, some of which are public classes with public * or protected methods that comprise the public interface of the jar file, * and a set of "versioned" entries contained in subdirectories of the * "META-INF/versions" directory. The versioned entries are partitioned by the * major version of the Java platform. A versioned entry, with a version * {@code n}, {@code 8 < n}, in the "META-INF/versions/{n}" directory overrides * the base entry as well as any entry with a version number {@code i} where * {@code 8 < i < n}. * *
By default, a {@code JarFile} for a multi-release jar file is configured * to process the multi-release jar file as if it were a plain (unversioned) jar * file, and as such an entry name is associated with at most one base entry. * The {@code JarFile} may be configured to process a multi-release jar file by * creating the {@code JarFile} with the * {@link JarFile#JarFile(File, boolean, int, Runtime.Version)} constructor. The * {@code Runtime.Version} object sets a maximum version used when searching for * versioned entries. When so configured, an entry name * can correspond with at most one base entry and zero or more versioned * entries. A search is required to associate the entry name with the latest * versioned entry whose version is less than or equal to the maximum version * (see {@link #getEntry(String)}). * *
Class loaders that utilize {@code JarFile} to load classes from the * contents of {@code JarFile} entries should construct the {@code JarFile} * by invoking the {@link JarFile#JarFile(File, boolean, int, Runtime.Version)} * constructor with the value {@code Runtime.version()} assigned to the last * argument. This assures that classes compatible with the major * version of the running JVM are loaded from multi-release jar files. * *
If the {@code verify} flag is on when opening a signed jar file, the content * of the jar entry is verified against the signature embedded inside the manifest * that is associated with its {@link JarEntry#getRealName() path name}. For a * multi-release jar file, the content of a versioned entry is verfieid against * its own signature and {@link JarEntry#getCodeSigners()} returns its own signers. * * Please 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. * *
Unless otherwise noted, passing a {@code null} argument to a constructor * or method in this class will cause a {@link NullPointerException} to be * thrown. * * @implNote *
* By default the feature version number of the returned {@code Version} will * be equal to the feature version number of {@code Runtime.version()}. * However, if the {@code jdk.util.jar.version} property is set, the * returned {@code Version} is derived from that property and feature version * numbers may not be equal. * * @return the version that represents the runtime versioned configuration * * @since 9 */ public static Runtime.Version runtimeVersion() { return RUNTIME_VERSION; } /** * Creates a new {@code JarFile} to read from the specified * file {@code name}. The {@code JarFile} will be verified if * it is signed. * @param name the name of the jar file to be opened for reading * @throws IOException if an I/O error has occurred * @throws SecurityException if access to the file is denied * by the SecurityManager */ public JarFile(String name) throws IOException { this(new File(name), true, ZipFile.OPEN_READ); } /** * Creates a new {@code JarFile} to read from the specified * file {@code name}. * @param name the name of the jar file to be opened for reading * @param verify whether or not to verify the jar file if * it is signed. * @throws IOException if an I/O error has occurred * @throws SecurityException if access to the file is denied * by the SecurityManager */ public JarFile(String name, boolean verify) throws IOException { this(new File(name), verify, ZipFile.OPEN_READ); } /** * Creates a new {@code JarFile} to read from the specified * {@code File} object. The {@code JarFile} will be verified if * it is signed. * @param file the jar file to be opened for reading * @throws IOException if an I/O error has occurred * @throws SecurityException if access to the file is denied * by the SecurityManager */ public JarFile(File file) throws IOException { this(file, true, ZipFile.OPEN_READ); } /** * Creates a new {@code JarFile} to read from the specified * {@code File} object. * @param file the jar file to be opened for reading * @param verify whether or not to verify the jar file if * it is signed. * @throws IOException if an I/O error has occurred * @throws SecurityException if access to the file is denied * by the SecurityManager. */ public JarFile(File file, boolean verify) throws IOException { this(file, verify, ZipFile.OPEN_READ); } /** * Creates a new {@code JarFile} to read from the specified * {@code File} object in the specified mode. The mode argument * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. * * @param file the jar file to be opened for reading * @param verify whether or not to verify the jar file if * it is signed. * @param mode the mode in which the file is to be opened * @throws IOException if an I/O error has occurred * @throws IllegalArgumentException * if the {@code mode} argument is invalid * @throws SecurityException if access to the file is denied * by the SecurityManager * @since 1.3 */ public JarFile(File file, boolean verify, int mode) throws IOException { this(file, verify, mode, BASE_VERSION); } /** * Creates a new {@code JarFile} to read from the specified * {@code File} object in the specified mode. The mode argument * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. * The version argument, after being converted to a canonical form, is * used to configure the {@code JarFile} for processing * multi-release jar files. *
* The canonical form derived from the version parameter is * {@code Runtime.Version.parse(Integer.toString(n))} where {@code n} is * {@code Math.max(version.feature(), JarFile.baseVersion().feature())}. * * @param file the jar file to be opened for reading * @param verify whether or not to verify the jar file if * it is signed. * @param mode the mode in which the file is to be opened * @param version specifies the release version for a multi-release jar file * @throws IOException if an I/O error has occurred * @throws IllegalArgumentException * if the {@code mode} argument is invalid * @throws SecurityException if access to the file is denied * by the SecurityManager * @throws NullPointerException if {@code version} is {@code null} * @since 9 */ public JarFile(File file, boolean verify, int mode, Runtime.Version version) throws IOException { super(file, mode); this.verify = verify; Objects.requireNonNull(version); if (MULTI_RELEASE_FORCED || version.feature() == RUNTIME_VERSION.feature()) { // This deals with the common case where the value from JarFile.runtimeVersion() is passed this.version = RUNTIME_VERSION; } else if (version.feature() <= BASE_VERSION_FEATURE) { // This also deals with the common case where the value from JarFile.baseVersion() is passed this.version = BASE_VERSION; } else { // Canonicalize this.version = Runtime.Version.parse(Integer.toString(version.feature())); } this.versionFeature = this.version.feature(); } /** * Returns the maximum version used when searching for versioned entries. *
* If this {@code JarFile} is not a multi-release jar file or is not * configured to be processed as such, then the version returned will be the * same as that returned from {@link #baseVersion()}. * * @return the maximum version * @since 9 */ public final Runtime.Version getVersion() { return isMultiRelease() ? this.version : BASE_VERSION; } /** * Indicates whether or not this jar file is a multi-release jar file. * * @return true if this JarFile is a multi-release jar file * @since 9 */ public final boolean isMultiRelease() { if (isMultiRelease) { return true; } if (MULTI_RELEASE_ENABLED) { try { checkForSpecialAttributes(); } catch (IOException io) { isMultiRelease = false; } } return isMultiRelease; } /** * Returns the jar file manifest, or {@code null} if none. * * @return the jar file manifest, or {@code null} if none * * @throws IllegalStateException * may be thrown if the jar file has been closed * @throws IOException if an I/O error has occurred */ public Manifest getManifest() throws IOException { return getManifestFromReference(); } private Manifest getManifestFromReference() throws IOException { Manifest man = manRef != null ? manRef.get() : null; if (man == null) { JarEntry manEntry = getManEntry(); // If found then load the manifest if (manEntry != null) { if (verify) { byte[] b = getBytes(manEntry); if (!jvInitialized) { jv = new JarVerifier(b); } man = new Manifest(jv, new ByteArrayInputStream(b), getName()); } else { try (InputStream is = super.getInputStream(manEntry)) { man = new Manifest(is, getName()); } } manRef = new SoftReference<>(man); } } return man; } /** * Returns the {@code JarEntry} for the given base entry name or * {@code null} if not found. * *
If this {@code JarFile} is a multi-release jar file and is configured * to be processed as such, then a search is performed to find and return * a {@code JarEntry} that is the latest versioned entry associated with the * given entry name. The returned {@code JarEntry} is the versioned entry * corresponding to the given base entry name prefixed with the string * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for * which an entry exists. If such a versioned entry does not exist, then * the {@code JarEntry} for the base entry is returned, otherwise * {@code null} is returned if no entries are found. The initial value for * the version {@code n} is the maximum version as returned by the method * {@link JarFile#getVersion()}. * * @param name the jar file entry name * @return the {@code JarEntry} for the given entry name, or * the versioned entry name, or {@code null} if not found * * @throws IllegalStateException * may be thrown if the jar file has been closed * * @see java.util.jar.JarEntry * * @implSpec *
If this {@code JarFile} is a multi-release jar file and is configured * to be processed as such, then a search is performed to find and return * a {@code ZipEntry} that is the latest versioned entry associated with the * given entry name. The returned {@code ZipEntry} is the versioned entry * corresponding to the given base entry name prefixed with the string * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for * which an entry exists. If such a versioned entry does not exist, then * the {@code ZipEntry} for the base entry is returned, otherwise * {@code null} is returned if no entries are found. The initial value for * the version {@code n} is the maximum version as returned by the method * {@link JarFile#getVersion()}. * * @param name the jar file entry name * @return the {@code ZipEntry} for the given entry name or * the versioned entry name or {@code null} if not found * * @throws IllegalStateException * may be thrown if the jar file has been closed * * @see java.util.zip.ZipEntry * * @implSpec *
If this {@code JarFile} is a multi-release jar file and is configured to
* be processed as such, then an entry in the stream is the latest versioned entry
* associated with the corresponding base entry name. The maximum version of the
* latest versioned entry is the version returned by {@link #getVersion()}.
* The returned stream may include an entry that only exists as a versioned entry.
*
* If the jar file is not a multi-release jar file or the {@code JarFile} is not
* configured for processing a multi-release jar file, this method returns the
* same stream that {@link #stream()} returns.
*
* @return stream of versioned entries
* @since 10
*/
public Stream