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