source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/zip/ZipFile.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 17.7 KB
Line 
1/*
2 * Copyright 2003-2005 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18package org.apache.tools.zip;
19
20import java.io.File;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.RandomAccessFile;
24import java.io.UnsupportedEncodingException;
25import java.util.Calendar;
26import java.util.Date;
27import java.util.Enumeration;
28import java.util.Hashtable;
29import java.util.zip.Inflater;
30import java.util.zip.InflaterInputStream;
31import java.util.zip.ZipException;
32
33/**
34 * Replacement for <code>java.util.ZipFile</code>.
35 *
36 * <p>This class adds support for file name encodings other than UTF-8
37 * (which is required to work on ZIP files created by native zip tools
38 * and is able to skip a preamble like the one found in self
39 * extracting archives. Furthermore it returns instances of
40 * <code>org.apache.tools.zip.ZipEntry</code> instead of
41 * <code>java.util.zip.ZipEntry</code>.</p>
42 *
43 * <p>It doesn't extend <code>java.util.zip.ZipFile</code> as it would
44 * have to reimplement all methods anyway. Like
45 * <code>java.util.ZipFile</code>, it uses RandomAccessFile under the
46 * covers and supports compressed and uncompressed entries.</p>
47 *
48 * <p>The method signatures mimic the ones of
49 * <code>java.util.zip.ZipFile</code>, with a couple of exceptions:
50 *
51 * <ul>
52 * <li>There is no getName method.</li>
53 * <li>entries has been renamed to getEntries.</li>
54 * <li>getEntries and getEntry return
55 * <code>org.apache.tools.zip.ZipEntry</code> instances.</li>
56 * <li>close is allowed to throw IOException.</li>
57 * </ul>
58 *
59 */
60public class ZipFile {
61
62 /**
63 * Maps ZipEntrys to Longs, recording the offsets of the local
64 * file headers.
65 */
66 private Hashtable entries = new Hashtable();
67
68 /**
69 * Maps String to ZipEntrys, name -> actual entry.
70 */
71 private Hashtable nameMap = new Hashtable();
72
73 /**
74 * Maps ZipEntrys to Longs, recording the offsets of the actual file data.
75 */
76 private Hashtable dataOffsets = new Hashtable();
77
78 /**
79 * The encoding to use for filenames and the file comment.
80 *
81 * <p>For a list of possible values see <a
82 * href="http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html">http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html</a>.
83 * Defaults to the platform's default character encoding.</p>
84 */
85 private String encoding = null;
86
87 /**
88 * The actual data source.
89 */
90 private RandomAccessFile archive;
91
92 /**
93 * Opens the given file for reading, assuming the platform's
94 * native encoding for file names.
95 *
96 * @param f the archive.
97 *
98 * @throws IOException if an error occurs while reading the file.
99 */
100 public ZipFile(File f) throws IOException {
101 this(f, null);
102 }
103
104 /**
105 * Opens the given file for reading, assuming the platform's
106 * native encoding for file names.
107 *
108 * @param name name of the archive.
109 *
110 * @throws IOException if an error occurs while reading the file.
111 */
112 public ZipFile(String name) throws IOException {
113 this(new File(name), null);
114 }
115
116 /**
117 * Opens the given file for reading, assuming the specified
118 * encoding for file names.
119 *
120 * @param name name of the archive.
121 * @param encoding the encoding to use for file names
122 *
123 * @throws IOException if an error occurs while reading the file.
124 */
125 public ZipFile(String name, String encoding) throws IOException {
126 this(new File(name), encoding);
127 }
128
129 /**
130 * Opens the given file for reading, assuming the specified
131 * encoding for file names.
132 *
133 * @param f the archive.
134 * @param encoding the encoding to use for file names
135 *
136 * @throws IOException if an error occurs while reading the file.
137 */
138 public ZipFile(File f, String encoding) throws IOException {
139 this.encoding = encoding;
140 archive = new RandomAccessFile(f, "r");
141 try {
142 populateFromCentralDirectory();
143 resolveLocalFileHeaderData();
144 } catch (IOException e) {
145 try {
146 archive.close();
147 } catch (IOException e2) {
148 // swallow, throw the original exception instead
149 }
150 throw e;
151 }
152 }
153
154 /**
155 * The encoding to use for filenames and the file comment.
156 *
157 * @return null if using the platform's default character encoding.
158 */
159 public String getEncoding() {
160 return encoding;
161 }
162
163 /**
164 * Closes the archive.
165 * @throws IOException if an error occurs closing the archive.
166 */
167 public void close() throws IOException {
168 archive.close();
169 }
170
171 /**
172 * Returns all entries.
173 * @return all entries as {@link ZipEntry} instances
174 */
175 public Enumeration getEntries() {
176 return entries.keys();
177 }
178
179 /**
180 * Returns a named entry - or <code>null</code> if no entry by
181 * that name exists.
182 * @param name name of the entry.
183 * @return the ZipEntry corresponding to the given name - or
184 * <code>null</code> if not present.
185 */
186 public ZipEntry getEntry(String name) {
187 return (ZipEntry) nameMap.get(name);
188 }
189
190 /**
191 * Returns an InputStream for reading the contents of the given entry.
192 * @param ze the entry to get the stream for.
193 * @return a stream to read the entry from.
194 */
195 public InputStream getInputStream(ZipEntry ze)
196 throws IOException, ZipException {
197 Long start = (Long) dataOffsets.get(ze);
198 if (start == null) {
199 return null;
200 }
201 BoundedInputStream bis =
202 new BoundedInputStream(start.longValue(), ze.getCompressedSize());
203 switch (ze.getMethod()) {
204 case ZipEntry.STORED:
205 return bis;
206 case ZipEntry.DEFLATED:
207 bis.addDummy();
208 return new InflaterInputStream(bis, new Inflater(true));
209 default:
210 throw new ZipException("Found unsupported compression method "
211 + ze.getMethod());
212 }
213 }
214
215 private static final int CFH_LEN =
216 /* version made by */ 2 +
217 /* version needed to extract */ 2 +
218 /* general purpose bit flag */ 2 +
219 /* compression method */ 2 +
220 /* last mod file time */ 2 +
221 /* last mod file date */ 2 +
222 /* crc-32 */ 4 +
223 /* compressed size */ 4 +
224 /* uncompressed size */ 4 +
225 /* filename length */ 2 +
226 /* extra field length */ 2 +
227 /* file comment length */ 2 +
228 /* disk number start */ 2 +
229 /* internal file attributes */ 2 +
230 /* external file attributes */ 4 +
231 /* relative offset of local header */ 4;
232
233 /**
234 * Reads the central directory of the given archive and populates
235 * the internal tables with ZipEntry instances.
236 *
237 * <p>The ZipEntrys will know all data that can be obtained from
238 * the central directory alone, but not the data that requires the
239 * local file header or additional data to be read.</p>
240 */
241 private void populateFromCentralDirectory()
242 throws IOException {
243 positionAtCentralDirectory();
244
245 byte[] cfh = new byte[CFH_LEN];
246
247 byte[] signatureBytes = new byte[4];
248 archive.readFully(signatureBytes);
249 ZipLong sig = new ZipLong(signatureBytes);
250 while (sig.equals(ZipOutputStream.CFH_SIG)) {
251 archive.readFully(cfh);
252 int off = 0;
253 ZipEntry ze = new ZipEntry();
254
255 ZipShort versionMadeBy = new ZipShort(cfh, off);
256 off += 2;
257 ze.setPlatform((versionMadeBy.getValue() >> 8) & 0x0F);
258
259 off += 4; // skip version info and general purpose byte
260
261 ze.setMethod((new ZipShort(cfh, off)).getValue());
262 off += 2;
263
264 ze.setTime(fromDosTime(new ZipLong(cfh, off)).getTime());
265 off += 4;
266
267 ze.setCrc((new ZipLong(cfh, off)).getValue());
268 off += 4;
269
270 ze.setCompressedSize((new ZipLong(cfh, off)).getValue());
271 off += 4;
272
273 ze.setSize((new ZipLong(cfh, off)).getValue());
274 off += 4;
275
276 int fileNameLen = (new ZipShort(cfh, off)).getValue();
277 off += 2;
278
279 int extraLen = (new ZipShort(cfh, off)).getValue();
280 off += 2;
281
282 int commentLen = (new ZipShort(cfh, off)).getValue();
283 off += 2;
284
285 off += 2; // disk number
286
287 ze.setInternalAttributes((new ZipShort(cfh, off)).getValue());
288 off += 2;
289
290 ze.setExternalAttributes((new ZipLong(cfh, off)).getValue());
291 off += 4;
292
293 // LFH offset
294 entries.put(ze, new Long((new ZipLong(cfh, off)).getValue()));
295
296 byte[] fileName = new byte[fileNameLen];
297 archive.readFully(fileName);
298 ze.setName(getString(fileName));
299
300 nameMap.put(ze.getName(), ze);
301
302 archive.skipBytes(extraLen);
303
304 byte[] comment = new byte[commentLen];
305 archive.readFully(comment);
306 ze.setComment(getString(comment));
307
308 archive.readFully(signatureBytes);
309 sig = new ZipLong(signatureBytes);
310 }
311 }
312
313 private static final int MIN_EOCD_SIZE =
314 /* end of central dir signature */ 4 +
315 /* number of this disk */ 2 +
316 /* number of the disk with the */ +
317 /* start of the central directory */ 2 +
318 /* total number of entries in */ +
319 /* the central dir on this disk */ 2 +
320 /* total number of entries in */ +
321 /* the central dir */ 2 +
322 /* size of the central directory */ 4 +
323 /* offset of start of central */ +
324 /* directory with respect to */ +
325 /* the starting disk number */ 4 +
326 /* zipfile comment length */ 2;
327
328 private static final int CFD_LOCATOR_OFFSET =
329 /* end of central dir signature */ 4 +
330 /* number of this disk */ 2 +
331 /* number of the disk with the */ +
332 /* start of the central directory */ 2 +
333 /* total number of entries in */ +
334 /* the central dir on this disk */ 2 +
335 /* total number of entries in */ +
336 /* the central dir */ 2 +
337 /* size of the central directory */ 4;
338
339 /**
340 * Searches for the &quot;End of central dir record&quot;, parses
341 * it and positions the stream at the first central directory
342 * record.
343 */
344 private void positionAtCentralDirectory()
345 throws IOException {
346 long off = archive.length() - MIN_EOCD_SIZE;
347 archive.seek(off);
348 byte[] sig = ZipOutputStream.EOCD_SIG.getBytes();
349 int curr = archive.read();
350 boolean found = false;
351 while (curr != -1) {
352 if (curr == sig[0]) {
353 curr = archive.read();
354 if (curr == sig[1]) {
355 curr = archive.read();
356 if (curr == sig[2]) {
357 curr = archive.read();
358 if (curr == sig[3]) {
359 found = true;
360 break;
361 }
362 }
363 }
364 }
365 archive.seek(--off);
366 curr = archive.read();
367 }
368 if (!found) {
369 throw new ZipException("archive is not a ZIP archive");
370 }
371 archive.seek(off + CFD_LOCATOR_OFFSET);
372 byte[] cfdOffset = new byte[4];
373 archive.readFully(cfdOffset);
374 archive.seek((new ZipLong(cfdOffset)).getValue());
375 }
376
377 /**
378 * Number of bytes in local file header up to the &quot;length of
379 * filename&quot; entry.
380 */
381 private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
382 /* local file header signature */ 4 +
383 /* version needed to extract */ 2 +
384 /* general purpose bit flag */ 2 +
385 /* compression method */ 2 +
386 /* last mod file time */ 2 +
387 /* last mod file date */ 2 +
388 /* crc-32 */ 4 +
389 /* compressed size */ 4 +
390 /* uncompressed size */ 4;
391
392 /**
393 * Walks through all recorded entries and adds the data available
394 * from the local file header.
395 *
396 * <p>Also records the offsets for the data to read from the
397 * entries.</p>
398 */
399 private void resolveLocalFileHeaderData()
400 throws IOException {
401 Enumeration e = getEntries();
402 while (e.hasMoreElements()) {
403 ZipEntry ze = (ZipEntry) e.nextElement();
404 long offset = ((Long) entries.get(ze)).longValue();
405 archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
406 byte[] b = new byte[2];
407 archive.readFully(b);
408 int fileNameLen = (new ZipShort(b)).getValue();
409 archive.readFully(b);
410 int extraFieldLen = (new ZipShort(b)).getValue();
411 archive.skipBytes(fileNameLen);
412 byte[] localExtraData = new byte[extraFieldLen];
413 archive.readFully(localExtraData);
414 ze.setExtra(localExtraData);
415 dataOffsets.put(ze,
416 new Long(offset + LFH_OFFSET_FOR_FILENAME_LENGTH
417 + 2 + 2 + fileNameLen + extraFieldLen));
418 }
419 }
420
421 /**
422 * Convert a DOS date/time field to a Date object.
423 *
424 * @param l contains the stored DOS time.
425 * @return a Date instance corresponding to the given time.
426 */
427 protected static Date fromDosTime(ZipLong l) {
428 long dosTime = l.getValue();
429 Calendar cal = Calendar.getInstance();
430 cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
431 cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
432 cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
433 cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
434 cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
435 cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
436 return cal.getTime();
437 }
438
439 /**
440 * Retrieve a String from the given bytes using the encoding set
441 * for this ZipFile.
442 *
443 * @param bytes the byte array to transform
444 * @return String obtained by using the given encoding
445 * @throws ZipException if the encoding cannot be recognized.
446 */
447 protected String getString(byte[] bytes) throws ZipException {
448 if (encoding == null) {
449 return new String(bytes);
450 } else {
451 try {
452 return new String(bytes, encoding);
453 } catch (UnsupportedEncodingException uee) {
454 throw new ZipException(uee.getMessage());
455 }
456 }
457 }
458
459 /**
460 * InputStream that delegates requests to the underlying
461 * RandomAccessFile, making sure that only bytes from a certain
462 * range can be read.
463 */
464 private class BoundedInputStream extends InputStream {
465 private long remaining;
466 private long loc;
467 private boolean addDummyByte = false;
468
469 BoundedInputStream(long start, long remaining) {
470 this.remaining = remaining;
471 loc = start;
472 }
473
474 public int read() throws IOException {
475 if (remaining-- <= 0) {
476 if (addDummyByte) {
477 addDummyByte = false;
478 return 0;
479 }
480 return -1;
481 }
482 synchronized (archive) {
483 archive.seek(loc++);
484 return archive.read();
485 }
486 }
487
488 public int read(byte[] b, int off, int len) throws IOException {
489 if (remaining <= 0) {
490 if (addDummyByte) {
491 addDummyByte = false;
492 b[off] = 0;
493 return 1;
494 }
495 return -1;
496 }
497
498 if (len <= 0) {
499 return 0;
500 }
501
502 if (len > remaining) {
503 len = (int) remaining;
504 }
505 int ret = -1;
506 synchronized (archive) {
507 archive.seek(loc);
508 ret = archive.read(b, off, len);
509 }
510 if (ret > 0) {
511 loc += ret;
512 remaining -= ret;
513 }
514 return ret;
515 }
516
517 /**
518 * Inflater needs an extra dummy byte for nowrap - see
519 * Inflater's javadocs.
520 */
521 void addDummy() {
522 addDummyByte = true;
523 }
524 }
525
526}
Note: See TracBrowser for help on using the repository browser.