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

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