JarFile.java revision c941a854631c4bf2369adc84887bb6dd386a1bcc
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.util.jar; 19 20import java.io.File; 21import java.io.FilterInputStream; 22import java.io.IOException; 23import java.io.InputStream; 24import java.util.ArrayList; 25import java.util.Enumeration; 26import java.util.List; 27import java.util.zip.ZipEntry; 28import java.util.zip.ZipFile; 29 30import org.apache.harmony.archive.util.Util; 31import org.apache.harmony.luni.util.InputStreamHelper; 32 33/** 34 * {@code JarFile} is used to read jar entries and their associated data from 35 * jar files. 36 * 37 * @see JarInputStream 38 * @see JarEntry 39 */ 40public class JarFile extends ZipFile { 41 42 /** 43 * The MANIFEST file name. 44 */ 45 public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; 46 47 // The directory containing the manifest. 48 static final String META_DIR = "META-INF/"; 49 50 // The manifest after it has been read from the JAR. 51 private Manifest manifest; 52 53 // The entry for the MANIFEST.MF file before it is read. 54 private ZipEntry manifestEntry; 55 56 JarVerifier verifier; 57 58 private boolean closed = false; 59 60 static final class JarFileInputStream extends FilterInputStream { 61 private long count; 62 63 private ZipEntry zipEntry; 64 65 private JarVerifier.VerifierEntry entry; 66 67 private boolean done = false; 68 69 JarFileInputStream(InputStream is, ZipEntry ze, 70 JarVerifier.VerifierEntry e) { 71 super(is); 72 zipEntry = ze; 73 count = zipEntry.getSize(); 74 entry = e; 75 } 76 77 @Override 78 public int read() throws IOException { 79 if (done) { 80 return -1; 81 } 82 if (count > 0) { 83 int r = super.read(); 84 if (r != -1) { 85 entry.write(r); 86 count--; 87 } else { 88 count = 0; 89 } 90 if (count == 0) { 91 done = true; 92 entry.verify(); 93 } 94 return r; 95 } else { 96 done = true; 97 entry.verify(); 98 return -1; 99 } 100 } 101 102 @Override 103 public int read(byte[] buf, int off, int nbytes) throws IOException { 104 if (done) { 105 return -1; 106 } 107 if (count > 0) { 108 int r = super.read(buf, off, nbytes); 109 if (r != -1) { 110 int size = r; 111 if (count < size) { 112 size = (int) count; 113 } 114 entry.write(buf, off, size); 115 count -= size; 116 } else { 117 count = 0; 118 } 119 if (count == 0) { 120 done = true; 121 entry.verify(); 122 } 123 return r; 124 } else { 125 done = true; 126 entry.verify(); 127 return -1; 128 } 129 } 130 131 @Override 132 public int available() throws IOException { 133 if (done) { 134 return 0; 135 } 136 return super.available(); 137 } 138 139 @Override 140 public long skip(long nbytes) throws IOException { 141 long cnt = 0, rem = 0; 142 byte[] buf = new byte[(int)Math.min(nbytes, 2048L)]; 143 while (cnt < nbytes) { 144 int x = read(buf, 0, 145 (rem = nbytes - cnt) > buf.length ? buf.length 146 : (int) rem); 147 if (x == -1) { 148 return cnt; 149 } 150 cnt += x; 151 } 152 return cnt; 153 } 154 } 155 156 /** 157 * Create a new {@code JarFile} using the contents of the specified file. 158 * 159 * @param file 160 * the JAR file as {@link File}. 161 * @throws IOException 162 * If the file cannot be read. 163 */ 164 public JarFile(File file) throws IOException { 165 this(file, true); 166 } 167 168 /** 169 * Create a new {@code JarFile} using the contents of the specified file. 170 * 171 * @param file 172 * the JAR file as {@link File}. 173 * @param verify 174 * if this JAR file is signed whether it must be verified. 175 * @throws IOException 176 * If the file cannot be read. 177 */ 178 public JarFile(File file, boolean verify) throws IOException { 179 super(file); 180 if (verify) { 181 verifier = new JarVerifier(file.getPath()); 182 } 183 readMetaEntries(); 184 } 185 186 /** 187 * Create a new {@code JarFile} using the contents of file. 188 * 189 * @param file 190 * the JAR file as {@link File}. 191 * @param verify 192 * if this JAR filed is signed whether it must be verified. 193 * @param mode 194 * the mode to use, either {@link ZipFile#OPEN_READ OPEN_READ} or 195 * {@link ZipFile#OPEN_DELETE OPEN_DELETE}. 196 * @throws IOException 197 * If the file cannot be read. 198 */ 199 public JarFile(File file, boolean verify, int mode) throws IOException { 200 super(file, mode); 201 if (verify) { 202 verifier = new JarVerifier(file.getPath()); 203 } 204 readMetaEntries(); 205 } 206 207 /** 208 * Create a new {@code JarFile} from the contents of the file specified by 209 * filename. 210 * 211 * @param filename 212 * the file name referring to the JAR file. 213 * @throws IOException 214 * if file name cannot be opened for reading. 215 */ 216 public JarFile(String filename) throws IOException { 217 this(filename, true); 218 } 219 220 /** 221 * Create a new {@code JarFile} from the contents of the file specified by 222 * {@code filename}. 223 * 224 * @param filename 225 * the file name referring to the JAR file. 226 * @param verify 227 * if this JAR filed is signed whether it must be verified. 228 * @throws IOException 229 * If file cannot be opened or read. 230 */ 231 public JarFile(String filename, boolean verify) throws IOException { 232 super(filename); 233 if (verify) { 234 verifier = new JarVerifier(filename); 235 } 236 readMetaEntries(); 237 } 238 239 /** 240 * Return an enumeration containing the {@code JarEntrys} contained in this 241 * {@code JarFile}. 242 * 243 * @return the {@code Enumeration} containing the JAR entries. 244 * @throws IllegalStateException 245 * if this {@code JarFile} is closed. 246 */ 247 @Override 248 public Enumeration<JarEntry> entries() { 249 class JarFileEnumerator implements Enumeration<JarEntry> { 250 Enumeration<? extends ZipEntry> ze; 251 252 JarFile jf; 253 254 JarFileEnumerator(Enumeration<? extends ZipEntry> zenum, JarFile jf) { 255 ze = zenum; 256 this.jf = jf; 257 } 258 259 public boolean hasMoreElements() { 260 return ze.hasMoreElements(); 261 } 262 263 public JarEntry nextElement() { 264 JarEntry je = new JarEntry(ze.nextElement()); 265 je.parentJar = jf; 266 return je; 267 } 268 } 269 return new JarFileEnumerator(super.entries(), this); 270 } 271 272 /** 273 * Return the {@code JarEntry} specified by its name or {@code null} if no 274 * such entry exists. 275 * 276 * @param name 277 * the name of the entry in the JAR file. 278 * @return the JAR entry defined by the name. 279 */ 280 public JarEntry getJarEntry(String name) { 281 return (JarEntry) getEntry(name); 282 } 283 284 /** 285 * Returns the {@code Manifest} object associated with this {@code JarFile} 286 * or {@code null} if no MANIFEST entry exists. 287 * 288 * @return the MANIFEST. 289 * @throws IOException 290 * if an error occurs reading the MANIFEST file. 291 * @throws IllegalStateException 292 * if the jar file is closed. 293 * @see Manifest 294 */ 295 public Manifest getManifest() throws IOException { 296 if (closed) { 297 throw new IllegalStateException("JarFile has been closed"); 298 } 299 if (manifest != null) { 300 return manifest; 301 } 302 try { 303 InputStream is = super.getInputStream(manifestEntry); 304 if (verifier != null) { 305 verifier.addMetaEntry(manifestEntry.getName(), 306 InputStreamHelper.readFullyAndClose(is)); 307 is = super.getInputStream(manifestEntry); 308 } 309 try { 310 manifest = new Manifest(is, verifier != null); 311 } finally { 312 is.close(); 313 } 314 manifestEntry = null; // Can discard the entry now. 315 } catch (NullPointerException e) { 316 manifestEntry = null; 317 } 318 return manifest; 319 } 320 321 /** 322 * Called by the JarFile constructors, this method reads the contents of the 323 * file's META-INF/ directory and picks out the MANIFEST.MF file and 324 * verifier signature files if they exist. Any signature files found are 325 * registered with the verifier. 326 * 327 * @throws IOException 328 * if there is a problem reading the jar file entries. 329 */ 330 private void readMetaEntries() throws IOException { 331 // Get all meta directory entries 332 ZipEntry[] metaEntries = getMetaEntriesImpl(); 333 if (metaEntries == null) { 334 verifier = null; 335 return; 336 } 337 338 boolean signed = false; 339 340 for (ZipEntry entry : metaEntries) { 341 String entryName = entry.getName(); 342 // Is this the entry for META-INF/MANIFEST.MF ? 343 if (manifestEntry == null 344 && Util.asciiEqualsIgnoreCase(MANIFEST_NAME, entryName)) { 345 manifestEntry = entry; 346 // If there is no verifier then we don't need to look any further. 347 if (verifier == null) { 348 break; 349 } 350 } else { 351 // Is this an entry that the verifier needs? 352 if (verifier != null 353 && (Util.asciiEndsWithIgnoreCase(entryName, ".SF") 354 || Util.asciiEndsWithIgnoreCase(entryName, ".DSA") 355 || Util.asciiEndsWithIgnoreCase(entryName, ".RSA"))) { 356 signed = true; 357 InputStream is = super.getInputStream(entry); 358 byte[] buf = InputStreamHelper.readFullyAndClose(is); 359 verifier.addMetaEntry(entryName, buf); 360 } 361 } 362 } 363 364 // If there were no signature files, then no verifier work to do. 365 if (!signed) { 366 verifier = null; 367 } 368 } 369 370 /** 371 * Return an {@code InputStream} for reading the decompressed contents of 372 * ZIP entry. 373 * 374 * @param ze 375 * the ZIP entry to be read. 376 * @return the input stream to read from. 377 * @throws IOException 378 * if an error occurred while creating the input stream. 379 */ 380 @Override 381 public InputStream getInputStream(ZipEntry ze) throws IOException { 382 if (manifestEntry != null) { 383 getManifest(); 384 } 385 if (verifier != null) { 386 verifier.setManifest(getManifest()); 387 if (manifest != null) { 388 verifier.mainAttributesEnd = manifest.getMainAttributesEnd(); 389 } 390 if (verifier.readCertificates()) { 391 verifier.removeMetaEntries(); 392 if (manifest != null) { 393 manifest.removeChunks(); 394 } 395 if (!verifier.isSignedJar()) { 396 verifier = null; 397 } 398 } 399 } 400 InputStream in = super.getInputStream(ze); 401 if (in == null) { 402 return null; 403 } 404 if (verifier == null || ze.getSize() == -1) { 405 return in; 406 } 407 JarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName()); 408 if (entry == null) { 409 return in; 410 } 411 return new JarFileInputStream(in, ze, entry); 412 } 413 414 /** 415 * Return the {@code JarEntry} specified by name or {@code null} if no such 416 * entry exists. 417 * 418 * @param name 419 * the name of the entry in the JAR file. 420 * @return the ZIP entry extracted. 421 */ 422 @Override 423 public ZipEntry getEntry(String name) { 424 ZipEntry ze = super.getEntry(name); 425 if (ze == null) { 426 return ze; 427 } 428 JarEntry je = new JarEntry(ze); 429 je.parentJar = this; 430 return je; 431 } 432 433 /** 434 * Returns all the ZipEntry's that relate to files in the 435 * JAR's META-INF directory. 436 * 437 * @return the list of ZipEntry's or {@code null} if there are none. 438 */ 439 private ZipEntry[] getMetaEntriesImpl() { 440 List<ZipEntry> list = new ArrayList<ZipEntry>(8); 441 Enumeration<? extends ZipEntry> allEntries = entries(); 442 while (allEntries.hasMoreElements()) { 443 ZipEntry ze = allEntries.nextElement(); 444 if (ze.getName().startsWith(META_DIR) 445 && ze.getName().length() > META_DIR.length()) { 446 list.add(ze); 447 } 448 } 449 if (list.size() == 0) { 450 return null; 451 } 452 ZipEntry[] result = new ZipEntry[list.size()]; 453 list.toArray(result); 454 return result; 455 } 456 457 /** 458 * Closes this {@code JarFile}. 459 * 460 * @throws IOException 461 * if an error occurs. 462 */ 463 @Override 464 public void close() throws IOException { 465 super.close(); 466 closed = true; 467 } 468} 469