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