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 java.io.*;
29 import java.lang.ref.SoftReference;
30 import java.net.URL;
31 import java.security.PrivilegedAction;
32 import java.util.*;
33 import java.util.stream.Stream;
34 import java.util.stream.StreamSupport;
35 import java.util.zip.*;
36 import java.security.CodeSigner;
37 import java.security.cert.Certificate;
38 import java.security.AccessController;
39 import java.security.CodeSource;
40 import jdk.internal.misc.SharedSecrets;
41 import sun.security.util.ManifestEntryVerifier;
42 import sun.security.util.SignatureFileVerifier;
43
44 import static java.util.jar.Attributes.Name.MULTI_RELEASE;
45
46 /**
47 * The {@code JarFile} class is used to read the contents of a jar file
48 * from any file that can be opened with {@code java.io.RandomAccessFile}.
49 * It extends the class {@code java.util.zip.ZipFile} with support
50 * for reading an optional {@code Manifest} entry, and support for
51 * processing multi-release jar files. The {@code Manifest} can be used
52 * to specify meta-information about the jar file and its entries.
53 *
54 * <p>A multi-release jar file is a jar file that contains
55 * a manifest with a main attribute named "Multi-Release",
56 * a set of "base" entries, some of which are public classes with public
57 * or protected methods that comprise the public interface of the jar file,
58 * and a set of "versioned" entries contained in subdirectories of the
59 * "META-INF/versions" directory. The versioned entries are partitioned by the
60 * major version of the Java platform. A versioned entry, with a version
61 * {@code n}, {@code 8 < n}, in the "META-INF/versions/{n}" directory overrides
62 * the base entry as well as any entry with a version number {@code i} where
63 * {@code 8 < i < n}.
64 *
65 * <p>By default, a {@code JarFile} for a multi-release jar file is configured
127 * @author David Connelly
128 * @see Manifest
129 * @see java.util.zip.ZipFile
130 * @see java.util.jar.JarEntry
131 * @since 1.2
132 */
133 public
134 class JarFile extends ZipFile {
135 private final static int BASE_VERSION;
136 private final static int RUNTIME_VERSION;
137 private final static boolean MULTI_RELEASE_ENABLED;
138 private final static boolean MULTI_RELEASE_FORCED;
139 private SoftReference<Manifest> manRef;
140 private JarEntry manEntry;
141 private JarVerifier jv;
142 private boolean jvInitialized;
143 private boolean verify;
144 private final int version;
145 private boolean notVersioned;
146 private final boolean runtimeVersioned;
147
148 // indicates if Class-Path attribute present (only valid if hasCheckedSpecialAttributes true)
149 private boolean hasClassPathAttribute;
150 // true if manifest checked for special attributes
151 private volatile boolean hasCheckedSpecialAttributes;
152
153 static {
154 // Set up JavaUtilJarAccess in SharedSecrets
155 SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
156
157 BASE_VERSION = 8; // one less than lowest version for versioned entries
158 RUNTIME_VERSION = AccessController.doPrivileged(
159 new PrivilegedAction<Integer>() {
160 public Integer run() {
161 Integer v = jdk.Version.current().major();
162 Integer i = Integer.getInteger("jdk.util.jar.version", v);
163 i = i < 0 ? 0 : i;
164 return i > v ? v : i;
165 }
166 }
167 );
168 String multi_release = AccessController.doPrivileged(
169 new PrivilegedAction<String>() {
170 public String run() {
171 return System.getProperty("jdk.util.jar.enableMultiRelease", "true");
172 }
173 }
174 );
175 switch (multi_release) {
176 case "true":
177 default:
178 MULTI_RELEASE_ENABLED = true;
179 MULTI_RELEASE_FORCED = false;
180 break;
181 case "false":
182 MULTI_RELEASE_ENABLED = false;
183 MULTI_RELEASE_FORCED = false;
184 break;
185 case "force":
186 MULTI_RELEASE_ENABLED = true;
187 MULTI_RELEASE_FORCED = true;
188 break;
189 }
190 }
191
192 /**
193 * A set of constants that represent the entries in either the base directory
194 * or one of the versioned directories in a multi-release jar file. It's
195 * possible for a multi-release jar file to contain versioned directories
336 * multi-release jar files.
337 *
338 * @param file the jar file to be opened for reading
339 * @param verify whether or not to verify the jar file if
340 * it is signed.
341 * @param mode the mode in which the file is to be opened
342 * @param version specifies the release version for a multi-release jar file
343 * @throws IOException if an I/O error has occurred
344 * @throws IllegalArgumentException
345 * if the {@code mode} argument is invalid
346 * @throws SecurityException if access to the file is denied
347 * by the SecurityManager
348 * @throws NullPointerException if {@code version} is {@code null}
349 * @since 9
350 */
351 public JarFile(File file, boolean verify, int mode, Release version) throws IOException {
352 super(file, mode);
353 Objects.requireNonNull(version);
354 this.verify = verify;
355 // version applies to multi-release jar files, ignored for regular jar files
356 this.version = MULTI_RELEASE_FORCED ? RUNTIME_VERSION : version.value();
357 this.runtimeVersioned = version == Release.RUNTIME;
358 assert runtimeVersionExists();
359 }
360
361 private boolean runtimeVersionExists() {
362 int version = jdk.Version.current().major();
363 try {
364 Release.valueOf(version);
365 return true;
366 } catch (IllegalArgumentException x) {
367 System.err.println("No JarFile.Release object for release " + version);
368 return false;
369 }
370 }
371
372 /**
373 * Returns the maximum version used when searching for versioned entries.
374 *
375 * @return the maximum version, or {@code Release.BASE} if this jar file is
376 * processed as if it is an unversioned jar file or is not a
377 * multi-release jar file
378 * @since 9
379 */
380 public final Release getVersion() {
381 if (isMultiRelease()) {
382 return runtimeVersioned ? Release.RUNTIME : Release.valueOf(version);
383 } else {
384 return Release.BASE;
385 }
386 }
387
388 /**
389 * Indicates whether or not this jar file is a multi-release jar file.
390 *
391 * @return true if this JarFile is a multi-release jar file
392 * @since 9
393 */
394 public final boolean isMultiRelease() {
395 // do not call this code in a constructor because some subclasses use
396 // lazy loading of manifest so it won't be available at construction time
397 if (MULTI_RELEASE_ENABLED) {
398 // Doubled-checked locking pattern
399 Boolean result = isMultiRelease;
400 if (result == null) {
401 synchronized (this) {
402 result = isMultiRelease;
403 if (result == null) {
404 Manifest man = null;
405 try {
406 man = getManifest();
407 } catch (IOException e) {
408 //Ignored, manifest cannot be read
409 }
410 isMultiRelease = result = (man != null)
411 && man.getMainAttributes().containsKey(MULTI_RELEASE)
412 ? Boolean.TRUE : Boolean.FALSE;
413 }
414 }
415 }
416 return result == Boolean.TRUE;
417 } else {
418 return false;
419 }
420 }
421 // the following field, isMultiRelease, should only be used in the method
422 // isMultiRelease(), like a static local
423 private volatile Boolean isMultiRelease; // is jar multi-release?
424
425 /**
426 * Returns the jar file manifest, or {@code null} if none.
427 *
428 * @return the jar file manifest, or {@code null} if none
429 *
430 * @throws IllegalStateException
431 * may be thrown if the jar file has been closed
432 * @throws IOException if an I/O error has occurred
433 */
434 public Manifest getManifest() throws IOException {
435 return getManifestFromReference();
436 }
437
438 private Manifest getManifestFromReference() throws IOException {
439 Manifest man = manRef != null ? manRef.get() : null;
440
441 if (man == null) {
442
443 JarEntry manEntry = getManEntry();
888 return new JarVerifier.VerifierStream(
889 getManifestFromReference(),
890 verifiableEntry(ze),
891 super.getInputStream(ze),
892 jv);
893 }
894
895 private JarEntry verifiableEntry(ZipEntry ze) {
896 if (ze instanceof JarFileEntry) {
897 // assure the name and entry match for verification
898 return ((JarFileEntry)ze).reifiedEntry();
899 }
900 ze = getJarEntry(ze.getName());
901 if (ze instanceof JarFileEntry) {
902 return ((JarFileEntry)ze).reifiedEntry();
903 }
904 return (JarEntry)ze;
905 }
906
907 // Statics for hand-coded Boyer-Moore search
908 private static final char[] CLASSPATH_CHARS = {'c','l','a','s','s','-','p','a','t','h'};
909 // The bad character shift for "class-path"
910 private static final int[] CLASSPATH_LASTOCC;
911 // The good suffix shift for "class-path"
912 private static final int[] CLASSPATH_OPTOSFT;
913
914 static {
915 CLASSPATH_LASTOCC = new int[128];
916 CLASSPATH_OPTOSFT = new int[10];
917 CLASSPATH_LASTOCC[(int)'c'] = 1;
918 CLASSPATH_LASTOCC[(int)'l'] = 2;
919 CLASSPATH_LASTOCC[(int)'s'] = 5;
920 CLASSPATH_LASTOCC[(int)'-'] = 6;
921 CLASSPATH_LASTOCC[(int)'p'] = 7;
922 CLASSPATH_LASTOCC[(int)'a'] = 8;
923 CLASSPATH_LASTOCC[(int)'t'] = 9;
924 CLASSPATH_LASTOCC[(int)'h'] = 10;
925 for (int i=0; i<9; i++)
926 CLASSPATH_OPTOSFT[i] = 10;
927 CLASSPATH_OPTOSFT[9]=1;
928 }
929
930 private JarEntry getManEntry() {
931 if (manEntry == null) {
932 // First look up manifest entry using standard name
933 ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
934 if (manEntry == null) {
935 // If not found, then iterate through all the "META-INF/"
936 // entries to find a match.
937 String[] names = getMetaInfEntryNames();
938 if (names != null) {
939 for (String name : names) {
940 if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
941 manEntry = super.getEntry(name);
942 break;
943 }
944 }
945 }
946 }
947 this.manEntry = (manEntry == null)
948 ? null
949 : new JarFileEntry(manEntry.getName(), manEntry);
950 }
951 return manEntry;
952 }
953
954 /**
955 * Returns {@code true} iff this JAR file has a manifest with the
956 * Class-Path attribute
957 */
958 boolean hasClassPathAttribute() throws IOException {
959 checkForSpecialAttributes();
960 return hasClassPathAttribute;
961 }
962
963 /**
964 * Returns true if the pattern {@code src} is found in {@code b}.
965 * The {@code lastOcc} and {@code optoSft} arrays are the precomputed
966 * bad character and good suffix shifts.
967 */
968 private boolean match(char[] src, byte[] b, int[] lastOcc, int[] optoSft) {
969 int len = src.length;
970 int last = b.length - len;
971 int i = 0;
972 next:
973 while (i<=last) {
974 for (int j=(len-1); j>=0; j--) {
975 char c = (char) b[i+j];
976 c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c;
977 if (c != src[j]) {
978 i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]);
979 continue next;
980 }
981 }
982 return true;
983 }
984 return false;
985 }
986
987 /**
988 * On first invocation, check if the JAR file has the Class-Path
989 * attribute. A no-op on subsequent calls.
990 */
991 private void checkForSpecialAttributes() throws IOException {
992 if (hasCheckedSpecialAttributes) return;
993 JarEntry manEntry = getManEntry();
994 if (manEntry != null) {
995 byte[] b = getBytes(manEntry);
996 if (match(CLASSPATH_CHARS, b, CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT))
997 hasClassPathAttribute = true;
998 }
999 hasCheckedSpecialAttributes = true;
1000 }
1001
1002 private synchronized void ensureInitialization() {
1003 try {
1004 maybeInstantiateVerifier();
1005 } catch (IOException e) {
1006 throw new RuntimeException(e);
1007 }
1008 if (jv != null && !jvInitialized) {
1009 initializeVerifier();
1010 jvInitialized = true;
1011 }
1012 }
1013
1014 JarEntry newEntry(ZipEntry ze) {
1015 return new JarFileEntry(ze);
1016 }
1017
1018 Enumeration<String> entryNames(CodeSource[] cs) {
1019 ensureInitialization();
|
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 java.io.*;
29 import java.lang.ref.SoftReference;
30 import java.net.URL;
31 import java.util.*;
32 import java.util.stream.Stream;
33 import java.util.stream.StreamSupport;
34 import java.util.zip.*;
35 import java.security.CodeSigner;
36 import java.security.cert.Certificate;
37 import java.security.AccessController;
38 import java.security.CodeSource;
39 import jdk.internal.misc.SharedSecrets;
40 import sun.security.action.GetPropertyAction;
41 import sun.security.util.ManifestEntryVerifier;
42 import sun.security.util.SignatureFileVerifier;
43
44 /**
45 * The {@code JarFile} class is used to read the contents of a jar file
46 * from any file that can be opened with {@code java.io.RandomAccessFile}.
47 * It extends the class {@code java.util.zip.ZipFile} with support
48 * for reading an optional {@code Manifest} entry, and support for
49 * processing multi-release jar files. The {@code Manifest} can be used
50 * to specify meta-information about the jar file and its entries.
51 *
52 * <p>A multi-release jar file is a jar file that contains
53 * a manifest with a main attribute named "Multi-Release",
54 * a set of "base" entries, some of which are public classes with public
55 * or protected methods that comprise the public interface of the jar file,
56 * and a set of "versioned" entries contained in subdirectories of the
57 * "META-INF/versions" directory. The versioned entries are partitioned by the
58 * major version of the Java platform. A versioned entry, with a version
59 * {@code n}, {@code 8 < n}, in the "META-INF/versions/{n}" directory overrides
60 * the base entry as well as any entry with a version number {@code i} where
61 * {@code 8 < i < n}.
62 *
63 * <p>By default, a {@code JarFile} for a multi-release jar file is configured
125 * @author David Connelly
126 * @see Manifest
127 * @see java.util.zip.ZipFile
128 * @see java.util.jar.JarEntry
129 * @since 1.2
130 */
131 public
132 class JarFile extends ZipFile {
133 private final static int BASE_VERSION;
134 private final static int RUNTIME_VERSION;
135 private final static boolean MULTI_RELEASE_ENABLED;
136 private final static boolean MULTI_RELEASE_FORCED;
137 private SoftReference<Manifest> manRef;
138 private JarEntry manEntry;
139 private JarVerifier jv;
140 private boolean jvInitialized;
141 private boolean verify;
142 private final int version;
143 private boolean notVersioned;
144 private final boolean runtimeVersioned;
145 private boolean isMultiRelease; // is jar multi-release?
146
147 // indicates if Class-Path attribute present
148 private boolean hasClassPathAttribute;
149 // true if manifest checked for special attributes
150 private volatile boolean hasCheckedSpecialAttributes;
151
152 static {
153 // Set up JavaUtilJarAccess in SharedSecrets
154 SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
155
156 BASE_VERSION = 8; // one less than lowest version for versioned entries
157 int runtimeVersion = jdk.Version.current().major();
158 String jarVersion = AccessController.doPrivileged(
159 new GetPropertyAction("jdk.util.jar.version"));
160 if (jarVersion != null) {
161 int jarVer = Integer.parseInt(jarVersion);
162 runtimeVersion = (jarVer > runtimeVersion)
163 ? runtimeVersion : Math.max(jarVer, 0);
164 }
165 RUNTIME_VERSION = runtimeVersion;
166 String enableMultiRelease = AccessController.doPrivileged(
167 new GetPropertyAction("jdk.util.jar.enableMultiRelease", "true"));
168 switch (enableMultiRelease) {
169 case "true":
170 default:
171 MULTI_RELEASE_ENABLED = true;
172 MULTI_RELEASE_FORCED = false;
173 break;
174 case "false":
175 MULTI_RELEASE_ENABLED = false;
176 MULTI_RELEASE_FORCED = false;
177 break;
178 case "force":
179 MULTI_RELEASE_ENABLED = true;
180 MULTI_RELEASE_FORCED = true;
181 break;
182 }
183 }
184
185 /**
186 * A set of constants that represent the entries in either the base directory
187 * or one of the versioned directories in a multi-release jar file. It's
188 * possible for a multi-release jar file to contain versioned directories
329 * multi-release jar files.
330 *
331 * @param file the jar file to be opened for reading
332 * @param verify whether or not to verify the jar file if
333 * it is signed.
334 * @param mode the mode in which the file is to be opened
335 * @param version specifies the release version for a multi-release jar file
336 * @throws IOException if an I/O error has occurred
337 * @throws IllegalArgumentException
338 * if the {@code mode} argument is invalid
339 * @throws SecurityException if access to the file is denied
340 * by the SecurityManager
341 * @throws NullPointerException if {@code version} is {@code null}
342 * @since 9
343 */
344 public JarFile(File file, boolean verify, int mode, Release version) throws IOException {
345 super(file, mode);
346 Objects.requireNonNull(version);
347 this.verify = verify;
348 // version applies to multi-release jar files, ignored for regular jar files
349 if (MULTI_RELEASE_FORCED) {
350 this.version = RUNTIME_VERSION;
351 version = Release.RUNTIME;
352 } else {
353 this.version = version.value();
354 }
355 this.runtimeVersioned = version == Release.RUNTIME;
356
357 assert runtimeVersionExists();
358 }
359
360 private boolean runtimeVersionExists() {
361 int version = jdk.Version.current().major();
362 try {
363 Release.valueOf(version);
364 return true;
365 } catch (IllegalArgumentException x) {
366 System.err.println("No JarFile.Release object for release " + version);
367 return false;
368 }
369 }
370
371 /**
372 * Returns the maximum version used when searching for versioned entries.
373 *
374 * @return the maximum version, or {@code Release.BASE} if this jar file is
375 * processed as if it is an unversioned jar file or is not a
376 * multi-release jar file
377 * @since 9
378 */
379 public final Release getVersion() {
380 if (isMultiRelease()) {
381 return runtimeVersioned ? Release.RUNTIME : Release.valueOf(version);
382 } else {
383 return Release.BASE;
384 }
385 }
386
387 /**
388 * Indicates whether or not this jar file is a multi-release jar file.
389 *
390 * @return true if this JarFile is a multi-release jar file
391 * @since 9
392 */
393 public final boolean isMultiRelease() {
394 if (isMultiRelease) {
395 return true;
396 }
397 if (MULTI_RELEASE_ENABLED && version != BASE_VERSION) {
398 try {
399 checkForSpecialAttributes();
400 } catch (IOException io) {
401 isMultiRelease = false;
402 }
403 }
404 return isMultiRelease;
405 }
406
407 /**
408 * Returns the jar file manifest, or {@code null} if none.
409 *
410 * @return the jar file manifest, or {@code null} if none
411 *
412 * @throws IllegalStateException
413 * may be thrown if the jar file has been closed
414 * @throws IOException if an I/O error has occurred
415 */
416 public Manifest getManifest() throws IOException {
417 return getManifestFromReference();
418 }
419
420 private Manifest getManifestFromReference() throws IOException {
421 Manifest man = manRef != null ? manRef.get() : null;
422
423 if (man == null) {
424
425 JarEntry manEntry = getManEntry();
870 return new JarVerifier.VerifierStream(
871 getManifestFromReference(),
872 verifiableEntry(ze),
873 super.getInputStream(ze),
874 jv);
875 }
876
877 private JarEntry verifiableEntry(ZipEntry ze) {
878 if (ze instanceof JarFileEntry) {
879 // assure the name and entry match for verification
880 return ((JarFileEntry)ze).reifiedEntry();
881 }
882 ze = getJarEntry(ze.getName());
883 if (ze instanceof JarFileEntry) {
884 return ((JarFileEntry)ze).reifiedEntry();
885 }
886 return (JarEntry)ze;
887 }
888
889 // Statics for hand-coded Boyer-Moore search
890 private static final byte[] CLASSPATH_CHARS =
891 {'C','L','A','S','S','-','P','A','T','H', ':', ' '};
892
893 // The bad character shift for "class-path:"
894 private static final byte[] CLASSPATH_LASTOCC;
895
896 private static final byte[] MULTIRELEASE_CHARS =
897 {'M','U','L','T','I','-','R','E','L','E', 'A', 'S', 'E', ':', ' '};
898
899 // The bad character shift for "multi-release: "
900 private static final byte[] MULTIRELEASE_LASTOCC;
901
902 static {
903 CLASSPATH_LASTOCC = new byte[64];
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
915 MULTIRELEASE_LASTOCC = new byte[64];
916 MULTIRELEASE_LASTOCC[(int)'M' - 32] = 1;
917 MULTIRELEASE_LASTOCC[(int)'U' - 32] = 2;
918 MULTIRELEASE_LASTOCC[(int)'T' - 32] = 4;
919 MULTIRELEASE_LASTOCC[(int)'I' - 32] = 5;
920 MULTIRELEASE_LASTOCC[(int)'-' - 32] = 6;
921 MULTIRELEASE_LASTOCC[(int)'R' - 32] = 7;
922 MULTIRELEASE_LASTOCC[(int)'L' - 32] = 9;
923 MULTIRELEASE_LASTOCC[(int)'A' - 32] = 11;
924 MULTIRELEASE_LASTOCC[(int)'S' - 32] = 12;
925 MULTIRELEASE_LASTOCC[(int)'E' - 32] = 13;
926 MULTIRELEASE_LASTOCC[(int)':' - 32] = 14;
927 MULTIRELEASE_LASTOCC[(int)' ' - 32] = 15;
928 }
929
930 private JarEntry getManEntry() {
931 if (manEntry == null) {
932 // First look up manifest entry using standard name
933 ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
934 if (manEntry == null) {
935 // If not found, then iterate through all the "META-INF/"
936 // entries to find a match.
937 String[] names = getMetaInfEntryNames();
938 if (names != null) {
939 for (String name : names) {
940 if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
941 manEntry = super.getEntry(name);
942 break;
943 }
944 }
945 }
946 }
947 this.manEntry = (manEntry == null)
948 ? null
949 : new JarFileEntry(manEntry.getName(), manEntry);
950 }
951 return manEntry;
952 }
953
954 /**
955 * Returns {@code true} iff this JAR file has a manifest with the
956 * Class-Path attribute
957 */
958 boolean hasClassPathAttribute() throws IOException {
959 checkForSpecialAttributes();
960 return hasClassPathAttribute;
961 }
962
963 /**
964 * Returns true if the pattern {@code src} is found in {@code b}.
965 * The {@code lastOcc} array is the precomputed bad character shifts.
966 * Since there are no repeated substring in our search strings,
967 * the good suffix shifts can be replaced with a comparison.
968 */
969 private boolean match(byte[] src, byte[] b, byte[] lastOcc) {
970 int len = src.length;
971 int last = b.length - len;
972 int i = 0;
973 next:
974 while (i <= last) {
975 for (int j = (len - 1); j >= 0; j--) {
976 byte c = b[i + j];
977 if (c >= ' ' && c <= 'z') {
978 if (c >= 'a') c -= 32; // Canonicalize
979
980 if (c != src[j]) {
981 // no match
982 int goodShift = (j < len - 1) ? len : 1;
983 int badShift = lastOcc[c - 32];
984 i += Math.max(j + 1 - badShift, goodShift);
985 continue next;
986 }
987 } else {
988 // no match, character not valid for name
989 i += len;
990 continue next;
991 }
992 }
993 return true;
994 }
995 return false;
996 }
997
998 /**
999 * On first invocation, check if the JAR file has the Class-Path
1000 * and the Multi-Release attribute. A no-op on subsequent calls.
1001 */
1002 private void checkForSpecialAttributes() throws IOException {
1003 if (hasCheckedSpecialAttributes) {
1004 return;
1005 }
1006 synchronized (this) {
1007 if (hasCheckedSpecialAttributes) {
1008 return;
1009 }
1010 JarEntry manEntry = getManEntry();
1011 if (manEntry != null) {
1012 byte[] b = getBytes(manEntry);
1013 hasClassPathAttribute = match(CLASSPATH_CHARS, b,
1014 CLASSPATH_LASTOCC);
1015 // is this a multi-release jar file
1016 if (MULTI_RELEASE_ENABLED && version != BASE_VERSION) {
1017 isMultiRelease = match(MULTIRELEASE_CHARS, b,
1018 MULTIRELEASE_LASTOCC);
1019 }
1020 }
1021 hasCheckedSpecialAttributes = true;
1022 }
1023 }
1024
1025 private synchronized void ensureInitialization() {
1026 try {
1027 maybeInstantiateVerifier();
1028 } catch (IOException e) {
1029 throw new RuntimeException(e);
1030 }
1031 if (jv != null && !jvInitialized) {
1032 initializeVerifier();
1033 jvInitialized = true;
1034 }
1035 }
1036
1037 JarEntry newEntry(ZipEntry ze) {
1038 return new JarFileEntry(ze);
1039 }
1040
1041 Enumeration<String> entryNames(CodeSource[] cs) {
1042 ensureInitialization();
|