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