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 38 kB

11 years ago
11 years ago
11 years ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048
  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 static org.apache.tools.zip.ZipConstants.DWORD;
  20. import static org.apache.tools.zip.ZipConstants.SHORT;
  21. import static org.apache.tools.zip.ZipConstants.WORD;
  22. import static org.apache.tools.zip.ZipConstants.ZIP64_MAGIC;
  23. import static org.apache.tools.zip.ZipConstants.ZIP64_MAGIC_SHORT;
  24. import java.io.EOFException;
  25. import java.io.File;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.io.RandomAccessFile;
  29. import java.util.Arrays;
  30. import java.util.Collections;
  31. import java.util.Comparator;
  32. import java.util.Enumeration;
  33. import java.util.HashMap;
  34. import java.util.Iterator;
  35. import java.util.LinkedList;
  36. import java.util.List;
  37. import java.util.Map;
  38. import java.util.zip.Inflater;
  39. import java.util.zip.InflaterInputStream;
  40. import java.util.zip.ZipException;
  41. /**
  42. * Replacement for <code>java.util.ZipFile</code>.
  43. *
  44. * <p>This class adds support for file name encodings other than UTF-8
  45. * (which is required to work on ZIP files created by native zip tools
  46. * and is able to skip a preamble like the one found in self
  47. * extracting archives. Furthermore it returns instances of
  48. * <code>org.apache.tools.zip.ZipEntry</code> instead of
  49. * <code>java.util.zip.ZipEntry</code>.</p>
  50. *
  51. * <p>It doesn't extend <code>java.util.zip.ZipFile</code> as it would
  52. * have to reimplement all methods anyway. Like
  53. * <code>java.util.ZipFile</code>, it uses RandomAccessFile under the
  54. * covers and supports compressed and uncompressed entries. As of
  55. * Apache Ant 1.9.0 it also transparently supports Zip64
  56. * extensions and thus individual entries and archives larger than 4
  57. * GB or with more than 65536 entries.</p>
  58. *
  59. * <p>The method signatures mimic the ones of
  60. * <code>java.util.zip.ZipFile</code>, with a couple of exceptions:
  61. *
  62. * <ul>
  63. * <li>There is no getName method.</li>
  64. * <li>entries has been renamed to getEntries.</li>
  65. * <li>getEntries and getEntry return
  66. * <code>org.apache.tools.zip.ZipEntry</code> instances.</li>
  67. * <li>close is allowed to throw IOException.</li>
  68. * </ul>
  69. *
  70. */
  71. public class ZipFile {
  72. private static final int HASH_SIZE = 509;
  73. static final int NIBLET_MASK = 0x0f;
  74. static final int BYTE_SHIFT = 8;
  75. private static final int POS_0 = 0;
  76. private static final int POS_1 = 1;
  77. private static final int POS_2 = 2;
  78. private static final int POS_3 = 3;
  79. /**
  80. * List of entries in the order they appear inside the central
  81. * directory.
  82. */
  83. private final List<ZipEntry> entries = new LinkedList<ZipEntry>();
  84. /**
  85. * Maps String to list of ZipEntrys, name -> actual entries.
  86. */
  87. private final Map<String, LinkedList<ZipEntry>> nameMap =
  88. new HashMap<String, LinkedList<ZipEntry>>(HASH_SIZE);
  89. private static final class OffsetEntry {
  90. private long headerOffset = -1;
  91. private long dataOffset = -1;
  92. }
  93. /**
  94. * The encoding to use for filenames and the file comment.
  95. *
  96. * <p>For a list of possible values see <a
  97. * 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>.
  98. * Defaults to the platform's default character encoding.</p>
  99. */
  100. private final String encoding;
  101. /**
  102. * The zip encoding to use for filenames and the file comment.
  103. */
  104. private final ZipEncoding zipEncoding;
  105. /**
  106. * File name of actual source.
  107. */
  108. private final String archiveName;
  109. /**
  110. * The actual data source.
  111. */
  112. private final RandomAccessFile archive;
  113. /**
  114. * Whether to look for and use Unicode extra fields.
  115. */
  116. private final boolean useUnicodeExtraFields;
  117. /**
  118. * Whether the file is closed.
  119. */
  120. private volatile boolean closed;
  121. // cached buffers
  122. private final byte[] DWORD_BUF = new byte[DWORD];
  123. private final byte[] WORD_BUF = new byte[WORD];
  124. private final byte[] CFH_BUF = new byte[CFH_LEN];
  125. private final byte[] SHORT_BUF = new byte[SHORT];
  126. /**
  127. * Opens the given file for reading, assuming the platform's
  128. * native encoding for file names.
  129. *
  130. * @param f the archive.
  131. *
  132. * @throws IOException if an error occurs while reading the file.
  133. */
  134. public ZipFile(final File f) throws IOException {
  135. this(f, null);
  136. }
  137. /**
  138. * Opens the given file for reading, assuming the platform's
  139. * native encoding for file names.
  140. *
  141. * @param name name of the archive.
  142. *
  143. * @throws IOException if an error occurs while reading the file.
  144. */
  145. public ZipFile(final String name) throws IOException {
  146. this(new File(name), null);
  147. }
  148. /**
  149. * Opens the given file for reading, assuming the specified
  150. * encoding for file names, scanning unicode extra fields.
  151. *
  152. * @param name name of the archive.
  153. * @param encoding the encoding to use for file names, use null
  154. * for the platform's default encoding
  155. *
  156. * @throws IOException if an error occurs while reading the file.
  157. */
  158. public ZipFile(final String name, final String encoding) throws IOException {
  159. this(new File(name), encoding, true);
  160. }
  161. /**
  162. * Opens the given file for reading, assuming the specified
  163. * encoding for file names and scanning for unicode extra fields.
  164. *
  165. * @param f the archive.
  166. * @param encoding the encoding to use for file names, use null
  167. * for the platform's default encoding
  168. *
  169. * @throws IOException if an error occurs while reading the file.
  170. */
  171. public ZipFile(final File f, final String encoding) throws IOException {
  172. this(f, encoding, true);
  173. }
  174. /**
  175. * Opens the given file for reading, assuming the specified
  176. * encoding for file names.
  177. *
  178. * @param f the archive.
  179. * @param encoding the encoding to use for file names, use null
  180. * for the platform's default encoding
  181. * @param useUnicodeExtraFields whether to use InfoZIP Unicode
  182. * Extra Fields (if present) to set the file names.
  183. *
  184. * @throws IOException if an error occurs while reading the file.
  185. */
  186. public ZipFile(final File f, final String encoding, final boolean useUnicodeExtraFields)
  187. throws IOException {
  188. this.archiveName = f.getAbsolutePath();
  189. this.encoding = encoding;
  190. this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
  191. this.useUnicodeExtraFields = useUnicodeExtraFields;
  192. archive = new RandomAccessFile(f, "r");
  193. boolean success = false;
  194. try {
  195. final Map<ZipEntry, NameAndComment> entriesWithoutUTF8Flag =
  196. populateFromCentralDirectory();
  197. resolveLocalFileHeaderData(entriesWithoutUTF8Flag);
  198. success = true;
  199. } finally {
  200. closed = !success;
  201. if (!success) {
  202. try {
  203. archive.close();
  204. } catch (final IOException e2) {
  205. // swallow, throw the original exception instead
  206. }
  207. }
  208. }
  209. }
  210. /**
  211. * The encoding to use for filenames and the file comment.
  212. *
  213. * @return null if using the platform's default character encoding.
  214. */
  215. public String getEncoding() {
  216. return encoding;
  217. }
  218. /**
  219. * Closes the archive.
  220. * @throws IOException if an error occurs closing the archive.
  221. */
  222. public void close() throws IOException {
  223. // this flag is only written here and read in finalize() which
  224. // can never be run in parallel.
  225. // no synchronization needed.
  226. closed = true;
  227. archive.close();
  228. }
  229. /**
  230. * close a zipfile quietly; throw no io fault, do nothing
  231. * on a null parameter
  232. * @param zipfile file to close, can be null
  233. */
  234. public static void closeQuietly(final ZipFile zipfile) {
  235. if (zipfile != null) {
  236. try {
  237. zipfile.close();
  238. } catch (final IOException e) {
  239. //ignore
  240. }
  241. }
  242. }
  243. /**
  244. * Returns all entries.
  245. *
  246. * <p>Entries will be returned in the same order they appear
  247. * within the archive's central directory.</p>
  248. *
  249. * @return all entries as {@link ZipEntry} instances
  250. */
  251. public Enumeration<ZipEntry> getEntries() {
  252. return Collections.enumeration(entries);
  253. }
  254. /**
  255. * Returns all entries in physical order.
  256. *
  257. * <p>Entries will be returned in the same order their contents
  258. * appear within the archive.</p>
  259. *
  260. * @return all entries as {@link ZipEntry} instances
  261. *
  262. * @since Ant 1.9.0
  263. */
  264. public Enumeration<ZipEntry> getEntriesInPhysicalOrder() {
  265. final ZipEntry[] allEntries = entries.toArray(new ZipEntry[0]);
  266. Arrays.sort(allEntries, OFFSET_COMPARATOR);
  267. return Collections.enumeration(Arrays.asList(allEntries));
  268. }
  269. /**
  270. * Returns a named entry - or {@code null} if no entry by
  271. * that name exists.
  272. *
  273. * <p>If multiple entries with the same name exist the first entry
  274. * in the archive's central directory by that name is
  275. * returned.</p>
  276. *
  277. * @param name name of the entry.
  278. * @return the ZipEntry corresponding to the given name - or
  279. * {@code null} if not present.
  280. */
  281. public ZipEntry getEntry(final String name) {
  282. final LinkedList<ZipEntry> entriesOfThatName = nameMap.get(name);
  283. return entriesOfThatName != null ? entriesOfThatName.getFirst() : null;
  284. }
  285. /**
  286. * Returns all named entries in the same order they appear within
  287. * the archive's central directory.
  288. *
  289. * @param name name of the entry.
  290. * @return the Iterable<ZipEntry> corresponding to the
  291. * given name
  292. * @since 1.9.2
  293. */
  294. public Iterable<ZipEntry> getEntries(final String name) {
  295. final List<ZipEntry> entriesOfThatName = nameMap.get(name);
  296. return entriesOfThatName != null ? entriesOfThatName
  297. : Collections.<ZipEntry>emptyList();
  298. }
  299. /**
  300. * Returns all named entries in the same order their contents
  301. * appear within the archive.
  302. *
  303. * @param name name of the entry.
  304. * @return the Iterable<ZipEntry> corresponding to the
  305. * given name
  306. * @since 1.9.2
  307. */
  308. public Iterable<ZipEntry> getEntriesInPhysicalOrder(final String name) {
  309. ZipEntry[] entriesOfThatName = new ZipEntry[0];
  310. if (nameMap.containsKey(name)) {
  311. entriesOfThatName = nameMap.get(name).toArray(entriesOfThatName);
  312. Arrays.sort(entriesOfThatName, OFFSET_COMPARATOR);
  313. }
  314. return Arrays.asList(entriesOfThatName);
  315. }
  316. /**
  317. * Whether this class is able to read the given entry.
  318. *
  319. * <p>May return false if it is set up to use encryption or a
  320. * compression method that hasn't been implemented yet.</p>
  321. */
  322. public boolean canReadEntryData(final ZipEntry ze) {
  323. return ZipUtil.canHandleEntryData(ze);
  324. }
  325. /**
  326. * Returns an InputStream for reading the contents of the given entry.
  327. *
  328. * @param ze the entry to get the stream for.
  329. * @return a stream to read the entry from.
  330. * @throws IOException if unable to create an input stream from the zipentry
  331. * @throws ZipException if the zipentry uses an unsupported feature
  332. */
  333. public InputStream getInputStream(final ZipEntry ze)
  334. throws IOException, ZipException {
  335. if (!(ze instanceof Entry)) {
  336. return null;
  337. }
  338. // cast valididty is checked just above
  339. final OffsetEntry offsetEntry = ((Entry) ze).getOffsetEntry();
  340. ZipUtil.checkRequestedFeatures(ze);
  341. final long start = offsetEntry.dataOffset;
  342. final BoundedInputStream bis =
  343. new BoundedInputStream(start, ze.getCompressedSize());
  344. switch (ze.getMethod()) {
  345. case ZipEntry.STORED:
  346. return bis;
  347. case ZipEntry.DEFLATED:
  348. bis.addDummy();
  349. final Inflater inflater = new Inflater(true);
  350. return new InflaterInputStream(bis, inflater) {
  351. @Override
  352. public void close() throws IOException {
  353. super.close();
  354. inflater.end();
  355. }
  356. };
  357. default:
  358. throw new ZipException("Found unsupported compression method "
  359. + ze.getMethod());
  360. }
  361. }
  362. /**
  363. * Ensures that the close method of this zipfile is called when
  364. * there are no more references to it.
  365. * @see #close()
  366. */
  367. @Override
  368. protected void finalize() throws Throwable {
  369. try {
  370. if (!closed) {
  371. System.err.println("Cleaning up unclosed ZipFile for archive "
  372. + archiveName);
  373. close();
  374. }
  375. } finally {
  376. super.finalize();
  377. }
  378. }
  379. /**
  380. * Length of a "central directory" entry structure without file
  381. * name, extra fields or comment.
  382. */
  383. private static final int CFH_LEN =
  384. /* version made by */ SHORT
  385. /* version needed to extract */ + SHORT
  386. /* general purpose bit flag */ + SHORT
  387. /* compression method */ + SHORT
  388. /* last mod file time */ + SHORT
  389. /* last mod file date */ + SHORT
  390. /* crc-32 */ + WORD
  391. /* compressed size */ + WORD
  392. /* uncompressed size */ + WORD
  393. /* filename length */ + SHORT
  394. /* extra field length */ + SHORT
  395. /* file comment length */ + SHORT
  396. /* disk number start */ + SHORT
  397. /* internal file attributes */ + SHORT
  398. /* external file attributes */ + WORD
  399. /* relative offset of local header */ + WORD;
  400. private static final long CFH_SIG =
  401. ZipLong.getValue(ZipOutputStream.CFH_SIG);
  402. /**
  403. * Reads the central directory of the given archive and populates
  404. * the internal tables with ZipEntry instances.
  405. *
  406. * <p>The ZipEntrys will know all data that can be obtained from
  407. * the central directory alone, but not the data that requires the
  408. * local file header or additional data to be read.</p>
  409. *
  410. * @return a map of zipentries that didn't have the language
  411. * encoding flag set when read.
  412. */
  413. private Map<ZipEntry, NameAndComment> populateFromCentralDirectory()
  414. throws IOException {
  415. final HashMap<ZipEntry, NameAndComment> noUTF8Flag =
  416. new HashMap<ZipEntry, NameAndComment>();
  417. positionAtCentralDirectory();
  418. archive.readFully(WORD_BUF);
  419. long sig = ZipLong.getValue(WORD_BUF);
  420. if (sig != CFH_SIG && startsWithLocalFileHeader()) {
  421. throw new IOException("central directory is empty, can't expand"
  422. + " corrupt archive.");
  423. }
  424. while (sig == CFH_SIG) {
  425. readCentralDirectoryEntry(noUTF8Flag);
  426. archive.readFully(WORD_BUF);
  427. sig = ZipLong.getValue(WORD_BUF);
  428. }
  429. return noUTF8Flag;
  430. }
  431. /**
  432. * Reads an individual entry of the central directory, creats an
  433. * ZipEntry from it and adds it to the global maps.
  434. *
  435. * @param noUTF8Flag map used to collect entries that don't have
  436. * their UTF-8 flag set and whose name will be set by data read
  437. * from the local file header later. The current entry may be
  438. * added to this map.
  439. */
  440. private void
  441. readCentralDirectoryEntry(final Map<ZipEntry, NameAndComment> noUTF8Flag)
  442. throws IOException {
  443. archive.readFully(CFH_BUF);
  444. int off = 0;
  445. final OffsetEntry offset = new OffsetEntry();
  446. final Entry ze = new Entry(offset);
  447. final int versionMadeBy = ZipShort.getValue(CFH_BUF, off);
  448. off += SHORT;
  449. ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK);
  450. off += SHORT; // skip version info
  451. final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(CFH_BUF, off);
  452. final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
  453. final ZipEncoding entryEncoding =
  454. hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
  455. ze.setGeneralPurposeBit(gpFlag);
  456. off += SHORT;
  457. ze.setMethod(ZipShort.getValue(CFH_BUF, off));
  458. off += SHORT;
  459. final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(CFH_BUF, off));
  460. ze.setTime(time);
  461. off += WORD;
  462. ze.setCrc(ZipLong.getValue(CFH_BUF, off));
  463. off += WORD;
  464. ze.setCompressedSize(ZipLong.getValue(CFH_BUF, off));
  465. off += WORD;
  466. ze.setSize(ZipLong.getValue(CFH_BUF, off));
  467. off += WORD;
  468. final int fileNameLen = ZipShort.getValue(CFH_BUF, off);
  469. off += SHORT;
  470. final int extraLen = ZipShort.getValue(CFH_BUF, off);
  471. off += SHORT;
  472. final int commentLen = ZipShort.getValue(CFH_BUF, off);
  473. off += SHORT;
  474. final int diskStart = ZipShort.getValue(CFH_BUF, off);
  475. off += SHORT;
  476. ze.setInternalAttributes(ZipShort.getValue(CFH_BUF, off));
  477. off += SHORT;
  478. ze.setExternalAttributes(ZipLong.getValue(CFH_BUF, off));
  479. off += WORD;
  480. final byte[] fileName = new byte[fileNameLen];
  481. archive.readFully(fileName);
  482. ze.setName(entryEncoding.decode(fileName), fileName);
  483. // LFH offset,
  484. offset.headerOffset = ZipLong.getValue(CFH_BUF, off);
  485. // data offset will be filled later
  486. entries.add(ze);
  487. final byte[] cdExtraData = new byte[extraLen];
  488. archive.readFully(cdExtraData);
  489. ze.setCentralDirectoryExtra(cdExtraData);
  490. setSizesAndOffsetFromZip64Extra(ze, offset, diskStart);
  491. final byte[] comment = new byte[commentLen];
  492. archive.readFully(comment);
  493. ze.setComment(entryEncoding.decode(comment));
  494. if (!hasUTF8Flag && useUnicodeExtraFields) {
  495. noUTF8Flag.put(ze, new NameAndComment(fileName, comment));
  496. }
  497. }
  498. /**
  499. * If the entry holds a Zip64 extended information extra field,
  500. * read sizes from there if the entry's sizes are set to
  501. * 0xFFFFFFFFF, do the same for the offset of the local file
  502. * header.
  503. *
  504. * <p>Ensures the Zip64 extra either knows both compressed and
  505. * uncompressed size or neither of both as the internal logic in
  506. * ExtraFieldUtils forces the field to create local header data
  507. * even if they are never used - and here a field with only one
  508. * size would be invalid.</p>
  509. */
  510. private void setSizesAndOffsetFromZip64Extra(final ZipEntry ze,
  511. final OffsetEntry offset,
  512. final int diskStart)
  513. throws IOException {
  514. final Zip64ExtendedInformationExtraField z64 =
  515. (Zip64ExtendedInformationExtraField)
  516. ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
  517. if (z64 != null) {
  518. final boolean hasUncompressedSize = ze.getSize() == ZIP64_MAGIC;
  519. final boolean hasCompressedSize = ze.getCompressedSize() == ZIP64_MAGIC;
  520. final boolean hasRelativeHeaderOffset =
  521. offset.headerOffset == ZIP64_MAGIC;
  522. z64.reparseCentralDirectoryData(hasUncompressedSize,
  523. hasCompressedSize,
  524. hasRelativeHeaderOffset,
  525. diskStart == ZIP64_MAGIC_SHORT);
  526. if (hasUncompressedSize) {
  527. ze.setSize(z64.getSize().getLongValue());
  528. } else if (hasCompressedSize) {
  529. z64.setSize(new ZipEightByteInteger(ze.getSize()));
  530. }
  531. if (hasCompressedSize) {
  532. ze.setCompressedSize(z64.getCompressedSize().getLongValue());
  533. } else if (hasUncompressedSize) {
  534. z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
  535. }
  536. if (hasRelativeHeaderOffset) {
  537. offset.headerOffset =
  538. z64.getRelativeHeaderOffset().getLongValue();
  539. }
  540. }
  541. }
  542. /**
  543. * Length of the "End of central directory record" - which is
  544. * supposed to be the last structure of the archive - without file
  545. * comment.
  546. */
  547. private static final int MIN_EOCD_SIZE =
  548. /* end of central dir signature */ WORD
  549. /* number of this disk */ + SHORT
  550. /* number of the disk with the */
  551. /* start of the central directory */ + SHORT
  552. /* total number of entries in */
  553. /* the central dir on this disk */ + SHORT
  554. /* total number of entries in */
  555. /* the central dir */ + SHORT
  556. /* size of the central directory */ + WORD
  557. /* offset of start of central */
  558. /* directory with respect to */
  559. /* the starting disk number */ + WORD
  560. /* zipfile comment length */ + SHORT;
  561. /**
  562. * Maximum length of the "End of central directory record" with a
  563. * file comment.
  564. */
  565. private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE
  566. /* maximum length of zipfile comment */ + ZIP64_MAGIC_SHORT;
  567. /**
  568. * Offset of the field that holds the location of the first
  569. * central directory entry inside the "End of central directory
  570. * record" relative to the start of the "End of central directory
  571. * record".
  572. */
  573. private static final int CFD_LOCATOR_OFFSET =
  574. /* end of central dir signature */ WORD
  575. /* number of this disk */ + SHORT
  576. /* number of the disk with the */
  577. /* start of the central directory */ + SHORT
  578. /* total number of entries in */
  579. /* the central dir on this disk */ + SHORT
  580. /* total number of entries in */
  581. /* the central dir */ + SHORT
  582. /* size of the central directory */ + WORD;
  583. /**
  584. * Length of the "Zip64 end of central directory locator" - which
  585. * should be right in front of the "end of central directory
  586. * record" if one is present at all.
  587. */
  588. private static final int ZIP64_EOCDL_LENGTH =
  589. /* zip64 end of central dir locator sig */ WORD
  590. /* number of the disk with the start */
  591. /* start of the zip64 end of */
  592. /* central directory */ + WORD
  593. /* relative offset of the zip64 */
  594. /* end of central directory record */ + DWORD
  595. /* total number of disks */ + WORD;
  596. /**
  597. * Offset of the field that holds the location of the "Zip64 end
  598. * of central directory record" inside the "Zip64 end of central
  599. * directory locator" relative to the start of the "Zip64 end of
  600. * central directory locator".
  601. */
  602. private static final int ZIP64_EOCDL_LOCATOR_OFFSET =
  603. /* zip64 end of central dir locator sig */ WORD
  604. /* number of the disk with the start */
  605. /* start of the zip64 end of */
  606. /* central directory */ + WORD;
  607. /**
  608. * Offset of the field that holds the location of the first
  609. * central directory entry inside the "Zip64 end of central
  610. * directory record" relative to the start of the "Zip64 end of
  611. * central directory record".
  612. */
  613. private static final int ZIP64_EOCD_CFD_LOCATOR_OFFSET =
  614. /* zip64 end of central dir */
  615. /* signature */ WORD
  616. /* size of zip64 end of central */
  617. /* directory record */ + DWORD
  618. /* version made by */ + SHORT
  619. /* version needed to extract */ + SHORT
  620. /* number of this disk */ + WORD
  621. /* number of the disk with the */
  622. /* start of the central directory */ + WORD
  623. /* total number of entries in the */
  624. /* central directory on this disk */ + DWORD
  625. /* total number of entries in the */
  626. /* central directory */ + DWORD
  627. /* size of the central directory */ + DWORD;
  628. /**
  629. * Searches for either the &quot;Zip64 end of central directory
  630. * locator&quot; or the &quot;End of central dir record&quot;, parses
  631. * it and positions the stream at the first central directory
  632. * record.
  633. */
  634. private void positionAtCentralDirectory()
  635. throws IOException {
  636. positionAtEndOfCentralDirectoryRecord();
  637. boolean found = false;
  638. final boolean searchedForZip64EOCD =
  639. archive.getFilePointer() > ZIP64_EOCDL_LENGTH;
  640. if (searchedForZip64EOCD) {
  641. archive.seek(archive.getFilePointer() - ZIP64_EOCDL_LENGTH);
  642. archive.readFully(WORD_BUF);
  643. found = Arrays.equals(ZipOutputStream.ZIP64_EOCD_LOC_SIG, WORD_BUF);
  644. }
  645. if (!found) {
  646. // not a ZIP64 archive
  647. if (searchedForZip64EOCD) {
  648. skipBytes(ZIP64_EOCDL_LENGTH - WORD);
  649. }
  650. positionAtCentralDirectory32();
  651. } else {
  652. positionAtCentralDirectory64();
  653. }
  654. }
  655. /**
  656. * Parses the &quot;Zip64 end of central directory locator&quot;,
  657. * finds the &quot;Zip64 end of central directory record&quot; using the
  658. * parsed information, parses that and positions the stream at the
  659. * first central directory record.
  660. */
  661. private void positionAtCentralDirectory64()
  662. throws IOException {
  663. skipBytes(ZIP64_EOCDL_LOCATOR_OFFSET
  664. - WORD /* signature has already been read */);
  665. archive.readFully(DWORD_BUF);
  666. archive.seek(ZipEightByteInteger.getLongValue(DWORD_BUF));
  667. archive.readFully(WORD_BUF);
  668. if (!Arrays.equals(WORD_BUF, ZipOutputStream.ZIP64_EOCD_SIG)) {
  669. throw new ZipException("archive's ZIP64 end of central "
  670. + "directory locator is corrupt.");
  671. }
  672. skipBytes(ZIP64_EOCD_CFD_LOCATOR_OFFSET
  673. - WORD /* signature has already been read */);
  674. archive.readFully(DWORD_BUF);
  675. archive.seek(ZipEightByteInteger.getLongValue(DWORD_BUF));
  676. }
  677. /**
  678. * Searches for the &quot;End of central dir record&quot;, parses
  679. * it and positions the stream at the first central directory
  680. * record.
  681. */
  682. private void positionAtCentralDirectory32()
  683. throws IOException {
  684. skipBytes(CFD_LOCATOR_OFFSET);
  685. archive.readFully(WORD_BUF);
  686. archive.seek(ZipLong.getValue(WORD_BUF));
  687. }
  688. /**
  689. * Searches for the and positions the stream at the start of the
  690. * &quot;End of central dir record&quot;.
  691. */
  692. private void positionAtEndOfCentralDirectoryRecord()
  693. throws IOException {
  694. final boolean found = tryToLocateSignature(MIN_EOCD_SIZE, MAX_EOCD_SIZE,
  695. ZipOutputStream.EOCD_SIG);
  696. if (!found) {
  697. throw new ZipException("archive is not a ZIP archive");
  698. }
  699. }
  700. /**
  701. * Searches the archive backwards from minDistance to maxDistance
  702. * for the given signature, positions the RandomaccessFile right
  703. * at the signature if it has been found.
  704. */
  705. private boolean tryToLocateSignature(final long minDistanceFromEnd,
  706. final long maxDistanceFromEnd,
  707. final byte[] sig) throws IOException {
  708. boolean found = false;
  709. long off = archive.length() - minDistanceFromEnd;
  710. final long stopSearching =
  711. Math.max(0L, archive.length() - maxDistanceFromEnd);
  712. if (off >= 0) {
  713. for (; off >= stopSearching; off--) {
  714. archive.seek(off);
  715. int curr = archive.read();
  716. if (curr == -1) {
  717. break;
  718. }
  719. if (curr == sig[POS_0]) {
  720. curr = archive.read();
  721. if (curr == sig[POS_1]) {
  722. curr = archive.read();
  723. if (curr == sig[POS_2]) {
  724. curr = archive.read();
  725. if (curr == sig[POS_3]) {
  726. found = true;
  727. break;
  728. }
  729. }
  730. }
  731. }
  732. }
  733. }
  734. if (found) {
  735. archive.seek(off);
  736. }
  737. return found;
  738. }
  739. /**
  740. * Skips the given number of bytes or throws an EOFException if
  741. * skipping failed.
  742. */
  743. private void skipBytes(final int count) throws IOException {
  744. int totalSkipped = 0;
  745. while (totalSkipped < count) {
  746. final int skippedNow = archive.skipBytes(count - totalSkipped);
  747. if (skippedNow <= 0) {
  748. throw new EOFException();
  749. }
  750. totalSkipped += skippedNow;
  751. }
  752. }
  753. /**
  754. * Number of bytes in local file header up to the &quot;length of
  755. * filename&quot; entry.
  756. */
  757. private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
  758. /* local file header signature */ WORD
  759. /* version needed to extract */ + SHORT
  760. /* general purpose bit flag */ + SHORT
  761. /* compression method */ + SHORT
  762. /* last mod file time */ + SHORT
  763. /* last mod file date */ + SHORT
  764. /* crc-32 */ + WORD
  765. /* compressed size */ + WORD
  766. /* uncompressed size */ + WORD;
  767. /**
  768. * Walks through all recorded entries and adds the data available
  769. * from the local file header.
  770. *
  771. * <p>Also records the offsets for the data to read from the
  772. * entries.</p>
  773. */
  774. private void resolveLocalFileHeaderData(final Map<ZipEntry, NameAndComment>
  775. entriesWithoutUTF8Flag)
  776. throws IOException {
  777. for (final Iterator<ZipEntry> it = entries.iterator(); it.hasNext();) {
  778. // entries is filled in populateFromCentralDirectory and
  779. // never modified
  780. final Entry ze = (Entry) it.next();
  781. final OffsetEntry offsetEntry = ze.getOffsetEntry();
  782. final long offset = offsetEntry.headerOffset;
  783. archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
  784. archive.readFully(SHORT_BUF);
  785. final int fileNameLen = ZipShort.getValue(SHORT_BUF);
  786. archive.readFully(SHORT_BUF);
  787. final int extraFieldLen = ZipShort.getValue(SHORT_BUF);
  788. int lenToSkip = fileNameLen;
  789. while (lenToSkip > 0) {
  790. final int skipped = archive.skipBytes(lenToSkip);
  791. if (skipped <= 0) {
  792. throw new IOException("failed to skip file name in"
  793. + " local file header");
  794. }
  795. lenToSkip -= skipped;
  796. }
  797. final byte[] localExtraData = new byte[extraFieldLen];
  798. archive.readFully(localExtraData);
  799. ze.setExtra(localExtraData);
  800. offsetEntry.dataOffset = offset + LFH_OFFSET_FOR_FILENAME_LENGTH
  801. + SHORT + SHORT + fileNameLen + extraFieldLen;
  802. if (entriesWithoutUTF8Flag.containsKey(ze)) {
  803. final NameAndComment nc = entriesWithoutUTF8Flag.get(ze);
  804. ZipUtil.setNameAndCommentFromExtraFields(ze, nc.name,
  805. nc.comment);
  806. }
  807. final String name = ze.getName();
  808. LinkedList<ZipEntry> entriesOfThatName = nameMap.get(name);
  809. if (entriesOfThatName == null) {
  810. entriesOfThatName = new LinkedList<ZipEntry>();
  811. nameMap.put(name, entriesOfThatName);
  812. }
  813. entriesOfThatName.addLast(ze);
  814. }
  815. }
  816. /**
  817. * Checks whether the archive starts with a LFH. If it doesn't,
  818. * it may be an empty archive.
  819. */
  820. private boolean startsWithLocalFileHeader() throws IOException {
  821. archive.seek(0);
  822. archive.readFully(WORD_BUF);
  823. return Arrays.equals(WORD_BUF, ZipOutputStream.LFH_SIG);
  824. }
  825. /**
  826. * InputStream that delegates requests to the underlying
  827. * RandomAccessFile, making sure that only bytes from a certain
  828. * range can be read.
  829. */
  830. private class BoundedInputStream extends InputStream {
  831. private long remaining;
  832. private long loc;
  833. private boolean addDummyByte = false;
  834. BoundedInputStream(final long start, final long remaining) {
  835. this.remaining = remaining;
  836. loc = start;
  837. }
  838. @Override
  839. public int read() throws IOException {
  840. if (remaining-- <= 0) {
  841. if (addDummyByte) {
  842. addDummyByte = false;
  843. return 0;
  844. }
  845. return -1;
  846. }
  847. synchronized (archive) {
  848. archive.seek(loc++);
  849. return archive.read();
  850. }
  851. }
  852. @Override
  853. public int read(final byte[] b, final int off, int len) throws IOException {
  854. if (remaining <= 0) {
  855. if (addDummyByte) {
  856. addDummyByte = false;
  857. b[off] = 0;
  858. return 1;
  859. }
  860. return -1;
  861. }
  862. if (len <= 0) {
  863. return 0;
  864. }
  865. if (len > remaining) {
  866. len = (int) remaining;
  867. }
  868. int ret = -1;
  869. synchronized (archive) {
  870. archive.seek(loc);
  871. ret = archive.read(b, off, len);
  872. }
  873. if (ret > 0) {
  874. loc += ret;
  875. remaining -= ret;
  876. }
  877. return ret;
  878. }
  879. /**
  880. * Inflater needs an extra dummy byte for nowrap - see
  881. * Inflater's javadocs.
  882. */
  883. void addDummy() {
  884. addDummyByte = true;
  885. }
  886. }
  887. private static final class NameAndComment {
  888. private final byte[] name;
  889. private final byte[] comment;
  890. private NameAndComment(final byte[] name, final byte[] comment) {
  891. this.name = name;
  892. this.comment = comment;
  893. }
  894. }
  895. /**
  896. * Compares two ZipEntries based on their offset within the archive.
  897. *
  898. * <p>Won't return any meaningful results if one of the entries
  899. * isn't part of the archive at all.</p>
  900. *
  901. * @since Ant 1.9.0
  902. */
  903. private final Comparator<ZipEntry> OFFSET_COMPARATOR =
  904. new Comparator<ZipEntry>() {
  905. public int compare(final ZipEntry e1, final ZipEntry e2) {
  906. if (e1 == e2) {
  907. return 0;
  908. }
  909. final Entry ent1 = e1 instanceof Entry ? (Entry) e1 : null;
  910. final Entry ent2 = e2 instanceof Entry ? (Entry) e2 : null;
  911. if (ent1 == null) {
  912. return 1;
  913. }
  914. if (ent2 == null) {
  915. return -1;
  916. }
  917. final long val = (ent1.getOffsetEntry().headerOffset
  918. - ent2.getOffsetEntry().headerOffset);
  919. return val == 0 ? 0 : val < 0 ? -1 : +1;
  920. }
  921. };
  922. /**
  923. * Extends ZipEntry to store the offset within the archive.
  924. */
  925. private static class Entry extends ZipEntry {
  926. private final OffsetEntry offsetEntry;
  927. Entry(final OffsetEntry offset) {
  928. this.offsetEntry = offset;
  929. }
  930. OffsetEntry getOffsetEntry() {
  931. return offsetEntry;
  932. }
  933. @Override
  934. public int hashCode() {
  935. return 3 * super.hashCode()
  936. + (int) (offsetEntry.headerOffset % Integer.MAX_VALUE);
  937. }
  938. @Override
  939. public boolean equals(final Object other) {
  940. if (super.equals(other)) {
  941. // super.equals would return false if other were not an Entry
  942. final Entry otherEntry = (Entry) other;
  943. return offsetEntry.headerOffset
  944. == otherEntry.offsetEntry.headerOffset
  945. && offsetEntry.dataOffset
  946. == otherEntry.offsetEntry.dataOffset;
  947. }
  948. return false;
  949. }
  950. }
  951. }