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

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