You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

ZipFile.java 20 kB

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