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