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