1. /*
  2. * @(#)JarFile.java 1.50 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.util.jar;
  8. import java.io.*;
  9. import java.util.*;
  10. import java.util.zip.*;
  11. import java.security.cert.Certificate;
  12. import java.security.AccessController;
  13. import sun.security.action.GetPropertyAction;
  14. import sun.security.util.ManifestEntryVerifier;
  15. import sun.misc.SharedSecrets;
  16. /**
  17. * The <code>JarFile</code> class is used to read the contents of a JAR file
  18. * from any file that can be opened with <code>java.io.RandomAccessFile</code>.
  19. * It extends the class <code>java.util.zip.ZipFile</code> with support
  20. * for reading an optional <code>Manifest</code> entry. The
  21. * <code>Manifest</code> can be used to specify meta-information about the
  22. * JAR file and its entries.
  23. *
  24. * @author David Connelly
  25. * @version 1.50, 01/23/03
  26. * @see Manifest
  27. * @see java.util.zip.ZipFile
  28. * @see java.util.jar.JarEntry
  29. * @since 1.2
  30. */
  31. public
  32. class JarFile extends ZipFile {
  33. private Manifest man;
  34. private JarEntry manEntry;
  35. private boolean manLoaded;
  36. private JarVerifier jv;
  37. private boolean jvInitialized;
  38. private boolean verify;
  39. private boolean computedHasClassPathAttribute;
  40. private boolean hasClassPathAttribute;
  41. // Set up JavaUtilJarAccess in SharedSecrets
  42. static {
  43. SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
  44. }
  45. /**
  46. * The JAR manifest file name.
  47. */
  48. public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
  49. /**
  50. * Creates a new <code>JarFile</code> to read from the specified
  51. * file <code>name</code>. The <code>JarFile</code> will be verified if
  52. * it is signed.
  53. * @param name the name of the JAR file to be opened for reading
  54. * @exception IOException if an I/O error has occurred
  55. * @exception SecurityException if access to the file is denied
  56. * by the SecurityManager
  57. */
  58. public JarFile(String name) throws IOException {
  59. this(new File(name), true, ZipFile.OPEN_READ);
  60. }
  61. /**
  62. * Creates a new <code>JarFile</code> to read from the specified
  63. * file <code>name</code>.
  64. * @param name the name of the JAR file to be opened for reading
  65. * @param verify whether or not to verify the JarFile if
  66. * it is signed.
  67. * @exception IOException if an I/O error has occurred
  68. * @exception SecurityException if access to the file is denied
  69. * by the SecurityManager
  70. */
  71. public JarFile(String name, boolean verify) throws IOException {
  72. this(new File(name), verify, ZipFile.OPEN_READ);
  73. }
  74. /**
  75. * Creates a new <code>JarFile</code> to read from the specified
  76. * <code>File</code> object. The <code>JarFile</code> will be verified if
  77. * it is signed.
  78. * @param file the JAR file to be opened for reading
  79. * @exception IOException if an I/O error has occurred
  80. * @exception SecurityException if access to the file is denied
  81. * by the SecurityManager
  82. */
  83. public JarFile(File file) throws IOException {
  84. this(file, true, ZipFile.OPEN_READ);
  85. }
  86. /**
  87. * Creates a new <code>JarFile</code> to read from the specified
  88. * <code>File</code> object.
  89. * @param file the JAR file to be opened for reading
  90. * @param verify whether or not to verify the JarFile if
  91. * it is signed.
  92. * @exception IOException if an I/O error has occurred
  93. * @exception SecurityException if access to the file is denied
  94. * by the SecurityManager.
  95. */
  96. public JarFile(File file, boolean verify) throws IOException {
  97. this(file, verify, ZipFile.OPEN_READ);
  98. }
  99. /**
  100. * Creates a new <code>JarFile</code> to read from the specified
  101. * <code>File</code> object in the specified mode. The mode argument
  102. * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
  103. *
  104. * @param file the JAR file to be opened for reading
  105. * @param verify whether or not to verify the JarFile if
  106. * it is signed.
  107. * @param mode the mode in which the file is to be opened
  108. * @exception IOException if an I/O error has occurred
  109. * @exception IllegalArgumentException
  110. * If the <tt>mode</tt> argument is invalid
  111. * @exception SecurityException if access to the file is denied
  112. * by the SecurityManager
  113. */
  114. public JarFile(File file, boolean verify, int mode) throws IOException {
  115. super(file, mode);
  116. this.verify = verify;
  117. }
  118. /**
  119. * Returns the JAR file manifest, or <code>null</code> if none.
  120. *
  121. * @return the JAR file manifest, or <code>null</code> if none
  122. */
  123. public Manifest getManifest() throws IOException {
  124. if (!manLoaded) {
  125. // First look up manifest entry using standard name
  126. manEntry = getJarEntry(MANIFEST_NAME);
  127. if (manEntry == null) {
  128. // If not found, then iterate through all the "META-INF/"
  129. // entries to find a match.
  130. String[] names = getMetaInfEntryNames();
  131. if (names != null) {
  132. for (int i = 0; i < names.length; i++) {
  133. if (MANIFEST_NAME.equals(
  134. names[i].toUpperCase(Locale.ENGLISH))) {
  135. manEntry = getJarEntry(names[i]);
  136. break;
  137. }
  138. }
  139. }
  140. }
  141. // If found then load the manifest
  142. if (manEntry != null) {
  143. if (verify) {
  144. byte[] b = getBytes(manEntry);
  145. man = new Manifest(new ByteArrayInputStream(b));
  146. jv = new JarVerifier(man, b);
  147. } else {
  148. man = new Manifest(super.getInputStream(manEntry));
  149. }
  150. }
  151. manLoaded = true;
  152. }
  153. return man;
  154. }
  155. private native String[] getMetaInfEntryNames();
  156. /**
  157. * Returns the <code>JarEntry</code> for the given entry name or
  158. * <code>null</code> if not found.
  159. *
  160. * @param name the JAR file entry name
  161. * @return the <code>JarEntry</code> for the given entry name or
  162. * <code>null</code> if not found.
  163. * @see java.util.jar.JarEntry
  164. */
  165. public JarEntry getJarEntry(String name) {
  166. return (JarEntry)getEntry(name);
  167. }
  168. /**
  169. * Returns the <code>ZipEntry</code> for the given entry name or
  170. * <code>null</code> if not found.
  171. *
  172. * @param name the JAR file entry name
  173. * @return the <code>ZipEntry</code> for the given entry name or
  174. * <code>null</code> if not found
  175. * @see java.util.zip.ZipEntry
  176. */
  177. public ZipEntry getEntry(String name) {
  178. ZipEntry ze = super.getEntry(name);
  179. if (ze != null) {
  180. return new JarFileEntry(ze);
  181. }
  182. return null;
  183. }
  184. /**
  185. * Returns an enumeration of the ZIP file entries.
  186. */
  187. public Enumeration entries() {
  188. final Enumeration enum = super.entries();
  189. return new Enumeration() {
  190. public boolean hasMoreElements() {
  191. return enum.hasMoreElements();
  192. }
  193. public Object nextElement() {
  194. ZipEntry ze = (ZipEntry)enum.nextElement();
  195. return new JarFileEntry(ze);
  196. }
  197. };
  198. }
  199. private class JarFileEntry extends JarEntry {
  200. JarFileEntry(ZipEntry ze) {
  201. super(ze);
  202. }
  203. public Attributes getAttributes() throws IOException {
  204. Manifest man = JarFile.this.getManifest();
  205. if (man != null) {
  206. return man.getAttributes(getName());
  207. } else {
  208. return null;
  209. }
  210. }
  211. public java.security.cert.Certificate[] getCertificates() {
  212. try {
  213. maybeInstantiateVerifier();
  214. } catch (IOException e) {
  215. throw new RuntimeException(e);
  216. }
  217. if (certs == null && jv != null) {
  218. Certificate[] cs = jv.getCerts(getName());
  219. if (cs != null) {
  220. certs = (Certificate[])cs.clone();
  221. }
  222. }
  223. return certs;
  224. }
  225. }
  226. /*
  227. * Ensures that the JarVerifier has been created if one is
  228. * necessary (i.e., the jar appears to be signed.) This is done as
  229. * a quick check to avoid processing of the manifest for unsigned
  230. * jars.
  231. */
  232. private void maybeInstantiateVerifier() throws IOException {
  233. if (jv != null) {
  234. return;
  235. }
  236. if (verify) {
  237. String[] names = getMetaInfEntryNames();
  238. if (names != null) {
  239. for (int i = 0; i < names.length; i++) {
  240. String name = names[i].toUpperCase(Locale.ENGLISH);
  241. if (name.endsWith(".DSA") ||
  242. name.endsWith(".RSA") ||
  243. name.endsWith(".SF")) {
  244. // Assume since we found a signature-related file
  245. // that the jar is signed and that we therefore
  246. // need a JarVerifier and Manifest
  247. getManifest();
  248. return;
  249. }
  250. }
  251. }
  252. // No signature-related files; don't instantiate a
  253. // verifier
  254. verify = false;
  255. }
  256. }
  257. /*
  258. * Initializes the verifier object by reading all the manifest
  259. * entries and passing them to the verifier.
  260. */
  261. private void initializeVerifier() {
  262. ManifestEntryVerifier mev = null;
  263. // Verify "META-INF/" entries...
  264. try {
  265. String[] names = getMetaInfEntryNames();
  266. if (names != null) {
  267. for (int i = 0; i < names.length; i++) {
  268. JarEntry e = getJarEntry(names[i]);
  269. if (!e.isDirectory()) {
  270. if (mev == null) {
  271. mev = new ManifestEntryVerifier(man);
  272. }
  273. byte[] b = getBytes(e);
  274. if (b != null && b.length > 0) {
  275. jv.beginEntry(e, mev);
  276. jv.update(b.length, b, 0, b.length, mev);
  277. jv.update(-1, null, 0, 0, mev);
  278. }
  279. }
  280. }
  281. }
  282. } catch (IOException ex) {
  283. // if we had an error parsing any blocks, just
  284. // treat the jar file as being unsigned
  285. jv = null;
  286. verify = false;
  287. }
  288. // if after initializing the verifier we have nothing
  289. // signed, we null it out.
  290. if (jv != null) {
  291. jv.doneWithMeta();
  292. if (JarVerifier.debug != null) {
  293. JarVerifier.debug.println("done with meta!");
  294. }
  295. if (jv.nothingToVerify()) {
  296. if (JarVerifier.debug != null) {
  297. JarVerifier.debug.println("nothing to verify!");
  298. }
  299. jv = null;
  300. verify = false;
  301. }
  302. }
  303. }
  304. /*
  305. * Reads all the bytes for a given entry. Used to process the
  306. * the META-INF files.
  307. */
  308. private byte[] getBytes(ZipEntry ze) throws IOException {
  309. byte[] b = new byte[(int)ze.getSize()];
  310. DataInputStream is = new DataInputStream(super.getInputStream(ze));
  311. is.readFully(b, 0, b.length);
  312. is.close();
  313. return b;
  314. }
  315. /**
  316. * Returns an input stream for reading the contents of the specified
  317. * ZIP file entry.
  318. * @param ze the zip file entry
  319. * @return an input stream for reading the contents of the specified
  320. * ZIP file entry
  321. * @exception ZipException if a ZIP format error has occurred
  322. * @exception IOException if an I/O error has occurred
  323. * @exception SecurityException if any of the JarFile entries are incorrectly signed.
  324. */
  325. public synchronized InputStream getInputStream(ZipEntry ze)
  326. throws IOException
  327. {
  328. maybeInstantiateVerifier();
  329. if (jv == null) {
  330. return super.getInputStream(ze);
  331. }
  332. if (!jvInitialized) {
  333. initializeVerifier();
  334. jvInitialized = true;
  335. // could be set to null after a call to
  336. // initializeVerifier if we have nothing to
  337. // verify
  338. if (jv == null)
  339. return super.getInputStream(ze);
  340. }
  341. // wrap a verifier stream around the real stream
  342. return new JarVerifier.VerifierStream(man, (JarEntry)ze,
  343. super.getInputStream(ze), jv);
  344. }
  345. // Statics for hand-coded Boyer-Moore search in hasClassPathAttribute()
  346. // The bad character shift for "class-path"
  347. private static int[] lastOcc;
  348. // The good suffix shift for "class-path"
  349. private static int[] optoSft;
  350. // Initialize the shift arrays to search for "class-path"
  351. private static char[] src = {'c','l','a','s','s','-','p','a','t','h'};
  352. static {
  353. lastOcc = new int[128];
  354. optoSft = new int[10];
  355. lastOcc[99]=1; lastOcc[108]=2; lastOcc[97]=8; lastOcc[115]=5;
  356. lastOcc[116]=9; lastOcc[45]=6; lastOcc[112]=7; lastOcc[104]=10;
  357. for (int i=0; i<9; i++)
  358. optoSft[i]=10;
  359. optoSft[9]=1;
  360. }
  361. // Returns true iff this jar file has a manifest with a class path
  362. // attribute. Returns false if there is no manifest or the manifest
  363. // does not contain a "Class-Path" attribute. Currently exported to
  364. // core libraries via sun.misc.SharedSecrets.
  365. boolean hasClassPathAttribute() throws IOException {
  366. if (computedHasClassPathAttribute) {
  367. return hasClassPathAttribute;
  368. }
  369. hasClassPathAttribute = false;
  370. if (!isKnownToNotHaveClassPathAttribute()) {
  371. JarEntry manEntry = getJarEntry(MANIFEST_NAME);
  372. if (manEntry == null) {
  373. // If not found, then iterate through all the "META-INF/"
  374. // entries to find a match.
  375. String[] names = getMetaInfEntryNames();
  376. if (names != null) {
  377. for (int i = 0; i < names.length; i++) {
  378. if (MANIFEST_NAME.equals(
  379. names[i].toUpperCase(Locale.ENGLISH))) {
  380. manEntry = getJarEntry(names[i]);
  381. break;
  382. }
  383. }
  384. }
  385. }
  386. if (manEntry != null) {
  387. byte[] b = new byte[(int)manEntry.getSize()];
  388. DataInputStream dis = new DataInputStream(
  389. super.getInputStream(manEntry));
  390. dis.readFully(b, 0, b.length);
  391. dis.close();
  392. int last = b.length - src.length;
  393. int i = 0;
  394. next:
  395. while (i<=last) {
  396. for (int j=9; j>=0; j--) {
  397. char c = (char) b[i+j];
  398. c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c;
  399. if (c != src[j]) {
  400. i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]);
  401. continue next;
  402. }
  403. }
  404. hasClassPathAttribute = true;
  405. break;
  406. }
  407. }
  408. }
  409. computedHasClassPathAttribute = true;
  410. return hasClassPathAttribute;
  411. }
  412. private static String javaHome;
  413. private boolean isKnownToNotHaveClassPathAttribute() {
  414. // Optimize away even scanning of manifest for jar files we
  415. // deliver which don't have a class-path attribute. If one of
  416. // these jars is changed to include such an attribute this code
  417. // must be changed.
  418. if (javaHome == null) {
  419. javaHome = (String) AccessController.doPrivileged(
  420. new GetPropertyAction("java.home"));
  421. }
  422. String name = getName();
  423. String localJavaHome = javaHome;
  424. if (name.startsWith(localJavaHome)) {
  425. if (name.endsWith("rt.jar") ||
  426. name.endsWith("sunrsasign.jar") ||
  427. name.endsWith("jsse.jar") ||
  428. name.endsWith("jce.jar") ||
  429. name.endsWith("charsets.jar") ||
  430. name.endsWith("dnsns.jar") ||
  431. name.endsWith("ldapsec.jar") ||
  432. name.endsWith("localedata.jar") ||
  433. name.endsWith("sunjce_provider.jar")) {
  434. return true;
  435. }
  436. }
  437. return false;
  438. }
  439. }