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.

ZipEntry.java 23 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. package org.apache.tools.zip;
  19. import java.io.File;
  20. import java.util.ArrayList;
  21. import java.util.Arrays;
  22. import java.util.Date;
  23. import java.util.LinkedHashMap;
  24. import java.util.List;
  25. import java.util.zip.ZipException;
  26. /**
  27. * Extension that adds better handling of extra fields and provides
  28. * access to the internal and external file attributes.
  29. *
  30. * <p>The extra data is expected to follow the recommendation of
  31. * {@link <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">
  32. * APPNOTE.txt</a>}:</p>
  33. * <ul>
  34. * <li>the extra byte array consists of a sequence of extra fields</li>
  35. * <li>each extra fields starts by a two byte header id followed by
  36. * a two byte sequence holding the length of the remainder of
  37. * data.</li>
  38. * </ul>
  39. *
  40. * <p>Any extra data that cannot be parsed by the rules above will be
  41. * consumed as "unparseable" extra data and treated differently by the
  42. * methods of this class. Older versions would have thrown an
  43. * exception if any attempt was made to read or write extra data not
  44. * conforming to the recommendation.</p>
  45. *
  46. */
  47. public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable {
  48. public static final int PLATFORM_UNIX = 3;
  49. public static final int PLATFORM_FAT = 0;
  50. private static final int SHORT_MASK = 0xFFFF;
  51. private static final int SHORT_SHIFT = 16;
  52. /**
  53. * The {@link java.util.zip.ZipEntry} base class only supports
  54. * the compression methods STORED and DEFLATED. We override the
  55. * field so that any compression methods can be used.
  56. * <p>
  57. * The default value -1 means that the method has not been specified.
  58. */
  59. private int method = -1;
  60. /**
  61. * The {@link java.util.zip.ZipEntry#setSize} method in the base
  62. * class throws an IllegalArgumentException if the size is bigger
  63. * than 2GB for Java versions < 7. Need to keep our own size
  64. * information for Zip64 support.
  65. */
  66. private long size = -1;
  67. private int internalAttributes = 0;
  68. private int platform = PLATFORM_FAT;
  69. private long externalAttributes = 0;
  70. private LinkedHashMap<ZipShort, ZipExtraField> extraFields = null;
  71. private UnparseableExtraFieldData unparseableExtra = null;
  72. private String name = null;
  73. private byte[] rawName = null;
  74. private GeneralPurposeBit gpb = new GeneralPurposeBit();
  75. /**
  76. * Creates a new zip entry with the specified name.
  77. *
  78. * <p>Assumes the entry represents a directory if and only if the
  79. * name ends with a forward slash "/".</p>
  80. *
  81. * @param name the name of the entry
  82. * @since 1.1
  83. */
  84. public ZipEntry(String name) {
  85. super(name);
  86. setName(name);
  87. }
  88. /**
  89. * Creates a new zip entry with fields taken from the specified zip entry.
  90. *
  91. * <p>Assumes the entry represents a directory if and only if the
  92. * name ends with a forward slash "/".</p>
  93. *
  94. * @param entry the entry to get fields from
  95. * @since 1.1
  96. * @throws ZipException on error
  97. */
  98. public ZipEntry(java.util.zip.ZipEntry entry) throws ZipException {
  99. super(entry);
  100. setName(entry.getName());
  101. byte[] extra = entry.getExtra();
  102. if (extra != null) {
  103. setExtraFields(ExtraFieldUtils.parse(extra, true,
  104. ExtraFieldUtils
  105. .UnparseableExtraField.READ));
  106. } else {
  107. // initializes extra data to an empty byte array
  108. setExtra();
  109. }
  110. setMethod(entry.getMethod());
  111. this.size = entry.getSize();
  112. }
  113. /**
  114. * Creates a new zip entry with fields taken from the specified zip entry.
  115. *
  116. * <p>Assumes the entry represents a directory if and only if the
  117. * name ends with a forward slash "/".</p>
  118. *
  119. * @param entry the entry to get fields from
  120. * @throws ZipException on error
  121. * @since 1.1
  122. */
  123. public ZipEntry(ZipEntry entry) throws ZipException {
  124. this((java.util.zip.ZipEntry) entry);
  125. setInternalAttributes(entry.getInternalAttributes());
  126. setExternalAttributes(entry.getExternalAttributes());
  127. setExtraFields(entry.getExtraFields(true));
  128. }
  129. /**
  130. * @since 1.9
  131. */
  132. protected ZipEntry() {
  133. this("");
  134. }
  135. /**
  136. * Creates a new zip entry taking some information from the given
  137. * file and using the provided name.
  138. *
  139. * <p>The name will be adjusted to end with a forward slash "/" if
  140. * the file is a directory. If the file is not a directory a
  141. * potential trailing forward slash will be stripped from the
  142. * entry name.</p>
  143. */
  144. public ZipEntry(File inputFile, String entryName) {
  145. this(inputFile.isDirectory() && !entryName.endsWith("/") ?
  146. entryName + "/" : entryName);
  147. if (inputFile.isFile()){
  148. setSize(inputFile.length());
  149. }
  150. setTime(inputFile.lastModified());
  151. // TODO are there any other fields we can set here?
  152. }
  153. /**
  154. * Overwrite clone.
  155. * @return a cloned copy of this ZipEntry
  156. * @since 1.1
  157. */
  158. @Override
  159. public Object clone() {
  160. ZipEntry e = (ZipEntry) super.clone();
  161. e.setInternalAttributes(getInternalAttributes());
  162. e.setExternalAttributes(getExternalAttributes());
  163. e.setExtraFields(getExtraFields(true));
  164. return e;
  165. }
  166. /**
  167. * Returns the compression method of this entry, or -1 if the
  168. * compression method has not been specified.
  169. *
  170. * @return compression method
  171. */
  172. @Override
  173. public int getMethod() {
  174. return method;
  175. }
  176. /**
  177. * Sets the compression method of this entry.
  178. *
  179. * @param method compression method
  180. */
  181. @Override
  182. public void setMethod(int method) {
  183. if (method < 0) {
  184. throw new IllegalArgumentException(
  185. "ZIP compression method can not be negative: " + method);
  186. }
  187. this.method = method;
  188. }
  189. /**
  190. * Retrieves the internal file attributes.
  191. *
  192. * @return the internal file attributes
  193. * @since 1.1
  194. */
  195. public int getInternalAttributes() {
  196. return internalAttributes;
  197. }
  198. /**
  199. * Sets the internal file attributes.
  200. * @param value an <code>int</code> value
  201. * @since 1.1
  202. */
  203. public void setInternalAttributes(int value) {
  204. internalAttributes = value;
  205. }
  206. /**
  207. * Retrieves the external file attributes.
  208. * @return the external file attributes
  209. * @since 1.1
  210. */
  211. public long getExternalAttributes() {
  212. return externalAttributes;
  213. }
  214. /**
  215. * Sets the external file attributes.
  216. * @param value an <code>long</code> value
  217. * @since 1.1
  218. */
  219. public void setExternalAttributes(long value) {
  220. externalAttributes = value;
  221. }
  222. /**
  223. * Sets Unix permissions in a way that is understood by Info-Zip's
  224. * unzip command.
  225. * @param mode an <code>int</code> value
  226. * @since Ant 1.5.2
  227. */
  228. public void setUnixMode(int mode) {
  229. // CheckStyle:MagicNumberCheck OFF - no point
  230. setExternalAttributes((mode << SHORT_SHIFT)
  231. // MS-DOS read-only attribute
  232. | ((mode & 0200) == 0 ? 1 : 0)
  233. // MS-DOS directory flag
  234. | (isDirectory() ? 0x10 : 0));
  235. // CheckStyle:MagicNumberCheck ON
  236. platform = PLATFORM_UNIX;
  237. }
  238. /**
  239. * Unix permission.
  240. * @return the unix permissions
  241. * @since Ant 1.6
  242. */
  243. public int getUnixMode() {
  244. return platform != PLATFORM_UNIX ? 0 :
  245. (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK);
  246. }
  247. /**
  248. * Platform specification to put into the &quot;version made
  249. * by&quot; part of the central file header.
  250. *
  251. * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode}
  252. * has been called, in which case PLATFORM_UNIX will be returned.
  253. *
  254. * @since Ant 1.5.2
  255. */
  256. public int getPlatform() {
  257. return platform;
  258. }
  259. /**
  260. * Set the platform (UNIX or FAT).
  261. * @param platform an <code>int</code> value - 0 is FAT, 3 is UNIX
  262. * @since 1.9
  263. */
  264. protected void setPlatform(int platform) {
  265. this.platform = platform;
  266. }
  267. /**
  268. * Replaces all currently attached extra fields with the new array.
  269. * @param fields an array of extra fields
  270. * @since 1.1
  271. */
  272. public void setExtraFields(ZipExtraField[] fields) {
  273. extraFields = new LinkedHashMap<ZipShort, ZipExtraField>();
  274. for (ZipExtraField field : fields) {
  275. if (field instanceof UnparseableExtraFieldData) {
  276. unparseableExtra = (UnparseableExtraFieldData) field;
  277. } else {
  278. extraFields.put(field.getHeaderId(), field);
  279. }
  280. }
  281. setExtra();
  282. }
  283. /**
  284. * Retrieves all extra fields that have been parsed successfully.
  285. * @return an array of the extra fields
  286. */
  287. public ZipExtraField[] getExtraFields() {
  288. return getExtraFields(false);
  289. }
  290. /**
  291. * Retrieves extra fields.
  292. * @param includeUnparseable whether to also return unparseable
  293. * extra fields as {@link UnparseableExtraFieldData} if such data
  294. * exists.
  295. * @return an array of the extra fields
  296. * @since 1.1
  297. */
  298. public ZipExtraField[] getExtraFields(boolean includeUnparseable) {
  299. if (extraFields == null) {
  300. return !includeUnparseable || unparseableExtra == null
  301. ? new ZipExtraField[0]
  302. : new ZipExtraField[] { unparseableExtra };
  303. }
  304. List<ZipExtraField> result =
  305. new ArrayList<ZipExtraField>(extraFields.values());
  306. if (includeUnparseable && unparseableExtra != null) {
  307. result.add(unparseableExtra);
  308. }
  309. return result.toArray(new ZipExtraField[0]);
  310. }
  311. /**
  312. * Adds an extra field - replacing an already present extra field
  313. * of the same type.
  314. *
  315. * <p>If no extra field of the same type exists, the field will be
  316. * added as last field.</p>
  317. * @param ze an extra field
  318. * @since 1.1
  319. */
  320. public void addExtraField(ZipExtraField ze) {
  321. if (ze instanceof UnparseableExtraFieldData) {
  322. unparseableExtra = (UnparseableExtraFieldData) ze;
  323. } else {
  324. if (extraFields == null) {
  325. extraFields = new LinkedHashMap<ZipShort, ZipExtraField>();
  326. }
  327. extraFields.put(ze.getHeaderId(), ze);
  328. }
  329. setExtra();
  330. }
  331. /**
  332. * Adds an extra field - replacing an already present extra field
  333. * of the same type.
  334. *
  335. * <p>The new extra field will be the first one.</p>
  336. * @param ze an extra field
  337. * @since 1.1
  338. */
  339. public void addAsFirstExtraField(ZipExtraField ze) {
  340. if (ze instanceof UnparseableExtraFieldData) {
  341. unparseableExtra = (UnparseableExtraFieldData) ze;
  342. } else {
  343. LinkedHashMap<ZipShort, ZipExtraField> copy = extraFields;
  344. extraFields = new LinkedHashMap<ZipShort, ZipExtraField>();
  345. extraFields.put(ze.getHeaderId(), ze);
  346. if (copy != null) {
  347. copy.remove(ze.getHeaderId());
  348. extraFields.putAll(copy);
  349. }
  350. }
  351. setExtra();
  352. }
  353. /**
  354. * Remove an extra field.
  355. * @param type the type of extra field to remove
  356. * @since 1.1
  357. */
  358. public void removeExtraField(ZipShort type) {
  359. if (extraFields == null) {
  360. throw new java.util.NoSuchElementException();
  361. }
  362. if (extraFields.remove(type) == null) {
  363. throw new java.util.NoSuchElementException();
  364. }
  365. setExtra();
  366. }
  367. /**
  368. * Removes unparseable extra field data.
  369. */
  370. public void removeUnparseableExtraFieldData() {
  371. if (unparseableExtra == null) {
  372. throw new java.util.NoSuchElementException();
  373. }
  374. unparseableExtra = null;
  375. setExtra();
  376. }
  377. /**
  378. * Looks up an extra field by its header id.
  379. *
  380. * @return null if no such field exists.
  381. */
  382. public ZipExtraField getExtraField(ZipShort type) {
  383. if (extraFields != null) {
  384. return extraFields.get(type);
  385. }
  386. return null;
  387. }
  388. /**
  389. * Looks up extra field data that couldn't be parsed correctly.
  390. *
  391. * @return null if no such field exists.
  392. */
  393. public UnparseableExtraFieldData getUnparseableExtraFieldData() {
  394. return unparseableExtra;
  395. }
  396. /**
  397. * Parses the given bytes as extra field data and consumes any
  398. * unparseable data as an {@link UnparseableExtraFieldData}
  399. * instance.
  400. * @param extra an array of bytes to be parsed into extra fields
  401. * @throws RuntimeException if the bytes cannot be parsed
  402. * @since 1.1
  403. * @throws RuntimeException on error
  404. */
  405. @Override
  406. public void setExtra(byte[] extra) throws RuntimeException {
  407. try {
  408. ZipExtraField[] local =
  409. ExtraFieldUtils.parse(extra, true,
  410. ExtraFieldUtils.UnparseableExtraField.READ);
  411. mergeExtraFields(local, true);
  412. } catch (ZipException e) {
  413. // actually this is not be possible as of Ant 1.8.1
  414. throw new RuntimeException("Error parsing extra fields for entry: "
  415. + getName() + " - " + e.getMessage(), e);
  416. }
  417. }
  418. /**
  419. * Unfortunately {@link java.util.zip.ZipOutputStream
  420. * java.util.zip.ZipOutputStream} seems to access the extra data
  421. * directly, so overriding getExtra doesn't help - we need to
  422. * modify super's data directly.
  423. *
  424. * @since 1.1
  425. */
  426. protected void setExtra() {
  427. super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields(true)));
  428. }
  429. /**
  430. * Sets the central directory part of extra fields.
  431. */
  432. public void setCentralDirectoryExtra(byte[] b) {
  433. try {
  434. ZipExtraField[] central =
  435. ExtraFieldUtils.parse(b, false,
  436. ExtraFieldUtils.UnparseableExtraField.READ);
  437. mergeExtraFields(central, false);
  438. } catch (ZipException e) {
  439. throw new RuntimeException(e.getMessage(), e);
  440. }
  441. }
  442. /**
  443. * Retrieves the extra data for the local file data.
  444. * @return the extra data for local file
  445. * @since 1.1
  446. */
  447. public byte[] getLocalFileDataExtra() {
  448. byte[] extra = getExtra();
  449. return extra != null ? extra : new byte[0];
  450. }
  451. /**
  452. * Retrieves the extra data for the central directory.
  453. * @return the central directory extra data
  454. * @since 1.1
  455. */
  456. public byte[] getCentralDirectoryExtra() {
  457. return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields(true));
  458. }
  459. /**
  460. * Make this class work in JDK 1.1 like a 1.2 class.
  461. *
  462. * <p>This either stores the size for later usage or invokes
  463. * setCompressedSize via reflection.</p>
  464. * @param size the size to use
  465. * @deprecated since 1.7.
  466. * Use setCompressedSize directly.
  467. * @since 1.2
  468. */
  469. public void setComprSize(long size) {
  470. setCompressedSize(size);
  471. }
  472. /**
  473. * Get the name of the entry.
  474. * @return the entry name
  475. * @since 1.9
  476. */
  477. @Override
  478. public String getName() {
  479. return name == null ? super.getName() : name;
  480. }
  481. /**
  482. * Is this entry a directory?
  483. * @return true if the entry is a directory
  484. * @since 1.10
  485. */
  486. @Override
  487. public boolean isDirectory() {
  488. return getName().endsWith("/");
  489. }
  490. /**
  491. * Set the name of the entry.
  492. * @param name the name to use
  493. */
  494. protected void setName(String name) {
  495. if (name != null && getPlatform() == PLATFORM_FAT
  496. && name.indexOf("/") == -1) {
  497. name = name.replace('\\', '/');
  498. }
  499. this.name = name;
  500. }
  501. /**
  502. * Gets the uncompressed size of the entry data.
  503. * @return the entry size
  504. */
  505. @Override
  506. public long getSize() {
  507. return size;
  508. }
  509. /**
  510. * Sets the uncompressed size of the entry data.
  511. * @param size the uncompressed size in bytes
  512. * @exception IllegalArgumentException if the specified size is less
  513. * than 0
  514. */
  515. @Override
  516. public void setSize(long size) {
  517. if (size < 0) {
  518. throw new IllegalArgumentException("invalid entry size");
  519. }
  520. this.size = size;
  521. }
  522. /**
  523. * Sets the name using the raw bytes and the string created from
  524. * it by guessing or using the configured encoding.
  525. * @param name the name to use created from the raw bytes using
  526. * the guessed or configured encoding
  527. * @param rawName the bytes originally read as name from the
  528. * archive
  529. */
  530. protected void setName(String name, byte[] rawName) {
  531. setName(name);
  532. this.rawName = rawName;
  533. }
  534. /**
  535. * Returns the raw bytes that made up the name before it has been
  536. * converted using the configured or guessed encoding.
  537. *
  538. * <p>This method will return null if this instance has not been
  539. * read from an archive.</p>
  540. */
  541. public byte[] getRawName() {
  542. if (rawName != null) {
  543. byte[] b = new byte[rawName.length];
  544. System.arraycopy(rawName, 0, b, 0, rawName.length);
  545. return b;
  546. }
  547. return null;
  548. }
  549. /**
  550. * Get the hashCode of the entry.
  551. * This uses the name as the hashcode.
  552. * @return a hashcode.
  553. * @since Ant 1.7
  554. */
  555. @Override
  556. public int hashCode() {
  557. // this method has severe consequences on performance. We cannot rely
  558. // on the super.hashCode() method since super.getName() always return
  559. // the empty string in the current implemention (there's no setter)
  560. // so it is basically draining the performance of a hashmap lookup
  561. return getName().hashCode();
  562. }
  563. /**
  564. * The "general purpose bit" field.
  565. */
  566. public GeneralPurposeBit getGeneralPurposeBit() {
  567. return gpb;
  568. }
  569. /**
  570. * The "general purpose bit" field.
  571. */
  572. public void setGeneralPurposeBit(GeneralPurposeBit b) {
  573. gpb = b;
  574. }
  575. /**
  576. * If there are no extra fields, use the given fields as new extra
  577. * data - otherwise merge the fields assuming the existing fields
  578. * and the new fields stem from different locations inside the
  579. * archive.
  580. * @param f the extra fields to merge
  581. * @param local whether the new fields originate from local data
  582. */
  583. private void mergeExtraFields(ZipExtraField[] f, boolean local)
  584. throws ZipException {
  585. if (extraFields == null) {
  586. setExtraFields(f);
  587. } else {
  588. for (ZipExtraField element : f) {
  589. ZipExtraField existing;
  590. if (element instanceof UnparseableExtraFieldData) {
  591. existing = unparseableExtra;
  592. } else {
  593. existing = getExtraField(element.getHeaderId());
  594. }
  595. if (existing == null) {
  596. addExtraField(element);
  597. } else {
  598. if (local
  599. || !(existing
  600. instanceof CentralDirectoryParsingZipExtraField)) {
  601. byte[] b = element.getLocalFileDataData();
  602. existing.parseFromLocalFileData(b, 0, b.length);
  603. } else {
  604. byte[] b = element.getCentralDirectoryData();
  605. ((CentralDirectoryParsingZipExtraField) existing)
  606. .parseFromCentralDirectoryData(b, 0, b.length);
  607. }
  608. }
  609. }
  610. setExtra();
  611. }
  612. }
  613. /** {@inheritDoc} */
  614. public Date getLastModifiedDate() {
  615. return new Date(getTime());
  616. }
  617. /* (non-Javadoc)
  618. * @see java.lang.Object#equals(java.lang.Object)
  619. */
  620. @Override
  621. public boolean equals(Object obj) {
  622. if (this == obj) {
  623. return true;
  624. }
  625. if (obj == null || getClass() != obj.getClass()) {
  626. return false;
  627. }
  628. ZipEntry other = (ZipEntry) obj;
  629. String myName = getName();
  630. String otherName = other.getName();
  631. if (myName == null) {
  632. if (otherName != null) {
  633. return false;
  634. }
  635. } else if (!myName.equals(otherName)) {
  636. return false;
  637. }
  638. String myComment = getComment();
  639. String otherComment = other.getComment();
  640. if (myComment == null) {
  641. myComment = "";
  642. }
  643. if (otherComment == null) {
  644. otherComment = "";
  645. }
  646. return getTime() == other.getTime()
  647. && myComment.equals(otherComment)
  648. && getInternalAttributes() == other.getInternalAttributes()
  649. && getPlatform() == other.getPlatform()
  650. && getExternalAttributes() == other.getExternalAttributes()
  651. && getMethod() == other.getMethod()
  652. && getSize() == other.getSize()
  653. && getCrc() == other.getCrc()
  654. && getCompressedSize() == other.getCompressedSize()
  655. && Arrays.equals(getCentralDirectoryExtra(),
  656. other.getCentralDirectoryExtra())
  657. && Arrays.equals(getLocalFileDataExtra(),
  658. other.getLocalFileDataExtra())
  659. && gpb.equals(other.gpb);
  660. }
  661. }