1 /*
2  * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package java.util.jar;
27 
28 import jdk.internal.misc.SharedSecrets;
29 import jdk.internal.misc.JavaUtilZipFileAccess;
30 import sun.security.action.GetPropertyAction;
31 import sun.security.util.ManifestEntryVerifier;
32 import sun.security.util.SignatureFileVerifier;
33 
34 import java.io.ByteArrayInputStream;
35 import java.io.EOFException;
36 import java.io.File;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.lang.ref.SoftReference;
40 import java.net.URL;
41 import java.security.CodeSigner;
42 import java.security.CodeSource;
43 import java.security.cert.Certificate;
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.Enumeration;
47 import java.util.List;
48 import java.util.Locale;
49 import java.util.NoSuchElementException;
50 import java.util.Objects;
51 import java.util.function.Function;
52 import java.util.stream.Stream;
53 import java.util.zip.ZipEntry;
54 import java.util.zip.ZipException;
55 import java.util.zip.ZipFile;
56 
57 /**
58  * The {@code JarFile} class is used to read the contents of a jar file
59  * from any file that can be opened with {@code java.io.RandomAccessFile}.
60  * It extends the class {@code java.util.zip.ZipFile} with support
61  * for reading an optional {@code Manifest} entry, and support for
62  * processing multi-release jar files.  The {@code Manifest} can be used
63  * to specify meta-information about the jar file and its entries.
64  *
65  * <p><a id="multirelease">A multi-release jar file</a> is a jar file that
66  * contains a manifest with a main attribute named "Multi-Release",
67  * a set of "base" entries, some of which are public classes with public
68  * or protected methods that comprise the public interface of the jar file,
69  * and a set of "versioned" entries contained in subdirectories of the
70  * "META-INF/versions" directory.  The versioned entries are partitioned by the
71  * major version of the Java platform.  A versioned entry, with a version
72  * {@code n}, {@code 8 < n}, in the "META-INF/versions/{n}" directory overrides
73  * the base entry as well as any entry with a version number {@code i} where
74  * {@code 8 < i < n}.
75  *
76  * <p>By default, a {@code JarFile} for a multi-release jar file is configured
77  * to process the multi-release jar file as if it were a plain (unversioned) jar
78  * file, and as such an entry name is associated with at most one base entry.
79  * The {@code JarFile} may be configured to process a multi-release jar file by
80  * creating the {@code JarFile} with the
81  * {@link JarFile#JarFile(File, boolean, int, Runtime.Version)} constructor.  The
82  * {@code Runtime.Version} object sets a maximum version used when searching for
83  * versioned entries.  When so configured, an entry name
84  * can correspond with at most one base entry and zero or more versioned
85  * entries. A search is required to associate the entry name with the latest
86  * versioned entry whose version is less than or equal to the maximum version
87  * (see {@link #getEntry(String)}).
88  *
89  * <p>Class loaders that utilize {@code JarFile} to load classes from the
90  * contents of {@code JarFile} entries should construct the {@code JarFile}
91  * by invoking the {@link JarFile#JarFile(File, boolean, int, Runtime.Version)}
92  * constructor with the value {@code Runtime.version()} assigned to the last
93  * argument.  This assures that classes compatible with the major
94  * version of the running JVM are loaded from multi-release jar files.
95  *
96  * <p> If the {@code verify} flag is on when opening a signed jar file, the content
97  * of the jar entry is verified against the signature embedded inside the manifest
98  * that is associated with its {@link JarEntry#getRealName() path name}. For a
99  * multi-release jar file, the content of a versioned entry is verfieid against
100  * its own signature and {@link JarEntry#getCodeSigners()} returns its own signers.
101  *
102  * Please note that the verification process does not include validating the
103  * signer's certificate. A caller should inspect the return value of
104  * {@link JarEntry#getCodeSigners()} to further determine if the signature
105  * can be trusted.
106  *
107  * <p> Unless otherwise noted, passing a {@code null} argument to a constructor
108  * or method in this class will cause a {@link NullPointerException} to be
109  * thrown.
110  *
111  * @implNote
112  * <div class="block">
113  * If the API can not be used to configure a {@code JarFile} (e.g. to override
114  * the configuration of a compiled application or library), two {@code System}
115  * properties are available.
116  * <ul>
117  * <li>
118  * {@code jdk.util.jar.version} can be assigned a value that is the
119  * {@code String} representation of a non-negative integer
120  * {@code <= Runtime.version().feature()}.  The value is used to set the effective
121  * runtime version to something other than the default value obtained by
122  * evaluating {@code Runtime.version().feature()}. The effective runtime version
123  * is the version that the {@link JarFile#JarFile(File, boolean, int, Runtime.Version)}
124  * constructor uses when the value of the last argument is
125  * {@code JarFile.runtimeVersion()}.
126  * </li>
127  * <li>
128  * {@code jdk.util.jar.enableMultiRelease} can be assigned one of the three
129  * {@code String} values <em>true</em>, <em>false</em>, or <em>force</em>.  The
130  * value <em>true</em>, the default value, enables multi-release jar file
131  * processing.  The value <em>false</em> disables multi-release jar processing,
132  * ignoring the "Multi-Release" manifest attribute, and the versioned
133  * directories in a multi-release jar file if they exist.  Furthermore,
134  * the method {@link JarFile#isMultiRelease()} returns <em>false</em>. The value
135  * <em>force</em> causes the {@code JarFile} to be initialized to runtime
136  * versioning after construction.  It effectively does the same as this code:
137  * {@code (new JarFile(File, boolean, int, JarFile.runtimeVersion())}.
138  * </li>
139  * </ul>
140  * </div>
141  *
142  * @author  David Connelly
143  * @see     Manifest
144  * @see     java.util.zip.ZipFile
145  * @see     java.util.jar.JarEntry
146  * @since   1.2
147  */
148 public
149 class JarFile extends ZipFile {
150     private static final Runtime.Version BASE_VERSION;
151     private static final int BASE_VERSION_FEATURE;
152     private static final Runtime.Version RUNTIME_VERSION;
153     private static final boolean MULTI_RELEASE_ENABLED;
154     private static final boolean MULTI_RELEASE_FORCED;
155     private static final ThreadLocal<Boolean> isInitializing = new ThreadLocal<>();
156     // The maximum size of array to allocate. Some VMs reserve some header words in an array.
157     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
158 
159     private SoftReference<Manifest> manRef;
160     private JarEntry manEntry;
161     private JarVerifier jv;
162     private boolean jvInitialized;
163     private boolean verify;
164     private final Runtime.Version version;  // current version
165     private final int versionFeature;       // version.feature()
166     private boolean isMultiRelease;         // is jar multi-release?
167 
168     // indicates if Class-Path attribute present
169     private boolean hasClassPathAttribute;
170     // true if manifest checked for special attributes
171     private volatile boolean hasCheckedSpecialAttributes;
172 
173     private static final JavaUtilZipFileAccess JUZFA;
174 
175     static {
176         // Set up JavaUtilJarAccess in SharedSecrets
SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl())177         SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
178         // Get JavaUtilZipFileAccess from SharedSecrets
179         JUZFA = jdk.internal.misc.SharedSecrets.getJavaUtilZipFileAccess();
180         // multi-release jar file versions >= 9
181         BASE_VERSION = Runtime.Version.parse(Integer.toString(8));
182         BASE_VERSION_FEATURE = BASE_VERSION.feature();
183         String jarVersion = GetPropertyAction.privilegedGetProperty("jdk.util.jar.version");
184         int runtimeVersion = Runtime.version().feature();
185         if (jarVersion != null) {
186             int jarVer = Integer.parseInt(jarVersion);
187             runtimeVersion = (jarVer > runtimeVersion)
188                     ? runtimeVersion
189                     : Math.max(jarVer, BASE_VERSION_FEATURE);
190         }
191         RUNTIME_VERSION = Runtime.Version.parse(Integer.toString(runtimeVersion));
192         String enableMultiRelease = GetPropertyAction
193                 .privilegedGetProperty("jdk.util.jar.enableMultiRelease", "true");
194         switch (enableMultiRelease) {
195             case "true":
196             default:
197                 MULTI_RELEASE_ENABLED = true;
198                 MULTI_RELEASE_FORCED = false;
199                 break;
200             case "false":
201                 MULTI_RELEASE_ENABLED = false;
202                 MULTI_RELEASE_FORCED = false;
203                 break;
204             case "force":
205                 MULTI_RELEASE_ENABLED = true;
206                 MULTI_RELEASE_FORCED = true;
207                 break;
208         }
209     }
210 
211     private static final String META_INF = "META-INF/";
212 
213     private static final String META_INF_VERSIONS = META_INF + "versions/";
214 
215     /**
216      * The JAR manifest file name.
217      */
218     public static final String MANIFEST_NAME = META_INF + "MANIFEST.MF";
219 
220     /**
221      * Returns the version that represents the unversioned configuration of a
222      * multi-release jar file.
223      *
224      * @return the version that represents the unversioned configuration
225      *
226      * @since 9
227      */
baseVersion()228     public static Runtime.Version baseVersion() {
229         return BASE_VERSION;
230     }
231 
232     /**
233      * Returns the version that represents the effective runtime versioned
234      * configuration of a multi-release jar file.
235      * <p>
236      * By default the feature version number of the returned {@code Version} will
237      * be equal to the feature version number of {@code Runtime.version()}.
238      * However, if the {@code jdk.util.jar.version} property is set, the
239      * returned {@code Version} is derived from that property and feature version
240      * numbers may not be equal.
241      *
242      * @return the version that represents the runtime versioned configuration
243      *
244      * @since 9
245      */
runtimeVersion()246     public static Runtime.Version runtimeVersion() {
247         return RUNTIME_VERSION;
248     }
249 
250     /**
251      * Creates a new {@code JarFile} to read from the specified
252      * file {@code name}. The {@code JarFile} will be verified if
253      * it is signed.
254      * @param name the name of the jar file to be opened for reading
255      * @throws IOException if an I/O error has occurred
256      * @throws SecurityException if access to the file is denied
257      *         by the SecurityManager
258      */
JarFile(String name)259     public JarFile(String name) throws IOException {
260         this(new File(name), true, ZipFile.OPEN_READ);
261     }
262 
263     /**
264      * Creates a new {@code JarFile} to read from the specified
265      * file {@code name}.
266      * @param name the name of the jar file to be opened for reading
267      * @param verify whether or not to verify the jar file if
268      * it is signed.
269      * @throws IOException if an I/O error has occurred
270      * @throws SecurityException if access to the file is denied
271      *         by the SecurityManager
272      */
JarFile(String name, boolean verify)273     public JarFile(String name, boolean verify) throws IOException {
274         this(new File(name), verify, ZipFile.OPEN_READ);
275     }
276 
277     /**
278      * Creates a new {@code JarFile} to read from the specified
279      * {@code File} object. The {@code JarFile} will be verified if
280      * it is signed.
281      * @param file the jar file to be opened for reading
282      * @throws IOException if an I/O error has occurred
283      * @throws SecurityException if access to the file is denied
284      *         by the SecurityManager
285      */
JarFile(File file)286     public JarFile(File file) throws IOException {
287         this(file, true, ZipFile.OPEN_READ);
288     }
289 
290     /**
291      * Creates a new {@code JarFile} to read from the specified
292      * {@code File} object.
293      * @param file the jar file to be opened for reading
294      * @param verify whether or not to verify the jar file if
295      * it is signed.
296      * @throws IOException if an I/O error has occurred
297      * @throws SecurityException if access to the file is denied
298      *         by the SecurityManager.
299      */
JarFile(File file, boolean verify)300     public JarFile(File file, boolean verify) throws IOException {
301         this(file, verify, ZipFile.OPEN_READ);
302     }
303 
304     /**
305      * Creates a new {@code JarFile} to read from the specified
306      * {@code File} object in the specified mode.  The mode argument
307      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
308      *
309      * @param file the jar file to be opened for reading
310      * @param verify whether or not to verify the jar file if
311      * it is signed.
312      * @param mode the mode in which the file is to be opened
313      * @throws IOException if an I/O error has occurred
314      * @throws IllegalArgumentException
315      *         if the {@code mode} argument is invalid
316      * @throws SecurityException if access to the file is denied
317      *         by the SecurityManager
318      * @since 1.3
319      */
JarFile(File file, boolean verify, int mode)320     public JarFile(File file, boolean verify, int mode) throws IOException {
321         this(file, verify, mode, BASE_VERSION);
322     }
323 
324     /**
325      * Creates a new {@code JarFile} to read from the specified
326      * {@code File} object in the specified mode.  The mode argument
327      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
328      * The version argument, after being converted to a canonical form, is
329      * used to configure the {@code JarFile} for processing
330      * multi-release jar files.
331      * <p>
332      * The canonical form derived from the version parameter is
333      * {@code Runtime.Version.parse(Integer.toString(n))} where {@code n} is
334      * {@code Math.max(version.feature(), JarFile.baseVersion().feature())}.
335      *
336      * @param file the jar file to be opened for reading
337      * @param verify whether or not to verify the jar file if
338      * it is signed.
339      * @param mode the mode in which the file is to be opened
340      * @param version specifies the release version for a multi-release jar file
341      * @throws IOException if an I/O error has occurred
342      * @throws IllegalArgumentException
343      *         if the {@code mode} argument is invalid
344      * @throws SecurityException if access to the file is denied
345      *         by the SecurityManager
346      * @throws NullPointerException if {@code version} is {@code null}
347      * @since 9
348      */
JarFile(File file, boolean verify, int mode, Runtime.Version version)349     public JarFile(File file, boolean verify, int mode, Runtime.Version version) throws IOException {
350         super(file, mode);
351         this.verify = verify;
352         Objects.requireNonNull(version);
353         if (MULTI_RELEASE_FORCED || version.feature() == RUNTIME_VERSION.feature()) {
354             // This deals with the common case where the value from JarFile.runtimeVersion() is passed
355             this.version = RUNTIME_VERSION;
356         } else if (version.feature() <= BASE_VERSION_FEATURE) {
357             // This also deals with the common case where the value from JarFile.baseVersion() is passed
358             this.version = BASE_VERSION;
359         } else {
360             // Canonicalize
361             this.version = Runtime.Version.parse(Integer.toString(version.feature()));
362         }
363         this.versionFeature = this.version.feature();
364     }
365 
366     /**
367      * Returns the maximum version used when searching for versioned entries.
368      * <p>
369      * If this {@code JarFile} is not a multi-release jar file or is not
370      * configured to be processed as such, then the version returned will be the
371      * same as that returned from {@link #baseVersion()}.
372      *
373      * @return the maximum version
374      * @since 9
375      */
getVersion()376     public final Runtime.Version getVersion() {
377         return isMultiRelease() ? this.version : BASE_VERSION;
378     }
379 
380     /**
381      * Indicates whether or not this jar file is a multi-release jar file.
382      *
383      * @return true if this JarFile is a multi-release jar file
384      * @since 9
385      */
isMultiRelease()386     public final boolean isMultiRelease() {
387         if (isMultiRelease) {
388             return true;
389         }
390         if (MULTI_RELEASE_ENABLED) {
391             try {
392                 checkForSpecialAttributes();
393             } catch (IOException io) {
394                 isMultiRelease = false;
395             }
396         }
397         return isMultiRelease;
398     }
399 
400     /**
401      * Returns the jar file manifest, or {@code null} if none.
402      *
403      * @return the jar file manifest, or {@code null} if none
404      *
405      * @throws IllegalStateException
406      *         may be thrown if the jar file has been closed
407      * @throws IOException  if an I/O error has occurred
408      */
getManifest()409     public Manifest getManifest() throws IOException {
410         return getManifestFromReference();
411     }
412 
getManifestFromReference()413     private Manifest getManifestFromReference() throws IOException {
414         Manifest man = manRef != null ? manRef.get() : null;
415 
416         if (man == null) {
417 
418             JarEntry manEntry = getManEntry();
419 
420             // If found then load the manifest
421             if (manEntry != null) {
422                 if (verify) {
423                     byte[] b = getBytes(manEntry);
424                     if (!jvInitialized) {
425                         if (JUZFA.getManifestNum(this) == 1) {
426                             jv = new JarVerifier(manEntry.getName(), b);
427                         } else {
428                             if (JarVerifier.debug != null) {
429                                 JarVerifier.debug.println("Multiple MANIFEST.MF found. Treat JAR file as unsigned");
430                             }
431                         }
432                     }
433                     man = new Manifest(jv, new ByteArrayInputStream(b));
434                 } else {
435                     man = new Manifest(super.getInputStream(manEntry));
436                 }
437                 manRef = new SoftReference<>(man);
438             }
439         }
440         return man;
441     }
442 
getMetaInfEntryNames()443     private String[] getMetaInfEntryNames() {
444         return JUZFA.getMetaInfEntryNames((ZipFile)this);
445     }
446 
447     /**
448      * Returns the {@code JarEntry} for the given base entry name or
449      * {@code null} if not found.
450      *
451      * <p>If this {@code JarFile} is a multi-release jar file and is configured
452      * to be processed as such, then a search is performed to find and return
453      * a {@code JarEntry} that is the latest versioned entry associated with the
454      * given entry name.  The returned {@code JarEntry} is the versioned entry
455      * corresponding to the given base entry name prefixed with the string
456      * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
457      * which an entry exists.  If such a versioned entry does not exist, then
458      * the {@code JarEntry} for the base entry is returned, otherwise
459      * {@code null} is returned if no entries are found.  The initial value for
460      * the version {@code n} is the maximum version as returned by the method
461      * {@link JarFile#getVersion()}.
462      *
463      * @param name the jar file entry name
464      * @return the {@code JarEntry} for the given entry name, or
465      *         the versioned entry name, or {@code null} if not found
466      *
467      * @throws IllegalStateException
468      *         may be thrown if the jar file has been closed
469      *
470      * @see java.util.jar.JarEntry
471      *
472      * @implSpec
473      * <div class="block">
474      * This implementation invokes {@link JarFile#getEntry(String)}.
475      * </div>
476      */
getJarEntry(String name)477     public JarEntry getJarEntry(String name) {
478         return (JarEntry)getEntry(name);
479     }
480 
481     /**
482      * Returns the {@code ZipEntry} for the given base entry name or
483      * {@code null} if not found.
484      *
485      * <p>If this {@code JarFile} is a multi-release jar file and is configured
486      * to be processed as such, then a search is performed to find and return
487      * a {@code ZipEntry} that is the latest versioned entry associated with the
488      * given entry name.  The returned {@code ZipEntry} is the versioned entry
489      * corresponding to the given base entry name prefixed with the string
490      * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
491      * which an entry exists.  If such a versioned entry does not exist, then
492      * the {@code ZipEntry} for the base entry is returned, otherwise
493      * {@code null} is returned if no entries are found.  The initial value for
494      * the version {@code n} is the maximum version as returned by the method
495      * {@link JarFile#getVersion()}.
496      *
497      * @param name the jar file entry name
498      * @return the {@code ZipEntry} for the given entry name or
499      *         the versioned entry name or {@code null} if not found
500      *
501      * @throws IllegalStateException
502      *         may be thrown if the jar file has been closed
503      *
504      * @see java.util.zip.ZipEntry
505      *
506      * @implSpec
507      * <div class="block">
508      * This implementation may return a versioned entry for the requested name
509      * even if there is not a corresponding base entry.  This can occur
510      * if there is a private or package-private versioned entry that matches.
511      * If a subclass overrides this method, assure that the override method
512      * invokes {@code super.getEntry(name)} to obtain all versioned entries.
513      * </div>
514      */
getEntry(String name)515     public ZipEntry getEntry(String name) {
516         JarFileEntry je = getEntry0(name);
517         if (isMultiRelease()) {
518             return getVersionedEntry(name, je);
519         }
520         return je;
521     }
522 
523     /**
524      * Returns an enumeration of the jar file entries.
525      *
526      * @return an enumeration of the jar file entries
527      * @throws IllegalStateException
528      *         may be thrown if the jar file has been closed
529      */
entries()530     public Enumeration<JarEntry> entries() {
531         return JUZFA.entries(this, JarFileEntry::new);
532     }
533 
534     /**
535      * Returns an ordered {@code Stream} over the jar file entries.
536      * Entries appear in the {@code Stream} in the order they appear in
537      * the central directory of the jar file.
538      *
539      * @return an ordered {@code Stream} of entries in this jar file
540      * @throws IllegalStateException if the jar file has been closed
541      * @since 1.8
542      */
stream()543     public Stream<JarEntry> stream() {
544         return JUZFA.stream(this, JarFileEntry::new);
545     }
546 
547     /**
548      * Returns a {@code Stream} of the versioned jar file entries.
549      *
550      * <p>If this {@code JarFile} is a multi-release jar file and is configured to
551      * be processed as such, then an entry in the stream is the latest versioned entry
552      * associated with the corresponding base entry name. The maximum version of the
553      * latest versioned entry is the version returned by {@link #getVersion()}.
554      * The returned stream may include an entry that only exists as a versioned entry.
555      *
556      * If the jar file is not a multi-release jar file or the {@code JarFile} is not
557      * configured for processing a multi-release jar file, this method returns the
558      * same stream that {@link #stream()} returns.
559      *
560      * @return stream of versioned entries
561      * @since 10
562      */
versionedStream()563     public Stream<JarEntry> versionedStream() {
564 
565         if (isMultiRelease()) {
566             return JUZFA.entryNameStream(this).map(this::getBasename)
567                                               .filter(Objects::nonNull)
568                                               .distinct()
569                                               .map(this::getJarEntry);
570         }
571         return stream();
572     }
573 
574     /*
575      * Invokes {@ZipFile}'s getEntry to Return a {@code JarFileEntry} for the
576      * given entry name or {@code null} if not found.
577      */
getEntry0(String name)578     private JarFileEntry getEntry0(String name) {
579         // Not using a lambda/method reference here to optimize startup time
580         Function<String, JarEntry> newJarFileEntryFn = new Function<>() {
581             @Override
582             public JarEntry apply(String name) {
583                 return new JarFileEntry(name);
584             }
585         };
586         return (JarFileEntry)JUZFA.getEntry(this, name, newJarFileEntryFn);
587     }
588 
getBasename(String name)589     private String getBasename(String name) {
590         if (name.startsWith(META_INF_VERSIONS)) {
591             int off = META_INF_VERSIONS.length();
592             int index = name.indexOf('/', off);
593             try {
594                 // filter out dir META-INF/versions/ and META-INF/versions/*/
595                 // and any entry with version > 'version'
596                 if (index == -1 || index == (name.length() - 1) ||
597                     Integer.parseInt(name, off, index, 10) > versionFeature) {
598                     return null;
599                 }
600             } catch (NumberFormatException x) {
601                 return null; // remove malformed entries silently
602             }
603             // map to its base name
604             return name.substring(index + 1);
605         }
606         return name;
607     }
608 
getVersionedEntry(String name, JarEntry je)609     private JarEntry getVersionedEntry(String name, JarEntry je) {
610         if (BASE_VERSION_FEATURE < versionFeature) {
611             if (!name.startsWith(META_INF)) {
612                 // search for versioned entry
613                 int v = versionFeature;
614                 while (v > BASE_VERSION_FEATURE) {
615                     JarFileEntry vje = getEntry0(META_INF_VERSIONS + v + "/" + name);
616                     if (vje != null) {
617                         return vje.withBasename(name);
618                     }
619                     v--;
620                 }
621             }
622         }
623         return je;
624     }
625 
626     // placeholder for now
getRealName(JarEntry entry)627     String getRealName(JarEntry entry) {
628         return entry.getRealName();
629     }
630 
631     private class JarFileEntry extends JarEntry {
632         private String basename;
633 
JarFileEntry(String name)634         JarFileEntry(String name) {
635             super(name);
636             this.basename = name;
637         }
638 
JarFileEntry(String name, ZipEntry vze)639         JarFileEntry(String name, ZipEntry vze) {
640             super(vze);
641             this.basename = name;
642         }
643 
644         @Override
getAttributes()645         public Attributes getAttributes() throws IOException {
646             Manifest man = JarFile.this.getManifest();
647             if (man != null) {
648                 return man.getAttributes(super.getName());
649             } else {
650                 return null;
651             }
652         }
653 
654         @Override
getCertificates()655         public Certificate[] getCertificates() {
656             try {
657                 maybeInstantiateVerifier();
658             } catch (IOException e) {
659                 throw new RuntimeException(e);
660             }
661             if (certs == null && jv != null) {
662                 certs = jv.getCerts(JarFile.this, realEntry());
663             }
664             return certs == null ? null : certs.clone();
665         }
666 
667         @Override
getCodeSigners()668         public CodeSigner[] getCodeSigners() {
669             try {
670                 maybeInstantiateVerifier();
671             } catch (IOException e) {
672                 throw new RuntimeException(e);
673             }
674             if (signers == null && jv != null) {
675                 signers = jv.getCodeSigners(JarFile.this, realEntry());
676             }
677             return signers == null ? null : signers.clone();
678         }
679 
680         @Override
getRealName()681         public String getRealName() {
682             return super.getName();
683         }
684 
685         @Override
getName()686         public String getName() {
687             return basename;
688         }
689 
realEntry()690         JarFileEntry realEntry() {
691             if (isMultiRelease() && versionFeature != BASE_VERSION_FEATURE) {
692                 String entryName = super.getName();
693                 return entryName == basename || entryName.equals(basename) ?
694                         this : new JarFileEntry(entryName, this);
695             }
696             return this;
697         }
698 
699         // changes the basename, returns "this"
withBasename(String name)700         JarFileEntry withBasename(String name) {
701             basename = name;
702             return this;
703         }
704     }
705 
706     /*
707      * Ensures that the JarVerifier has been created if one is
708      * necessary (i.e., the jar appears to be signed.) This is done as
709      * a quick check to avoid processing of the manifest for unsigned
710      * jars.
711      */
maybeInstantiateVerifier()712     private void maybeInstantiateVerifier() throws IOException {
713         if (jv != null) {
714             return;
715         }
716 
717         if (verify) {
718             String[] names = getMetaInfEntryNames();
719             if (names != null) {
720                 for (String nameLower : names) {
721                     String name = nameLower.toUpperCase(Locale.ENGLISH);
722                     if (name.endsWith(".DSA") ||
723                         name.endsWith(".RSA") ||
724                         name.endsWith(".EC") ||
725                         name.endsWith(".SF")) {
726                         // Assume since we found a signature-related file
727                         // that the jar is signed and that we therefore
728                         // need a JarVerifier and Manifest
729                         getManifest();
730                         return;
731                     }
732                 }
733             }
734             // No signature-related files; don't instantiate a
735             // verifier
736             verify = false;
737         }
738     }
739 
740     /*
741      * Initializes the verifier object by reading all the manifest
742      * entries and passing them to the verifier.
743      */
initializeVerifier()744     private void initializeVerifier() {
745         ManifestEntryVerifier mev = null;
746 
747         // Verify "META-INF/" entries...
748         try {
749             String[] names = getMetaInfEntryNames();
750             if (names != null) {
751                 for (String name : names) {
752                     String uname = name.toUpperCase(Locale.ENGLISH);
753                     if (MANIFEST_NAME.equals(uname)
754                             || SignatureFileVerifier.isBlockOrSF(uname)) {
755                         JarEntry e = getJarEntry(name);
756                         if (e == null) {
757                             throw new JarException("corrupted jar file");
758                         }
759                         if (mev == null) {
760                             mev = new ManifestEntryVerifier
761                                 (getManifestFromReference());
762                         }
763                         byte[] b = getBytes(e);
764                         if (b != null && b.length > 0) {
765                             jv.beginEntry(e, mev);
766                             jv.update(b.length, b, 0, b.length, mev);
767                             jv.update(-1, null, 0, 0, mev);
768                         }
769                     }
770                 }
771             }
772         } catch (IOException ex) {
773             // if we had an error parsing any blocks, just
774             // treat the jar file as being unsigned
775             jv = null;
776             verify = false;
777             if (JarVerifier.debug != null) {
778                 JarVerifier.debug.println("jarfile parsing error!");
779                 ex.printStackTrace();
780             }
781         }
782 
783         // if after initializing the verifier we have nothing
784         // signed, we null it out.
785 
786         if (jv != null) {
787 
788             jv.doneWithMeta();
789             if (JarVerifier.debug != null) {
790                 JarVerifier.debug.println("done with meta!");
791             }
792 
793             if (jv.nothingToVerify()) {
794                 if (JarVerifier.debug != null) {
795                     JarVerifier.debug.println("nothing to verify!");
796                 }
797                 jv = null;
798                 verify = false;
799             }
800         }
801     }
802 
803     /*
804      * Reads all the bytes for a given entry. Used to process the
805      * META-INF files.
806      */
getBytes(ZipEntry ze)807     private byte[] getBytes(ZipEntry ze) throws IOException {
808         try (InputStream is = super.getInputStream(ze)) {
809             long uncompressedSize = ze.getSize();
810             if (uncompressedSize > MAX_ARRAY_SIZE) {
811                 throw new IOException("Unsupported size: " + uncompressedSize);
812             }
813             int len = (int)uncompressedSize;
814             int bytesRead;
815             byte[] b;
816             // trust specified entry sizes when reasonably small
817             if (len != -1 && len <= 65535) {
818                 b = new byte[len];
819                 bytesRead = is.readNBytes(b, 0, len);
820             } else {
821                 b = is.readAllBytes();
822                 bytesRead = b.length;
823             }
824             if (len != -1 && len != bytesRead) {
825                 throw new EOFException("Expected:" + len + ", read:" + bytesRead);
826             }
827             return b;
828         }
829     }
830 
831     /**
832      * Returns an input stream for reading the contents of the specified
833      * zip file entry.
834      * @param ze the zip file entry
835      * @return an input stream for reading the contents of the specified
836      *         zip file entry
837      * @throws ZipException if a zip file format error has occurred
838      * @throws IOException if an I/O error has occurred
839      * @throws SecurityException if any of the jar file entries
840      *         are incorrectly signed.
841      * @throws IllegalStateException
842      *         may be thrown if the jar file has been closed
843      */
getInputStream(ZipEntry ze)844     public synchronized InputStream getInputStream(ZipEntry ze)
845         throws IOException
846     {
847         maybeInstantiateVerifier();
848         if (jv == null) {
849             return super.getInputStream(ze);
850         }
851         if (!jvInitialized) {
852             initializeVerifier();
853             jvInitialized = true;
854             // could be set to null after a call to
855             // initializeVerifier if we have nothing to
856             // verify
857             if (jv == null)
858                 return super.getInputStream(ze);
859         }
860 
861         // wrap a verifier stream around the real stream
862         return new JarVerifier.VerifierStream(
863             getManifestFromReference(),
864             verifiableEntry(ze),
865             super.getInputStream(ze),
866             jv);
867     }
868 
verifiableEntry(ZipEntry ze)869     private JarEntry verifiableEntry(ZipEntry ze) {
870         if (ze instanceof JarFileEntry) {
871             // assure the name and entry match for verification
872             return ((JarFileEntry)ze).realEntry();
873         }
874         ze = getJarEntry(ze.getName());
875         if (ze instanceof JarFileEntry) {
876             return ((JarFileEntry)ze).realEntry();
877         }
878         return (JarEntry)ze;
879     }
880 
881     // Statics for hand-coded Boyer-Moore search
882     private static final byte[] CLASSPATH_CHARS =
883             {'C','L','A','S','S','-','P','A','T','H', ':', ' '};
884 
885     // The bad character shift for "class-path: "
886     private static final byte[] CLASSPATH_LASTOCC;
887 
888     // The good suffix shift for "class-path: "
889     private static final byte[] CLASSPATH_OPTOSFT;
890 
891     private static final byte[] MULTIRELEASE_CHARS =
892             {'M','U','L','T','I','-','R','E','L','E', 'A', 'S', 'E', ':',
893                     ' ', 'T', 'R', 'U', 'E'};
894 
895     // The bad character shift for "multi-release: true"
896     private static final byte[] MULTIRELEASE_LASTOCC;
897 
898     // The good suffix shift for "multi-release: true"
899     private static final byte[] MULTIRELEASE_OPTOSFT;
900 
901     static {
902         CLASSPATH_LASTOCC = new byte[65];
903         CLASSPATH_OPTOSFT = new byte[12];
904         CLASSPATH_LASTOCC[(int)'C' - 32] = 1;
905         CLASSPATH_LASTOCC[(int)'L' - 32] = 2;
906         CLASSPATH_LASTOCC[(int)'S' - 32] = 5;
907         CLASSPATH_LASTOCC[(int)'-' - 32] = 6;
908         CLASSPATH_LASTOCC[(int)'P' - 32] = 7;
909         CLASSPATH_LASTOCC[(int)'A' - 32] = 8;
910         CLASSPATH_LASTOCC[(int)'T' - 32] = 9;
911         CLASSPATH_LASTOCC[(int)'H' - 32] = 10;
912         CLASSPATH_LASTOCC[(int)':' - 32] = 11;
913         CLASSPATH_LASTOCC[(int)' ' - 32] = 12;
914         for (int i = 0; i < 11; i++) {
915             CLASSPATH_OPTOSFT[i] = 12;
916         }
917         CLASSPATH_OPTOSFT[11] = 1;
918 
919         MULTIRELEASE_LASTOCC = new byte[65];
920         MULTIRELEASE_OPTOSFT = new byte[19];
921         MULTIRELEASE_LASTOCC[(int)'M' - 32] = 1;
922         MULTIRELEASE_LASTOCC[(int)'I' - 32] = 5;
923         MULTIRELEASE_LASTOCC[(int)'-' - 32] = 6;
924         MULTIRELEASE_LASTOCC[(int)'L' - 32] = 9;
925         MULTIRELEASE_LASTOCC[(int)'A' - 32] = 11;
926         MULTIRELEASE_LASTOCC[(int)'S' - 32] = 12;
927         MULTIRELEASE_LASTOCC[(int)':' - 32] = 14;
928         MULTIRELEASE_LASTOCC[(int)' ' - 32] = 15;
929         MULTIRELEASE_LASTOCC[(int)'T' - 32] = 16;
930         MULTIRELEASE_LASTOCC[(int)'R' - 32] = 17;
931         MULTIRELEASE_LASTOCC[(int)'U' - 32] = 18;
932         MULTIRELEASE_LASTOCC[(int)'E' - 32] = 19;
933         for (int i = 0; i < 17; i++) {
934             MULTIRELEASE_OPTOSFT[i] = 19;
935         }
936         MULTIRELEASE_OPTOSFT[17] = 6;
937         MULTIRELEASE_OPTOSFT[18] = 1;
938     }
939 
getManEntry()940     private JarEntry getManEntry() {
941         if (manEntry == null) {
942             // First look up manifest entry using standard name
943             JarEntry manEntry = getEntry0(MANIFEST_NAME);
944             if (manEntry == null) {
945                 // If not found, then iterate through all the "META-INF/"
946                 // entries to find a match.
947                 String[] names = getMetaInfEntryNames();
948                 if (names != null) {
949                     for (String name : names) {
950                         if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
951                             manEntry = getEntry0(name);
952                             break;
953                         }
954                     }
955                 }
956             }
957             this.manEntry = manEntry;
958         }
959         return manEntry;
960     }
961 
962    /**
963     * Returns {@code true} iff this JAR file has a manifest with the
964     * Class-Path attribute
965     */
hasClassPathAttribute()966     boolean hasClassPathAttribute() throws IOException {
967         checkForSpecialAttributes();
968         return hasClassPathAttribute;
969     }
970 
971     /**
972      * Returns true if the pattern {@code src} is found in {@code b}.
973      * The {@code lastOcc} array is the precomputed bad character shifts.
974      * Since there are no repeated substring in our search strings,
975      * the good suffix shifts can be replaced with a comparison.
976      */
match(byte[] src, byte[] b, byte[] lastOcc, byte[] optoSft)977     private int match(byte[] src, byte[] b, byte[] lastOcc, byte[] optoSft) {
978         int len = src.length;
979         int last = b.length - len;
980         int i = 0;
981         next:
982         while (i <= last) {
983             for (int j = (len - 1); j >= 0; j--) {
984                 byte c = b[i + j];
985                 if (c >= ' ' && c <= 'z') {
986                     if (c >= 'a') c -= 32; // Canonicalize
987 
988                     if (c != src[j]) {
989                         // no match
990                         int badShift = lastOcc[c - 32];
991                         i += Math.max(j + 1 - badShift, optoSft[j]);
992                         continue next;
993                     }
994                 } else {
995                     // no match, character not valid for name
996                     i += len;
997                     continue next;
998                 }
999             }
1000             return i;
1001         }
1002         return -1;
1003     }
1004 
1005     /**
1006      * On first invocation, check if the JAR file has the Class-Path
1007      * and the Multi-Release attribute. A no-op on subsequent calls.
1008      */
checkForSpecialAttributes()1009     private void checkForSpecialAttributes() throws IOException {
1010         if (hasCheckedSpecialAttributes) {
1011             return;
1012         }
1013         synchronized (this) {
1014             if (hasCheckedSpecialAttributes) {
1015                 return;
1016             }
1017             JarEntry manEntry = getManEntry();
1018             if (manEntry != null) {
1019                 byte[] b = getBytes(manEntry);
1020                 hasClassPathAttribute = match(CLASSPATH_CHARS, b,
1021                         CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT) != -1;
1022                 // is this a multi-release jar file
1023                 if (MULTI_RELEASE_ENABLED) {
1024                     int i = match(MULTIRELEASE_CHARS, b, MULTIRELEASE_LASTOCC,
1025                             MULTIRELEASE_OPTOSFT);
1026                     if (i != -1) {
1027                         // Read the main attributes of the manifest
1028                         byte[] lbuf = new byte[512];
1029                         Attributes attr = new Attributes();
1030                         attr.read(new Manifest.FastInputStream(
1031                             new ByteArrayInputStream(b)), lbuf);
1032                         isMultiRelease = Boolean.parseBoolean(
1033                             attr.getValue(Attributes.Name.MULTI_RELEASE));
1034                     }
1035                 }
1036             }
1037             hasCheckedSpecialAttributes = true;
1038         }
1039     }
1040 
ensureInitialization()1041     synchronized void ensureInitialization() {
1042         try {
1043             maybeInstantiateVerifier();
1044         } catch (IOException e) {
1045             throw new RuntimeException(e);
1046         }
1047         if (jv != null && !jvInitialized) {
1048             isInitializing.set(Boolean.TRUE);
1049             try {
1050                 initializeVerifier();
1051                 jvInitialized = true;
1052             } finally {
1053                 isInitializing.set(Boolean.FALSE);
1054             }
1055         }
1056     }
1057 
isInitializing()1058     static boolean isInitializing() {
1059         Boolean value = isInitializing.get();
1060         return (value == null) ? false : value;
1061     }
1062 
1063     /*
1064      * Returns a versioned {@code JarFileEntry} for the given entry,
1065      * if there is one. Otherwise returns the original entry. This
1066      * is invoked by the {@code entries2} for verifier.
1067      */
newEntry(JarEntry je)1068     JarEntry newEntry(JarEntry je) {
1069         if (isMultiRelease()) {
1070             return getVersionedEntry(je.getName(), je);
1071         }
1072         return je;
1073     }
1074 
1075     /*
1076      * Returns a versioned {@code JarFileEntry} for the given entry
1077      * name, if there is one. Otherwise returns a {@code JarFileEntry}
1078      * with the given name. It is invoked from JarVerifier's entries2
1079      * for {@code singers}.
1080      */
newEntry(String name)1081     JarEntry newEntry(String name) {
1082         if (isMultiRelease()) {
1083             JarEntry vje = getVersionedEntry(name, (JarEntry)null);
1084             if (vje != null) {
1085                 return vje;
1086             }
1087         }
1088         return new JarFileEntry(name);
1089     }
1090 
entryNames(CodeSource[] cs)1091     Enumeration<String> entryNames(CodeSource[] cs) {
1092         ensureInitialization();
1093         if (jv != null) {
1094             return jv.entryNames(this, cs);
1095         }
1096 
1097         /*
1098          * JAR file has no signed content. Is there a non-signing
1099          * code source?
1100          */
1101         boolean includeUnsigned = false;
1102         for (CodeSource c : cs) {
1103             if (c.getCodeSigners() == null) {
1104                 includeUnsigned = true;
1105                 break;
1106             }
1107         }
1108         if (includeUnsigned) {
1109             return unsignedEntryNames();
1110         } else {
1111             return Collections.emptyEnumeration();
1112         }
1113     }
1114 
1115     /**
1116      * Returns an enumeration of the zip file entries
1117      * excluding internal JAR mechanism entries and including
1118      * signed entries missing from the ZIP directory.
1119      */
entries2()1120     Enumeration<JarEntry> entries2() {
1121         ensureInitialization();
1122         if (jv != null) {
1123             return jv.entries2(this, JUZFA.entries(JarFile.this,
1124                                                    JarFileEntry::new));
1125         }
1126 
1127         // screen out entries which are never signed
1128         final var unfilteredEntries = JUZFA.entries(JarFile.this, JarFileEntry::new);
1129 
1130         return new Enumeration<>() {
1131 
1132             JarEntry entry;
1133 
1134             public boolean hasMoreElements() {
1135                 if (entry != null) {
1136                     return true;
1137                 }
1138                 while (unfilteredEntries.hasMoreElements()) {
1139                     JarEntry je = unfilteredEntries.nextElement();
1140                     if (JarVerifier.isSigningRelated(je.getName())) {
1141                         continue;
1142                     }
1143                     entry = je;
1144                     return true;
1145                 }
1146                 return false;
1147             }
1148 
1149             public JarEntry nextElement() {
1150                 if (hasMoreElements()) {
1151                     JarEntry je = entry;
1152                     entry = null;
1153                     return newEntry(je);
1154                 }
1155                 throw new NoSuchElementException();
1156             }
1157         };
1158     }
1159 
getCodeSources(URL url)1160     CodeSource[] getCodeSources(URL url) {
1161         ensureInitialization();
1162         if (jv != null) {
1163             return jv.getCodeSources(this, url);
1164         }
1165 
1166         /*
1167          * JAR file has no signed content. Is there a non-signing
1168          * code source?
1169          */
1170         Enumeration<String> unsigned = unsignedEntryNames();
1171         if (unsigned.hasMoreElements()) {
1172             return new CodeSource[]{JarVerifier.getUnsignedCS(url)};
1173         } else {
1174             return null;
1175         }
1176     }
1177 
unsignedEntryNames()1178     private Enumeration<String> unsignedEntryNames() {
1179         final Enumeration<JarEntry> entries = entries();
1180         return new Enumeration<>() {
1181 
1182             String name;
1183 
1184             /*
1185              * Grab entries from ZIP directory but screen out
1186              * metadata.
1187              */
1188             public boolean hasMoreElements() {
1189                 if (name != null) {
1190                     return true;
1191                 }
1192                 while (entries.hasMoreElements()) {
1193                     String value;
1194                     ZipEntry e = entries.nextElement();
1195                     value = e.getName();
1196                     if (e.isDirectory() || JarVerifier.isSigningRelated(value)) {
1197                         continue;
1198                     }
1199                     name = value;
1200                     return true;
1201                 }
1202                 return false;
1203             }
1204 
1205             public String nextElement() {
1206                 if (hasMoreElements()) {
1207                     String value = name;
1208                     name = null;
1209                     return value;
1210                 }
1211                 throw new NoSuchElementException();
1212             }
1213         };
1214     }
1215 
1216     CodeSource getCodeSource(URL url, String name) {
1217         ensureInitialization();
1218         if (jv != null) {
1219             if (jv.eagerValidation) {
1220                 CodeSource cs = null;
1221                 JarEntry je = getJarEntry(name);
1222                 if (je != null) {
1223                     cs = jv.getCodeSource(url, this, je);
1224                 } else {
1225                     cs = jv.getCodeSource(url, name);
1226                 }
1227                 return cs;
1228             } else {
1229                 return jv.getCodeSource(url, name);
1230             }
1231         }
1232 
1233         return JarVerifier.getUnsignedCS(url);
1234     }
1235 
1236     void setEagerValidation(boolean eager) {
1237         try {
1238             maybeInstantiateVerifier();
1239         } catch (IOException e) {
1240             throw new RuntimeException(e);
1241         }
1242         if (jv != null) {
1243             jv.setEagerValidation(eager);
1244         }
1245     }
1246 
1247     List<Object> getManifestDigests() {
1248         ensureInitialization();
1249         if (jv != null) {
1250             return jv.getManifestDigests();
1251         }
1252         return new ArrayList<>();
1253     }
1254 }
1255