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 sun.security.action.GetPropertyAction;
30 import sun.security.util.ManifestEntryVerifier;
31 import sun.security.util.SignatureFileVerifier;
32
33 import java.io.ByteArrayInputStream;
34 import java.io.EOFException;
35 import java.io.File;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.lang.ref.SoftReference;
39 import java.net.URL;
40 import java.security.CodeSigner;
41 import java.security.CodeSource;
42 import java.security.cert.Certificate;
43 import java.util.ArrayList;
44 import java.util.Enumeration;
45 import java.util.Iterator;
46 import java.util.List;
47 import java.util.Locale;
48 import java.util.NoSuchElementException;
49 import java.util.Objects;
50 import java.util.Spliterator;
51 import java.util.Spliterators;
52 import java.util.stream.Stream;
53 import java.util.stream.StreamSupport;
54 import java.util.zip.ZipEntry;
55 import java.util.zip.ZipException;
56 import java.util.zip.ZipFile;
57
58 /**
59 * The {@code JarFile} class is used to read the contents of a jar file
60 * from any file that can be opened with {@code java.io.RandomAccessFile}.
61 * It extends the class {@code java.util.zip.ZipFile} with support
62 * for reading an optional {@code Manifest} entry, and support for
63 * processing multi-release jar files. The {@code Manifest} can be used
64 * to specify meta-information about the jar file and its entries.
65 *
66 * <p><a id="multirelease">A multi-release jar file</a> is a jar file that
67 * contains a manifest with a main attribute named "Multi-Release",
68 * a set of "base" entries, some of which are public classes with public
69 * or protected methods that comprise the public interface of the jar file,
70 * and a set of "versioned" entries contained in subdirectories of the
71 * "META-INF/versions" directory. The versioned entries are partitioned by the
146 class JarFile extends ZipFile {
147 private final static Runtime.Version BASE_VERSION;
148 private final static int BASE_VERSION_MAJOR;
149 private final static Runtime.Version RUNTIME_VERSION;
150 private final static boolean MULTI_RELEASE_ENABLED;
151 private final static boolean MULTI_RELEASE_FORCED;
152 private SoftReference<Manifest> manRef;
153 private JarEntry manEntry;
154 private JarVerifier jv;
155 private boolean jvInitialized;
156 private boolean verify;
157 private final Runtime.Version version; // current version
158 private final int versionMajor; // version.major()
159 private boolean isMultiRelease; // is jar multi-release?
160
161 // indicates if Class-Path attribute present
162 private boolean hasClassPathAttribute;
163 // true if manifest checked for special attributes
164 private volatile boolean hasCheckedSpecialAttributes;
165
166 static {
167 // Set up JavaUtilJarAccess in SharedSecrets
168 SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
169 // multi-release jar file versions >= 9
170 BASE_VERSION = Runtime.Version.parse(Integer.toString(8));
171 BASE_VERSION_MAJOR = BASE_VERSION.major();
172 String jarVersion = GetPropertyAction.privilegedGetProperty("jdk.util.jar.version");
173 int runtimeVersion = Runtime.version().major();
174 if (jarVersion != null) {
175 int jarVer = Integer.parseInt(jarVersion);
176 runtimeVersion = (jarVer > runtimeVersion)
177 ? runtimeVersion
178 : Math.max(jarVer, BASE_VERSION_MAJOR);
179 }
180 RUNTIME_VERSION = Runtime.Version.parse(Integer.toString(runtimeVersion));
181 String enableMultiRelease = GetPropertyAction
182 .privilegedGetProperty("jdk.util.jar.enableMultiRelease", "true");
183 switch (enableMultiRelease) {
184 case "true":
185 default:
186 MULTI_RELEASE_ENABLED = true;
187 MULTI_RELEASE_FORCED = false;
188 break;
407 JarEntry manEntry = getManEntry();
408
409 // If found then load the manifest
410 if (manEntry != null) {
411 if (verify) {
412 byte[] b = getBytes(manEntry);
413 man = new Manifest(new ByteArrayInputStream(b));
414 if (!jvInitialized) {
415 jv = new JarVerifier(b);
416 }
417 } else {
418 man = new Manifest(super.getInputStream(manEntry));
419 }
420 manRef = new SoftReference<>(man);
421 }
422 }
423 return man;
424 }
425
426 private String[] getMetaInfEntryNames() {
427 return jdk.internal.misc.SharedSecrets.getJavaUtilZipFileAccess()
428 .getMetaInfEntryNames((ZipFile)this);
429 }
430
431 /**
432 * Returns the {@code JarEntry} for the given base entry name or
433 * {@code null} if not found.
434 *
435 * <p>If this {@code JarFile} is a multi-release jar file and is configured
436 * to be processed as such, then a search is performed to find and return
437 * a {@code JarEntry} that is the latest versioned entry associated with the
438 * given entry name. The returned {@code JarEntry} is the versioned entry
439 * corresponding to the given base entry name prefixed with the string
440 * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
441 * which an entry exists. If such a versioned entry does not exist, then
442 * the {@code JarEntry} for the base entry is returned, otherwise
443 * {@code null} is returned if no entries are found. The initial value for
444 * the version {@code n} is the maximum version as returned by the method
445 * {@link JarFile#getVersion()}.
446 *
447 * @param name the jar file entry name
448 * @return the {@code JarEntry} for the given entry name, or
480 *
481 * @param name the jar file entry name
482 * @return the {@code ZipEntry} for the given entry name or
483 * the versioned entry name or {@code null} if not found
484 *
485 * @throws IllegalStateException
486 * may be thrown if the jar file has been closed
487 *
488 * @see java.util.zip.ZipEntry
489 *
490 * @implSpec
491 * <div class="block">
492 * This implementation may return a versioned entry for the requested name
493 * even if there is not a corresponding base entry. This can occur
494 * if there is a private or package-private versioned entry that matches.
495 * If a subclass overrides this method, assure that the override method
496 * invokes {@code super.getEntry(name)} to obtain all versioned entries.
497 * </div>
498 */
499 public ZipEntry getEntry(String name) {
500 ZipEntry ze = super.getEntry(name);
501 if (ze != null) {
502 return new JarFileEntry(ze);
503 }
504 // no matching base entry, but maybe there is a versioned entry,
505 // like a new private class
506 if (isMultiRelease()) {
507 ze = new ZipEntry(name);
508 ZipEntry vze = getVersionedEntry(ze);
509 if (ze != vze) {
510 return new JarFileEntry(name, vze);
511 }
512 }
513 return null;
514 }
515
516 private class JarEntryIterator implements Enumeration<JarEntry>,
517 Iterator<JarEntry>
518 {
519 final Enumeration<? extends ZipEntry> e = JarFile.super.entries();
520
521 public boolean hasNext() {
522 return e.hasMoreElements();
523 }
524
525 public JarEntry next() {
526 ZipEntry ze = e.nextElement();
527 return new JarFileEntry(ze.getName(), ze);
528 }
529
530 public boolean hasMoreElements() {
531 return hasNext();
532 }
533
534 public JarEntry nextElement() {
535 return next();
536 }
537
538 public Iterator<JarEntry> asIterator() {
539 return this;
540 }
541 }
542
543 /**
544 * Returns an enumeration of the jar file entries.
545 *
546 * @return an enumeration of the jar file entries
547 * @throws IllegalStateException
548 * may be thrown if the jar file has been closed
549 */
550 public Enumeration<JarEntry> entries() {
551 return new JarEntryIterator();
552 }
553
554 /**
555 * Returns an ordered {@code Stream} over the jar file entries.
556 * Entries appear in the {@code Stream} in the order they appear in
557 * the central directory of the jar file.
558 *
559 * @return an ordered {@code Stream} of entries in this jar file
560 * @throws IllegalStateException if the jar file has been closed
561 * @since 1.8
562 */
563 public Stream<JarEntry> stream() {
564 return StreamSupport.stream(Spliterators.spliterator(
565 new JarEntryIterator(), size(),
566 Spliterator.ORDERED | Spliterator.DISTINCT |
567 Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
568 }
569
570 private ZipEntry searchForVersionedEntry(final int version, String name) {
571 ZipEntry vze = null;
572 String sname = "/" + name;
573 int i = version;
574 while (i > BASE_VERSION_MAJOR) {
575 vze = super.getEntry(META_INF_VERSIONS + i + sname);
576 if (vze != null) break;
577 i--;
578 }
579 return vze;
580 }
581
582 private ZipEntry getVersionedEntry(ZipEntry ze) {
583 ZipEntry vze = null;
584 if (BASE_VERSION_MAJOR < versionMajor) {
585 String name = ze.getName();
586 if (!name.startsWith(META_INF)) {
587 vze = searchForVersionedEntry(versionMajor, name);
588 }
589 }
590 return vze == null ? ze : vze;
591 }
592
593 /**
594 * Returns the real name of a {@code JarEntry}. If this {@code JarFile} is
595 * a multi-release jar file and is configured to be processed as such, the
596 * name returned by this method is the path name of the versioned entry
597 * that the {@code JarEntry} represents, rather than the path name of the
598 * base entry that {@link JarEntry#getName()} returns. If the
599 * {@code JarEntry} does not represent a versioned entry, or the
600 * jar file is not a multi-release jar file or {@code JarFile} is not
601 * configured for processing a multi-release jar file, this method returns
602 * the same name that {@link JarEntry#getName()} returns.
603 *
604 * @param entry the JarEntry
605 * @return the real name of the JarEntry
606 * @since 9
607 */
608 String getRealName(JarEntry entry) {
609 if (entry instanceof JarFileEntry) {
610 return ((JarFileEntry)entry).realName();
611 }
612 return entry.getName();
613 }
614
615 private class JarFileEntry extends JarEntry {
616 final private String name;
617
618 JarFileEntry(ZipEntry ze) {
619 super(isMultiRelease() ? getVersionedEntry(ze) : ze);
620 this.name = ze.getName();
621 }
622 JarFileEntry(String name, ZipEntry vze) {
623 super(vze);
624 this.name = name;
625 }
626 public Attributes getAttributes() throws IOException {
627 Manifest man = JarFile.this.getManifest();
628 if (man != null) {
629 return man.getAttributes(super.getName());
630 } else {
631 return null;
632 }
633 }
634 public Certificate[] getCertificates() {
635 try {
636 maybeInstantiateVerifier();
637 } catch (IOException e) {
638 throw new RuntimeException(e);
639 }
640 if (certs == null && jv != null) {
641 certs = jv.getCerts(JarFile.this, realEntry());
642 }
643 return certs == null ? null : certs.clone();
644 }
645 public CodeSigner[] getCodeSigners() {
646 try {
647 maybeInstantiateVerifier();
648 } catch (IOException e) {
649 throw new RuntimeException(e);
650 }
651 if (signers == null && jv != null) {
652 signers = jv.getCodeSigners(JarFile.this, realEntry());
653 }
654 return signers == null ? null : signers.clone();
655 }
656 JarFileEntry realEntry() {
657 if (isMultiRelease() && versionMajor != BASE_VERSION_MAJOR) {
658 String entryName = super.getName();
659 return entryName.equals(this.name) ? this : new JarFileEntry(entryName, this);
660 }
661 return this;
662 }
663 String realName() {
664 return super.getName();
665 }
666
667 @Override
668 public String getName() {
669 return name;
670 }
671 }
672
673 /*
674 * Ensures that the JarVerifier has been created if one is
675 * necessary (i.e., the jar appears to be signed.) This is done as
676 * a quick check to avoid processing of the manifest for unsigned
677 * jars.
678 */
679 private void maybeInstantiateVerifier() throws IOException {
680 if (jv != null) {
681 return;
682 }
683
684 if (verify) {
685 String[] names = getMetaInfEntryNames();
686 if (names != null) {
687 for (String nameLower : names) {
688 String name = nameLower.toUpperCase(Locale.ENGLISH);
689 if (name.endsWith(".DSA") ||
690 name.endsWith(".RSA") ||
691 name.endsWith(".EC") ||
692 name.endsWith(".SF")) {
693 // Assume since we found a signature-related file
694 // that the jar is signed and that we therefore
695 // need a JarVerifier and Manifest
696 getManifest();
697 return;
698 }
699 }
700 }
701 // No signature-related files; don't instantiate a
702 // verifier
703 verify = false;
704 }
705 }
706
707
708 /*
709 * Initializes the verifier object by reading all the manifest
710 * entries and passing them to the verifier.
711 */
712 private void initializeVerifier() {
713 ManifestEntryVerifier mev = null;
714
715 // Verify "META-INF/" entries...
716 try {
717 String[] names = getMetaInfEntryNames();
718 if (names != null) {
719 for (String name : names) {
720 String uname = name.toUpperCase(Locale.ENGLISH);
721 if (MANIFEST_NAME.equals(uname)
722 || SignatureFileVerifier.isBlockOrSF(uname)) {
723 JarEntry e = getJarEntry(name);
724 if (e == null) {
725 throw new JarException("corrupted jar file");
726 }
727 if (mev == null) {
887 MULTIRELEASE_LASTOCC[(int)'-' - 32] = 6;
888 MULTIRELEASE_LASTOCC[(int)'L' - 32] = 9;
889 MULTIRELEASE_LASTOCC[(int)'A' - 32] = 11;
890 MULTIRELEASE_LASTOCC[(int)'S' - 32] = 12;
891 MULTIRELEASE_LASTOCC[(int)':' - 32] = 14;
892 MULTIRELEASE_LASTOCC[(int)' ' - 32] = 15;
893 MULTIRELEASE_LASTOCC[(int)'T' - 32] = 16;
894 MULTIRELEASE_LASTOCC[(int)'R' - 32] = 17;
895 MULTIRELEASE_LASTOCC[(int)'U' - 32] = 18;
896 MULTIRELEASE_LASTOCC[(int)'E' - 32] = 19;
897 for (int i = 0; i < 17; i++) {
898 MULTIRELEASE_OPTOSFT[i] = 19;
899 }
900 MULTIRELEASE_OPTOSFT[17] = 6;
901 MULTIRELEASE_OPTOSFT[18] = 1;
902 }
903
904 private JarEntry getManEntry() {
905 if (manEntry == null) {
906 // First look up manifest entry using standard name
907 ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
908 if (manEntry == null) {
909 // If not found, then iterate through all the "META-INF/"
910 // entries to find a match.
911 String[] names = getMetaInfEntryNames();
912 if (names != null) {
913 for (String name : names) {
914 if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
915 manEntry = super.getEntry(name);
916 break;
917 }
918 }
919 }
920 }
921 this.manEntry = (manEntry == null)
922 ? null
923 : new JarFileEntry(manEntry.getName(), manEntry);
924 }
925 return manEntry;
926 }
927
928 /**
929 * Returns {@code true} iff this JAR file has a manifest with the
930 * Class-Path attribute
931 */
932 boolean hasClassPathAttribute() throws IOException {
933 checkForSpecialAttributes();
934 return hasClassPathAttribute;
935 }
936
937 /**
938 * Returns true if the pattern {@code src} is found in {@code b}.
939 * The {@code lastOcc} array is the precomputed bad character shifts.
940 * Since there are no repeated substring in our search strings,
941 * the good suffix shifts can be replaced with a comparison.
942 */
943 private int match(byte[] src, byte[] b, byte[] lastOcc, byte[] optoSft) {
1015 }
1016 }
1017 }
1018 }
1019 hasCheckedSpecialAttributes = true;
1020 }
1021 }
1022
1023 private synchronized void ensureInitialization() {
1024 try {
1025 maybeInstantiateVerifier();
1026 } catch (IOException e) {
1027 throw new RuntimeException(e);
1028 }
1029 if (jv != null && !jvInitialized) {
1030 initializeVerifier();
1031 jvInitialized = true;
1032 }
1033 }
1034
1035 JarEntry newEntry(ZipEntry ze) {
1036 return new JarFileEntry(ze);
1037 }
1038
1039 Enumeration<String> entryNames(CodeSource[] cs) {
1040 ensureInitialization();
1041 if (jv != null) {
1042 return jv.entryNames(this, cs);
1043 }
1044
1045 /*
1046 * JAR file has no signed content. Is there a non-signing
1047 * code source?
1048 */
1049 boolean includeUnsigned = false;
1050 for (CodeSource c : cs) {
1051 if (c.getCodeSigners() == null) {
1052 includeUnsigned = true;
1053 break;
1054 }
1055 }
1056 if (includeUnsigned) {
1060
1061 public boolean hasMoreElements() {
1062 return false;
1063 }
1064
1065 public String nextElement() {
1066 throw new NoSuchElementException();
1067 }
1068 };
1069 }
1070 }
1071
1072 /**
1073 * Returns an enumeration of the zip file entries
1074 * excluding internal JAR mechanism entries and including
1075 * signed entries missing from the ZIP directory.
1076 */
1077 Enumeration<JarEntry> entries2() {
1078 ensureInitialization();
1079 if (jv != null) {
1080 return jv.entries2(this, super.entries());
1081 }
1082
1083 // screen out entries which are never signed
1084 final Enumeration<? extends ZipEntry> enum_ = super.entries();
1085 return new Enumeration<>() {
1086
1087 ZipEntry entry;
1088
1089 public boolean hasMoreElements() {
1090 if (entry != null) {
1091 return true;
1092 }
1093 while (enum_.hasMoreElements()) {
1094 ZipEntry ze = enum_.nextElement();
1095 if (JarVerifier.isSigningRelated(ze.getName())) {
1096 continue;
1097 }
1098 entry = ze;
1099 return true;
1100 }
1101 return false;
1102 }
1103
1104 public JarFileEntry nextElement() {
1105 if (hasMoreElements()) {
1106 ZipEntry ze = entry;
1107 entry = null;
1108 return new JarFileEntry(ze);
1109 }
1110 throw new NoSuchElementException();
1111 }
1112 };
1113 }
1114
1115 CodeSource[] getCodeSources(URL url) {
1116 ensureInitialization();
1117 if (jv != null) {
1118 return jv.getCodeSources(this, url);
1119 }
1120
1121 /*
1122 * JAR file has no signed content. Is there a non-signing
1123 * code source?
1124 */
1125 Enumeration<String> unsigned = unsignedEntryNames();
1126 if (unsigned.hasMoreElements()) {
1127 return new CodeSource[]{JarVerifier.getUnsignedCS(url)};
1128 } else {
|
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.Enumeration;
46 import java.util.Iterator;
47 import java.util.List;
48 import java.util.Locale;
49 import java.util.Map;
50 import java.util.NoSuchElementException;
51 import java.util.Objects;
52 import java.util.Spliterator;
53 import java.util.Spliterators;
54 import java.util.stream.Collector;
55 import java.util.stream.Stream;
56 import java.util.stream.StreamSupport;
57 import java.util.zip.ZipEntry;
58 import java.util.zip.ZipException;
59 import java.util.zip.ZipFile;
60
61 /**
62 * The {@code JarFile} class is used to read the contents of a jar file
63 * from any file that can be opened with {@code java.io.RandomAccessFile}.
64 * It extends the class {@code java.util.zip.ZipFile} with support
65 * for reading an optional {@code Manifest} entry, and support for
66 * processing multi-release jar files. The {@code Manifest} can be used
67 * to specify meta-information about the jar file and its entries.
68 *
69 * <p><a id="multirelease">A multi-release jar file</a> is a jar file that
70 * contains a manifest with a main attribute named "Multi-Release",
71 * a set of "base" entries, some of which are public classes with public
72 * or protected methods that comprise the public interface of the jar file,
73 * and a set of "versioned" entries contained in subdirectories of the
74 * "META-INF/versions" directory. The versioned entries are partitioned by the
149 class JarFile extends ZipFile {
150 private final static Runtime.Version BASE_VERSION;
151 private final static int BASE_VERSION_MAJOR;
152 private final static Runtime.Version RUNTIME_VERSION;
153 private final static boolean MULTI_RELEASE_ENABLED;
154 private final static boolean MULTI_RELEASE_FORCED;
155 private SoftReference<Manifest> manRef;
156 private JarEntry manEntry;
157 private JarVerifier jv;
158 private boolean jvInitialized;
159 private boolean verify;
160 private final Runtime.Version version; // current version
161 private final int versionMajor; // version.major()
162 private boolean isMultiRelease; // is jar multi-release?
163
164 // indicates if Class-Path attribute present
165 private boolean hasClassPathAttribute;
166 // true if manifest checked for special attributes
167 private volatile boolean hasCheckedSpecialAttributes;
168
169 private static final JavaUtilZipFileAccess JUZFA;
170
171 static {
172 // Set up JavaUtilJarAccess in SharedSecrets
173 SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
174 // Get JavaUtilZipFileAccess from SharedSecrets
175 JUZFA = jdk.internal.misc.SharedSecrets.getJavaUtilZipFileAccess();
176 // multi-release jar file versions >= 9
177 BASE_VERSION = Runtime.Version.parse(Integer.toString(8));
178 BASE_VERSION_MAJOR = BASE_VERSION.major();
179 String jarVersion = GetPropertyAction.privilegedGetProperty("jdk.util.jar.version");
180 int runtimeVersion = Runtime.version().major();
181 if (jarVersion != null) {
182 int jarVer = Integer.parseInt(jarVersion);
183 runtimeVersion = (jarVer > runtimeVersion)
184 ? runtimeVersion
185 : Math.max(jarVer, BASE_VERSION_MAJOR);
186 }
187 RUNTIME_VERSION = Runtime.Version.parse(Integer.toString(runtimeVersion));
188 String enableMultiRelease = GetPropertyAction
189 .privilegedGetProperty("jdk.util.jar.enableMultiRelease", "true");
190 switch (enableMultiRelease) {
191 case "true":
192 default:
193 MULTI_RELEASE_ENABLED = true;
194 MULTI_RELEASE_FORCED = false;
195 break;
414 JarEntry manEntry = getManEntry();
415
416 // If found then load the manifest
417 if (manEntry != null) {
418 if (verify) {
419 byte[] b = getBytes(manEntry);
420 man = new Manifest(new ByteArrayInputStream(b));
421 if (!jvInitialized) {
422 jv = new JarVerifier(b);
423 }
424 } else {
425 man = new Manifest(super.getInputStream(manEntry));
426 }
427 manRef = new SoftReference<>(man);
428 }
429 }
430 return man;
431 }
432
433 private String[] getMetaInfEntryNames() {
434 return JUZFA.getMetaInfEntryNames((ZipFile)this);
435 }
436
437 /**
438 * Returns the {@code JarEntry} for the given base entry name or
439 * {@code null} if not found.
440 *
441 * <p>If this {@code JarFile} is a multi-release jar file and is configured
442 * to be processed as such, then a search is performed to find and return
443 * a {@code JarEntry} that is the latest versioned entry associated with the
444 * given entry name. The returned {@code JarEntry} is the versioned entry
445 * corresponding to the given base entry name prefixed with the string
446 * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
447 * which an entry exists. If such a versioned entry does not exist, then
448 * the {@code JarEntry} for the base entry is returned, otherwise
449 * {@code null} is returned if no entries are found. The initial value for
450 * the version {@code n} is the maximum version as returned by the method
451 * {@link JarFile#getVersion()}.
452 *
453 * @param name the jar file entry name
454 * @return the {@code JarEntry} for the given entry name, or
486 *
487 * @param name the jar file entry name
488 * @return the {@code ZipEntry} for the given entry name or
489 * the versioned entry name or {@code null} if not found
490 *
491 * @throws IllegalStateException
492 * may be thrown if the jar file has been closed
493 *
494 * @see java.util.zip.ZipEntry
495 *
496 * @implSpec
497 * <div class="block">
498 * This implementation may return a versioned entry for the requested name
499 * even if there is not a corresponding base entry. This can occur
500 * if there is a private or package-private versioned entry that matches.
501 * If a subclass overrides this method, assure that the override method
502 * invokes {@code super.getEntry(name)} to obtain all versioned entries.
503 * </div>
504 */
505 public ZipEntry getEntry(String name) {
506 JarFileEntry je = getEntry0(name);
507 if (isMultiRelease()) {
508 return getVersionedEntry(name, je);
509 }
510 return je;
511 }
512
513 /**
514 * Returns an enumeration of the jar file entries.
515 *
516 * @return an enumeration of the jar file entries
517 * @throws IllegalStateException
518 * may be thrown if the jar file has been closed
519 */
520 public Enumeration<JarEntry> entries() {
521 return JUZFA.entries(this, JarFileEntry::new);
522 }
523
524 /**
525 * Returns an ordered {@code Stream} over the jar file entries.
526 * Entries appear in the {@code Stream} in the order they appear in
527 * the central directory of the jar file.
528 *
529 * @return an ordered {@code Stream} of entries in this jar file
530 * @throws IllegalStateException if the jar file has been closed
531 * @since 1.8
532 */
533 public Stream<JarEntry> stream() {
534 return JUZFA.stream(this, JarFileEntry::new);
535 }
536
537 /**
538 * Returns a {@code Stream} of the versioned jar file entries.
539 *
540 * <p>If this {@code JarFile} is a multi-release jar file and is configured to
541 * be processed as such, then an entry in the stream is the latest versioned entry
542 * associated with the corresponding base entry name. The maximum version of the
543 * latest versioned entry is the version returned by {@link #getVersion()}.
544 * The returned stream may include an entry that only exists as a versioned entry.
545 *
546 * If the jar file is not a multi-release jar file or the {@code JarFile} is not
547 * configured for processing a multi-release jar file, this method returns the
548 * same stream that {@link #stream()} returns.
549 *
550 * @return stream of versioned entries
551 * @since 10
552 */
553 public Stream<JarEntry> versionedStream() {
554
555 if (isMultiRelease()) {
556 return JUZFA.entryNameStream(this).map(this::getBasename)
557 .filter(Objects::nonNull)
558 .distinct()
559 .map(this::getJarEntry);
560 }
561 return stream();
562 }
563
564 /*
565 * Invokes {@ZipFile}'s getEntry to Return a {@code JarFileEntry} for the
566 * given entry name or {@code null} if not found.
567 */
568 private JarFileEntry getEntry0(String name) {
569 return (JarFileEntry)JUZFA.getEntry(this, name, JarFileEntry::new);
570 }
571
572 private String getBasename(String name) {
573 if (name.startsWith(META_INF_VERSIONS)) {
574 int off = META_INF_VERSIONS.length();
575 int index = name.indexOf('/', off);
576 try {
577 // filter out dir META-INF/versions/ and META-INF/versions/*/
578 // and any entry with version > 'version'
579 if (index == -1 || index == (name.length() - 1) ||
580 Integer.parseInt(name, off, index, 10) > versionMajor) {
581 return null;
582 }
583 } catch (NumberFormatException x) {
584 return null; // remove malformed entries silently
585 }
586 // map to its base name
587 return name.substring(index + 1);
588 }
589 return name;
590 }
591
592 private JarEntry getVersionedEntry(String name, JarEntry je) {
593 if (BASE_VERSION_MAJOR < versionMajor) {
594 if (!name.startsWith(META_INF)) {
595 // search for versioned entry
596 int v = versionMajor;
597 while (v > BASE_VERSION_MAJOR) {
598 JarFileEntry vje = getEntry0(META_INF_VERSIONS + v + "/" + name);
599 if (vje != null) {
600 return vje.withBasename(name);
601 }
602 v--;
603 }
604 }
605 }
606 return je;
607 }
608
609 // placeholder for now
610 String getRealName(JarEntry entry) {
611 return entry.getRealName();
612 }
613
614 private class JarFileEntry extends JarEntry {
615 private String basename;
616
617 JarFileEntry(String name) {
618 super(name);
619 this.basename = name;
620 }
621
622 JarFileEntry(String name, ZipEntry vze) {
623 super(vze);
624 this.basename = name;
625 }
626
627 @Override
628 public Attributes getAttributes() throws IOException {
629 Manifest man = JarFile.this.getManifest();
630 if (man != null) {
631 return man.getAttributes(super.getName());
632 } else {
633 return null;
634 }
635 }
636
637 @Override
638 public Certificate[] getCertificates() {
639 try {
640 maybeInstantiateVerifier();
641 } catch (IOException e) {
642 throw new RuntimeException(e);
643 }
644 if (certs == null && jv != null) {
645 certs = jv.getCerts(JarFile.this, realEntry());
646 }
647 return certs == null ? null : certs.clone();
648 }
649
650 @Override
651 public CodeSigner[] getCodeSigners() {
652 try {
653 maybeInstantiateVerifier();
654 } catch (IOException e) {
655 throw new RuntimeException(e);
656 }
657 if (signers == null && jv != null) {
658 signers = jv.getCodeSigners(JarFile.this, realEntry());
659 }
660 return signers == null ? null : signers.clone();
661 }
662
663 @Override
664 public String getRealName() {
665 return super.getName();
666 }
667
668 @Override
669 public String getName() {
670 return basename;
671 }
672
673 JarFileEntry realEntry() {
674 if (isMultiRelease() && versionMajor != BASE_VERSION_MAJOR) {
675 String entryName = super.getName();
676 return entryName == basename || entryName.equals(basename) ?
677 this : new JarFileEntry(entryName, this);
678 }
679 return this;
680 }
681
682 // changes the basename, returns "this"
683 JarFileEntry withBasename(String name) {
684 basename = name;
685 return this;
686 }
687 }
688
689 /*
690 * Ensures that the JarVerifier has been created if one is
691 * necessary (i.e., the jar appears to be signed.) This is done as
692 * a quick check to avoid processing of the manifest for unsigned
693 * jars.
694 */
695 private void maybeInstantiateVerifier() throws IOException {
696 if (jv != null) {
697 return;
698 }
699
700 if (verify) {
701 String[] names = getMetaInfEntryNames();
702 if (names != null) {
703 for (String nameLower : names) {
704 String name = nameLower.toUpperCase(Locale.ENGLISH);
705 if (name.endsWith(".DSA") ||
706 name.endsWith(".RSA") ||
707 name.endsWith(".EC") ||
708 name.endsWith(".SF")) {
709 // Assume since we found a signature-related file
710 // that the jar is signed and that we therefore
711 // need a JarVerifier and Manifest
712 getManifest();
713 return;
714 }
715 }
716 }
717 // No signature-related files; don't instantiate a
718 // verifier
719 verify = false;
720 }
721 }
722
723 /*
724 * Initializes the verifier object by reading all the manifest
725 * entries and passing them to the verifier.
726 */
727 private void initializeVerifier() {
728 ManifestEntryVerifier mev = null;
729
730 // Verify "META-INF/" entries...
731 try {
732 String[] names = getMetaInfEntryNames();
733 if (names != null) {
734 for (String name : names) {
735 String uname = name.toUpperCase(Locale.ENGLISH);
736 if (MANIFEST_NAME.equals(uname)
737 || SignatureFileVerifier.isBlockOrSF(uname)) {
738 JarEntry e = getJarEntry(name);
739 if (e == null) {
740 throw new JarException("corrupted jar file");
741 }
742 if (mev == null) {
902 MULTIRELEASE_LASTOCC[(int)'-' - 32] = 6;
903 MULTIRELEASE_LASTOCC[(int)'L' - 32] = 9;
904 MULTIRELEASE_LASTOCC[(int)'A' - 32] = 11;
905 MULTIRELEASE_LASTOCC[(int)'S' - 32] = 12;
906 MULTIRELEASE_LASTOCC[(int)':' - 32] = 14;
907 MULTIRELEASE_LASTOCC[(int)' ' - 32] = 15;
908 MULTIRELEASE_LASTOCC[(int)'T' - 32] = 16;
909 MULTIRELEASE_LASTOCC[(int)'R' - 32] = 17;
910 MULTIRELEASE_LASTOCC[(int)'U' - 32] = 18;
911 MULTIRELEASE_LASTOCC[(int)'E' - 32] = 19;
912 for (int i = 0; i < 17; i++) {
913 MULTIRELEASE_OPTOSFT[i] = 19;
914 }
915 MULTIRELEASE_OPTOSFT[17] = 6;
916 MULTIRELEASE_OPTOSFT[18] = 1;
917 }
918
919 private JarEntry getManEntry() {
920 if (manEntry == null) {
921 // First look up manifest entry using standard name
922 JarEntry manEntry = getEntry0(MANIFEST_NAME);
923 if (manEntry == null) {
924 // If not found, then iterate through all the "META-INF/"
925 // entries to find a match.
926 String[] names = getMetaInfEntryNames();
927 if (names != null) {
928 for (String name : names) {
929 if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
930 manEntry = getEntry0(name);
931 break;
932 }
933 }
934 }
935 }
936 this.manEntry = manEntry;
937 }
938 return manEntry;
939 }
940
941 /**
942 * Returns {@code true} iff this JAR file has a manifest with the
943 * Class-Path attribute
944 */
945 boolean hasClassPathAttribute() throws IOException {
946 checkForSpecialAttributes();
947 return hasClassPathAttribute;
948 }
949
950 /**
951 * Returns true if the pattern {@code src} is found in {@code b}.
952 * The {@code lastOcc} array is the precomputed bad character shifts.
953 * Since there are no repeated substring in our search strings,
954 * the good suffix shifts can be replaced with a comparison.
955 */
956 private int match(byte[] src, byte[] b, byte[] lastOcc, byte[] optoSft) {
1028 }
1029 }
1030 }
1031 }
1032 hasCheckedSpecialAttributes = true;
1033 }
1034 }
1035
1036 private synchronized void ensureInitialization() {
1037 try {
1038 maybeInstantiateVerifier();
1039 } catch (IOException e) {
1040 throw new RuntimeException(e);
1041 }
1042 if (jv != null && !jvInitialized) {
1043 initializeVerifier();
1044 jvInitialized = true;
1045 }
1046 }
1047
1048 /*
1049 * Returns a versioned {@code JarFileEntry} for the given entry,
1050 * if there is one. Otherwise returns the original entry. This
1051 * is invoked by the {@code entries2} for verifier.
1052 */
1053 JarEntry newEntry(JarEntry je) {
1054 if (isMultiRelease()) {
1055 return getVersionedEntry(je.getName(), je);
1056 }
1057 return je;
1058 }
1059
1060 /*
1061 * Returns a versioned {@code JarFileEntry} for the given entry
1062 * name, if there is one. Otherwise returns a {@code JarFileEntry}
1063 * with the given name. It is invoked from JarVerifier's entries2
1064 * for {@code singers}.
1065 */
1066 JarEntry newEntry(String name) {
1067 if (isMultiRelease()) {
1068 JarEntry vje = getVersionedEntry(name, (JarEntry)null);
1069 if (vje != null) {
1070 return vje;
1071 }
1072 }
1073 return new JarFileEntry(name);
1074 }
1075
1076 Enumeration<String> entryNames(CodeSource[] cs) {
1077 ensureInitialization();
1078 if (jv != null) {
1079 return jv.entryNames(this, cs);
1080 }
1081
1082 /*
1083 * JAR file has no signed content. Is there a non-signing
1084 * code source?
1085 */
1086 boolean includeUnsigned = false;
1087 for (CodeSource c : cs) {
1088 if (c.getCodeSigners() == null) {
1089 includeUnsigned = true;
1090 break;
1091 }
1092 }
1093 if (includeUnsigned) {
1097
1098 public boolean hasMoreElements() {
1099 return false;
1100 }
1101
1102 public String nextElement() {
1103 throw new NoSuchElementException();
1104 }
1105 };
1106 }
1107 }
1108
1109 /**
1110 * Returns an enumeration of the zip file entries
1111 * excluding internal JAR mechanism entries and including
1112 * signed entries missing from the ZIP directory.
1113 */
1114 Enumeration<JarEntry> entries2() {
1115 ensureInitialization();
1116 if (jv != null) {
1117 return jv.entries2(this, JUZFA.entries(JarFile.this,
1118 JarFileEntry::new));
1119 }
1120
1121 // screen out entries which are never signed
1122 final var unfilteredEntries = JUZFA.entries(JarFile.this, JarFileEntry::new);
1123
1124 return new Enumeration<>() {
1125
1126 JarEntry entry;
1127
1128 public boolean hasMoreElements() {
1129 if (entry != null) {
1130 return true;
1131 }
1132 while (unfilteredEntries.hasMoreElements()) {
1133 JarEntry je = unfilteredEntries.nextElement();
1134 if (JarVerifier.isSigningRelated(je.getName())) {
1135 continue;
1136 }
1137 entry = je;
1138 return true;
1139 }
1140 return false;
1141 }
1142
1143 public JarEntry nextElement() {
1144 if (hasMoreElements()) {
1145 JarEntry je = entry;
1146 entry = null;
1147 return newEntry(je);
1148 }
1149 throw new NoSuchElementException();
1150 }
1151 };
1152 }
1153
1154 CodeSource[] getCodeSources(URL url) {
1155 ensureInitialization();
1156 if (jv != null) {
1157 return jv.getCodeSources(this, url);
1158 }
1159
1160 /*
1161 * JAR file has no signed content. Is there a non-signing
1162 * code source?
1163 */
1164 Enumeration<String> unsigned = unsignedEntryNames();
1165 if (unsigned.hasMoreElements()) {
1166 return new CodeSource[]{JarVerifier.getUnsignedCS(url)};
1167 } else {
|