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