< prev index next >

src/java.base/share/classes/java/util/jar/JarFile.java

Print this page
rev 13960 : 8152733: Avoid creating Manifest when checking for Multi-Release attribute
Reviewed-by: psandoz, alanb, dchuyko
Contributed-by: claes.redestad@oracle.com, steve.drach@oracle.com


  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();


< prev index next >