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