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