1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.zip;
  18. import java.io.File;
  19. import java.io.FileOutputStream;
  20. import java.io.FilterOutputStream;
  21. import java.io.IOException;
  22. import java.io.OutputStream;
  23. import java.io.RandomAccessFile;
  24. import java.io.UnsupportedEncodingException;
  25. import java.util.Date;
  26. import java.util.Hashtable;
  27. import java.util.Vector;
  28. import java.util.zip.CRC32;
  29. import java.util.zip.Deflater;
  30. import java.util.zip.ZipException;
  31. /**
  32. * Reimplementation of {@link java.util.zip.ZipOutputStream
  33. * java.util.zip.ZipOutputStream} that does handle the extended
  34. * functionality of this package, especially internal/external file
  35. * attributes and extra fields with different layouts for local file
  36. * data and central directory entries.
  37. *
  38. * <p>This class will try to use {@link java.io.RandomAccessFile
  39. * RandomAccessFile} when you know that the output is going to go to a
  40. * file.</p>
  41. *
  42. * <p>If RandomAccessFile cannot be used, this implementation will use
  43. * a Data Descriptor to store size and CRC information for {@link
  44. * #DEFLATED DEFLATED} entries, this means, you don't need to
  45. * calculate them yourself. Unfortunately this is not possible for
  46. * the {@link #STORED STORED} method, here setting the CRC and
  47. * uncompressed size information is required before {@link
  48. * #putNextEntry putNextEntry} can be called.</p>
  49. *
  50. * @version $Revision: 1.17.2.7 $
  51. */
  52. public class ZipOutputStream extends FilterOutputStream {
  53. /**
  54. * Current entry.
  55. *
  56. * @since 1.1
  57. */
  58. private ZipEntry entry;
  59. /**
  60. * The file comment.
  61. *
  62. * @since 1.1
  63. */
  64. private String comment = "";
  65. /**
  66. * Compression level for next entry.
  67. *
  68. * @since 1.1
  69. */
  70. private int level = Deflater.DEFAULT_COMPRESSION;
  71. /**
  72. * Has the compression level changed when compared to the last
  73. * entry?
  74. *
  75. * @since 1.5
  76. */
  77. private boolean hasCompressionLevelChanged = false;
  78. /**
  79. * Default compression method for next entry.
  80. *
  81. * @since 1.1
  82. */
  83. private int method = DEFLATED;
  84. /**
  85. * List of ZipEntries written so far.
  86. *
  87. * @since 1.1
  88. */
  89. private Vector entries = new Vector();
  90. /**
  91. * CRC instance to avoid parsing DEFLATED data twice.
  92. *
  93. * @since 1.1
  94. */
  95. private CRC32 crc = new CRC32();
  96. /**
  97. * Count the bytes written to out.
  98. *
  99. * @since 1.1
  100. */
  101. private long written = 0;
  102. /**
  103. * Data for local header data
  104. *
  105. * @since 1.1
  106. */
  107. private long dataStart = 0;
  108. /**
  109. * Offset for CRC entry in the local file header data for the
  110. * current entry starts here.
  111. *
  112. * @since 1.15
  113. */
  114. private long localDataStart = 0;
  115. /**
  116. * Start of central directory.
  117. *
  118. * @since 1.1
  119. */
  120. private ZipLong cdOffset = new ZipLong(0);
  121. /**
  122. * Length of central directory.
  123. *
  124. * @since 1.1
  125. */
  126. private ZipLong cdLength = new ZipLong(0);
  127. /**
  128. * Helper, a 0 as ZipShort.
  129. *
  130. * @since 1.1
  131. */
  132. private static final byte[] ZERO = {0, 0};
  133. /**
  134. * Helper, a 0 as ZipLong.
  135. *
  136. * @since 1.1
  137. */
  138. private static final byte[] LZERO = {0, 0, 0, 0};
  139. /**
  140. * Holds the offsets of the LFH starts for each entry.
  141. *
  142. * @since 1.1
  143. */
  144. private Hashtable offsets = new Hashtable();
  145. /**
  146. * The encoding to use for filenames and the file comment.
  147. *
  148. * <p>For a list of possible values see <a
  149. * href="http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html">http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html</a>.
  150. * Defaults to the platform's default character encoding.</p>
  151. *
  152. * @since 1.3
  153. */
  154. private String encoding = null;
  155. /**
  156. * This Deflater object is used for output.
  157. *
  158. * <p>This attribute is only protected to provide a level of API
  159. * backwards compatibility. This class used to extend {@link
  160. * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
  161. * Revision 1.13.</p>
  162. *
  163. * @since 1.14
  164. */
  165. protected Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
  166. /**
  167. * This buffer servers as a Deflater.
  168. *
  169. * <p>This attribute is only protected to provide a level of API
  170. * backwards compatibility. This class used to extend {@link
  171. * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
  172. * Revision 1.13.</p>
  173. *
  174. * @since 1.14
  175. */
  176. protected byte[] buf = new byte[512];
  177. /**
  178. * Optional random access output.
  179. *
  180. * @since 1.14
  181. */
  182. private RandomAccessFile raf = null;
  183. /**
  184. * Compression method for deflated entries.
  185. *
  186. * @since 1.1
  187. */
  188. public static final int DEFLATED = ZipEntry.DEFLATED;
  189. /**
  190. * Compression method for deflated entries.
  191. *
  192. * @since 1.1
  193. */
  194. public static final int STORED = ZipEntry.STORED;
  195. /**
  196. * Creates a new ZIP OutputStream filtering the underlying stream.
  197. *
  198. * @since 1.1
  199. */
  200. public ZipOutputStream(OutputStream out) {
  201. super(out);
  202. }
  203. /**
  204. * Creates a new ZIP OutputStream writing to a File. Will use
  205. * random access if possible.
  206. *
  207. * @since 1.14
  208. */
  209. public ZipOutputStream(File file) throws IOException {
  210. super(null);
  211. try {
  212. raf = new RandomAccessFile(file, "rw");
  213. raf.setLength(0);
  214. } catch (IOException e) {
  215. if (raf != null) {
  216. try {
  217. raf.close();
  218. } catch (IOException inner) {
  219. // ignore
  220. }
  221. raf = null;
  222. }
  223. out = new FileOutputStream(file);
  224. }
  225. }
  226. /**
  227. * This method indicates whether this archive is writing to a seekable stream (i.e., to a random
  228. * access file).
  229. *
  230. * <p>For seekable streams, you don't need to calculate the CRC or
  231. * uncompressed size for {@link #STORED} entries before
  232. * invoking {@link #putNextEntry}.
  233. *
  234. * @since 1.17
  235. */
  236. public boolean isSeekable() {
  237. return raf != null;
  238. }
  239. /**
  240. * The encoding to use for filenames and the file comment.
  241. *
  242. * <p>For a list of possible values see <a
  243. * href="http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html">http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html</a>.
  244. * Defaults to the platform's default character encoding.</p>
  245. *
  246. * @since 1.3
  247. */
  248. public void setEncoding(String encoding) {
  249. this.encoding = encoding;
  250. }
  251. /**
  252. * The encoding to use for filenames and the file comment.
  253. *
  254. * @return null if using the platform's default character encoding.
  255. *
  256. * @since 1.3
  257. */
  258. public String getEncoding() {
  259. return encoding;
  260. }
  261. /**
  262. * Finishs writing the contents and closes this as well as the
  263. * underlying stream.
  264. *
  265. * @since 1.1
  266. */
  267. public void finish() throws IOException {
  268. closeEntry();
  269. cdOffset = new ZipLong(written);
  270. for (int i = 0; i < entries.size(); i++) {
  271. writeCentralFileHeader((ZipEntry) entries.elementAt(i));
  272. }
  273. cdLength = new ZipLong(written - cdOffset.getValue());
  274. writeCentralDirectoryEnd();
  275. offsets.clear();
  276. entries.removeAllElements();
  277. }
  278. /**
  279. * Writes all necessary data for this entry.
  280. *
  281. * @since 1.1
  282. */
  283. public void closeEntry() throws IOException {
  284. if (entry == null) {
  285. return;
  286. }
  287. long realCrc = crc.getValue();
  288. crc.reset();
  289. if (entry.getMethod() == DEFLATED) {
  290. def.finish();
  291. while (!def.finished()) {
  292. deflate();
  293. }
  294. entry.setSize(def.getTotalIn());
  295. entry.setComprSize(def.getTotalOut());
  296. entry.setCrc(realCrc);
  297. def.reset();
  298. written += entry.getCompressedSize();
  299. } else if (raf == null) {
  300. if (entry.getCrc() != realCrc) {
  301. throw new ZipException("bad CRC checksum for entry "
  302. + entry.getName() + ": "
  303. + Long.toHexString(entry.getCrc())
  304. + " instead of "
  305. + Long.toHexString(realCrc));
  306. }
  307. if (entry.getSize() != written - dataStart) {
  308. throw new ZipException("bad size for entry "
  309. + entry.getName() + ": "
  310. + entry.getSize()
  311. + " instead of "
  312. + (written - dataStart));
  313. }
  314. } else { /* method is STORED and we used RandomAccessFile */
  315. long size = written - dataStart;
  316. entry.setSize(size);
  317. entry.setComprSize(size);
  318. entry.setCrc(realCrc);
  319. }
  320. // If random access output, write the local file header containing
  321. // the correct CRC and compressed/uncompressed sizes
  322. if (raf != null) {
  323. long save = raf.getFilePointer();
  324. raf.seek(localDataStart);
  325. writeOut((new ZipLong(entry.getCrc())).getBytes());
  326. writeOut((new ZipLong(entry.getCompressedSize())).getBytes());
  327. writeOut((new ZipLong(entry.getSize())).getBytes());
  328. raf.seek(save);
  329. }
  330. writeDataDescriptor(entry);
  331. entry = null;
  332. }
  333. /**
  334. * Begin writing next entry.
  335. *
  336. * @since 1.1
  337. */
  338. public void putNextEntry(ZipEntry ze) throws IOException {
  339. closeEntry();
  340. entry = ze;
  341. entries.addElement(entry);
  342. if (entry.getMethod() == -1) { // not specified
  343. entry.setMethod(method);
  344. }
  345. if (entry.getTime() == -1) { // not specified
  346. entry.setTime(System.currentTimeMillis());
  347. }
  348. // Size/CRC not required if RandomAccessFile is used
  349. if (entry.getMethod() == STORED && raf == null) {
  350. if (entry.getSize() == -1) {
  351. throw new ZipException("uncompressed size is required for"
  352. + " STORED method when not writing to a"
  353. + " file");
  354. }
  355. if (entry.getCrc() == -1) {
  356. throw new ZipException("crc checksum is required for STORED"
  357. + " method when not writing to a file");
  358. }
  359. entry.setComprSize(entry.getSize());
  360. }
  361. if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
  362. def.setLevel(level);
  363. hasCompressionLevelChanged = false;
  364. }
  365. writeLocalFileHeader(entry);
  366. }
  367. /**
  368. * Set the file comment.
  369. *
  370. * @since 1.1
  371. */
  372. public void setComment(String comment) {
  373. this.comment = comment;
  374. }
  375. /**
  376. * Sets the compression level for subsequent entries.
  377. *
  378. * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>
  379. *
  380. * @since 1.1
  381. */
  382. public void setLevel(int level) {
  383. hasCompressionLevelChanged = (this.level != level);
  384. this.level = level;
  385. }
  386. /**
  387. * Sets the default compression method for subsequent entries.
  388. *
  389. * <p>Default is DEFLATED.</p>
  390. *
  391. * @since 1.1
  392. */
  393. public void setMethod(int method) {
  394. this.method = method;
  395. }
  396. /**
  397. * Writes bytes to ZIP entry.
  398. */
  399. public void write(byte[] b, int offset, int length) throws IOException {
  400. if (entry.getMethod() == DEFLATED) {
  401. if (length > 0) {
  402. if (!def.finished()) {
  403. def.setInput(b, offset, length);
  404. while (!def.needsInput()) {
  405. deflate();
  406. }
  407. }
  408. }
  409. } else {
  410. writeOut(b, offset, length);
  411. written += length;
  412. }
  413. crc.update(b, offset, length);
  414. }
  415. /**
  416. * Writes a single byte to ZIP entry.
  417. *
  418. * <p>Delegates to the three arg method.</p>
  419. *
  420. * @since 1.14
  421. */
  422. public void write(int b) throws IOException {
  423. byte[] buf = new byte[1];
  424. buf[0] = (byte) (b & 0xff);
  425. write(buf, 0, 1);
  426. }
  427. /**
  428. * Closes this output stream and releases any system resources
  429. * associated with the stream.
  430. *
  431. * @exception IOException if an I/O error occurs.
  432. * @since 1.14
  433. */
  434. public void close() throws IOException {
  435. finish();
  436. if (raf != null) {
  437. raf.close();
  438. }
  439. if (out != null) {
  440. out.close();
  441. }
  442. }
  443. /**
  444. * Flushes this output stream and forces any buffered output bytes
  445. * to be written out to the stream.
  446. *
  447. * @exception IOException if an I/O error occurs.
  448. * @since 1.14
  449. */
  450. public void flush() throws IOException {
  451. if (out != null) {
  452. out.flush();
  453. }
  454. }
  455. /*
  456. * Various ZIP constants
  457. */
  458. /**
  459. * local file header signature
  460. *
  461. * @since 1.1
  462. */
  463. protected static final ZipLong LFH_SIG = new ZipLong(0X04034B50L);
  464. /**
  465. * data descriptor signature
  466. *
  467. * @since 1.1
  468. */
  469. protected static final ZipLong DD_SIG = new ZipLong(0X08074B50L);
  470. /**
  471. * central file header signature
  472. *
  473. * @since 1.1
  474. */
  475. protected static final ZipLong CFH_SIG = new ZipLong(0X02014B50L);
  476. /**
  477. * end of central dir signature
  478. *
  479. * @since 1.1
  480. */
  481. protected static final ZipLong EOCD_SIG = new ZipLong(0X06054B50L);
  482. /**
  483. * Writes next block of compressed data to the output stream.
  484. *
  485. * @since 1.14
  486. */
  487. protected final void deflate() throws IOException {
  488. int len = def.deflate(buf, 0, buf.length);
  489. if (len > 0) {
  490. writeOut(buf, 0, len);
  491. }
  492. }
  493. /**
  494. * Writes the local file header entry
  495. *
  496. * @since 1.1
  497. */
  498. protected void writeLocalFileHeader(ZipEntry ze) throws IOException {
  499. offsets.put(ze, new ZipLong(written));
  500. writeOut(LFH_SIG.getBytes());
  501. written += 4;
  502. // version needed to extract
  503. // general purpose bit flag
  504. if (ze.getMethod() == DEFLATED && raf == null) {
  505. // requires version 2 as we are going to store length info
  506. // in the data descriptor
  507. writeOut((new ZipShort(20)).getBytes());
  508. // bit3 set to signal, we use a data descriptor
  509. writeOut((new ZipShort(8)).getBytes());
  510. } else {
  511. writeOut((new ZipShort(10)).getBytes());
  512. writeOut(ZERO);
  513. }
  514. written += 4;
  515. // compression method
  516. writeOut((new ZipShort(ze.getMethod())).getBytes());
  517. written += 2;
  518. // last mod. time and date
  519. writeOut(toDosTime(new Date(ze.getTime())).getBytes());
  520. written += 4;
  521. // CRC
  522. // compressed length
  523. // uncompressed length
  524. localDataStart = written;
  525. if (ze.getMethod() == DEFLATED || raf != null) {
  526. writeOut(LZERO);
  527. writeOut(LZERO);
  528. writeOut(LZERO);
  529. } else {
  530. writeOut((new ZipLong(ze.getCrc())).getBytes());
  531. writeOut((new ZipLong(ze.getSize())).getBytes());
  532. writeOut((new ZipLong(ze.getSize())).getBytes());
  533. }
  534. written += 12;
  535. // file name length
  536. byte[] name = getBytes(ze.getName());
  537. writeOut((new ZipShort(name.length)).getBytes());
  538. written += 2;
  539. // extra field length
  540. byte[] extra = ze.getLocalFileDataExtra();
  541. writeOut((new ZipShort(extra.length)).getBytes());
  542. written += 2;
  543. // file name
  544. writeOut(name);
  545. written += name.length;
  546. // extra field
  547. writeOut(extra);
  548. written += extra.length;
  549. dataStart = written;
  550. }
  551. /**
  552. * Writes the data descriptor entry
  553. *
  554. * @since 1.1
  555. */
  556. protected void writeDataDescriptor(ZipEntry ze) throws IOException {
  557. if (ze.getMethod() != DEFLATED || raf != null) {
  558. return;
  559. }
  560. writeOut(DD_SIG.getBytes());
  561. writeOut((new ZipLong(entry.getCrc())).getBytes());
  562. writeOut((new ZipLong(entry.getCompressedSize())).getBytes());
  563. writeOut((new ZipLong(entry.getSize())).getBytes());
  564. written += 16;
  565. }
  566. /**
  567. * Writes the central file header entry
  568. *
  569. * @since 1.1
  570. */
  571. protected void writeCentralFileHeader(ZipEntry ze) throws IOException {
  572. writeOut(CFH_SIG.getBytes());
  573. written += 4;
  574. // version made by
  575. writeOut((new ZipShort((ze.getPlatform() << 8) | 20)).getBytes());
  576. written += 2;
  577. // version needed to extract
  578. // general purpose bit flag
  579. if (ze.getMethod() == DEFLATED && raf == null) {
  580. // requires version 2 as we are going to store length info
  581. // in the data descriptor
  582. writeOut((new ZipShort(20)).getBytes());
  583. // bit3 set to signal, we use a data descriptor
  584. writeOut((new ZipShort(8)).getBytes());
  585. } else {
  586. writeOut((new ZipShort(10)).getBytes());
  587. writeOut(ZERO);
  588. }
  589. written += 4;
  590. // compression method
  591. writeOut((new ZipShort(ze.getMethod())).getBytes());
  592. written += 2;
  593. // last mod. time and date
  594. writeOut(toDosTime(new Date(ze.getTime())).getBytes());
  595. written += 4;
  596. // CRC
  597. // compressed length
  598. // uncompressed length
  599. writeOut((new ZipLong(ze.getCrc())).getBytes());
  600. writeOut((new ZipLong(ze.getCompressedSize())).getBytes());
  601. writeOut((new ZipLong(ze.getSize())).getBytes());
  602. written += 12;
  603. // file name length
  604. byte[] name = getBytes(ze.getName());
  605. writeOut((new ZipShort(name.length)).getBytes());
  606. written += 2;
  607. // extra field length
  608. byte[] extra = ze.getCentralDirectoryExtra();
  609. writeOut((new ZipShort(extra.length)).getBytes());
  610. written += 2;
  611. // file comment length
  612. String comm = ze.getComment();
  613. if (comm == null) {
  614. comm = "";
  615. }
  616. byte[] comment = getBytes(comm);
  617. writeOut((new ZipShort(comment.length)).getBytes());
  618. written += 2;
  619. // disk number start
  620. writeOut(ZERO);
  621. written += 2;
  622. // internal file attributes
  623. writeOut((new ZipShort(ze.getInternalAttributes())).getBytes());
  624. written += 2;
  625. // external file attributes
  626. writeOut((new ZipLong(ze.getExternalAttributes())).getBytes());
  627. written += 4;
  628. // relative offset of LFH
  629. writeOut(((ZipLong) offsets.get(ze)).getBytes());
  630. written += 4;
  631. // file name
  632. writeOut(name);
  633. written += name.length;
  634. // extra field
  635. writeOut(extra);
  636. written += extra.length;
  637. // file comment
  638. writeOut(comment);
  639. written += comment.length;
  640. }
  641. /**
  642. * Writes the "End of central dir record"
  643. *
  644. * @since 1.1
  645. */
  646. protected void writeCentralDirectoryEnd() throws IOException {
  647. writeOut(EOCD_SIG.getBytes());
  648. // disk numbers
  649. writeOut(ZERO);
  650. writeOut(ZERO);
  651. // number of entries
  652. byte[] num = (new ZipShort(entries.size())).getBytes();
  653. writeOut(num);
  654. writeOut(num);
  655. // length and location of CD
  656. writeOut(cdLength.getBytes());
  657. writeOut(cdOffset.getBytes());
  658. // ZIP file comment
  659. byte[] data = getBytes(comment);
  660. writeOut((new ZipShort(data.length)).getBytes());
  661. writeOut(data);
  662. }
  663. /**
  664. * Smallest date/time ZIP can handle.
  665. *
  666. * @since 1.1
  667. */
  668. private static final ZipLong DOS_TIME_MIN = new ZipLong(0x00002100L);
  669. /**
  670. * Convert a Date object to a DOS date/time field.
  671. *
  672. * <p>Stolen from InfoZip's <code>fileio.c</code></p>
  673. *
  674. * @since 1.1
  675. */
  676. protected static ZipLong toDosTime(Date time) {
  677. int year = time.getYear() + 1900;
  678. int month = time.getMonth() + 1;
  679. if (year < 1980) {
  680. return DOS_TIME_MIN;
  681. }
  682. long value = ((year - 1980) << 25)
  683. | (month << 21)
  684. | (time.getDate() << 16)
  685. | (time.getHours() << 11)
  686. | (time.getMinutes() << 5)
  687. | (time.getSeconds() >> 1);
  688. byte[] result = new byte[4];
  689. result[0] = (byte) ((value & 0xFF));
  690. result[1] = (byte) ((value & 0xFF00) >> 8);
  691. result[2] = (byte) ((value & 0xFF0000) >> 16);
  692. result[3] = (byte) ((value & 0xFF000000L) >> 24);
  693. return new ZipLong(result);
  694. }
  695. /**
  696. * Retrieve the bytes for the given String in the encoding set for
  697. * this Stream.
  698. *
  699. * @since 1.3
  700. */
  701. protected byte[] getBytes(String name) throws ZipException {
  702. if (encoding == null) {
  703. return name.getBytes();
  704. } else {
  705. try {
  706. return name.getBytes(encoding);
  707. } catch (UnsupportedEncodingException uee) {
  708. throw new ZipException(uee.getMessage());
  709. }
  710. }
  711. }
  712. /**
  713. * Write bytes to output or random access file
  714. *
  715. * @since 1.14
  716. */
  717. protected final void writeOut(byte [] data) throws IOException {
  718. writeOut(data, 0, data.length);
  719. }
  720. /**
  721. * Write bytes to output or random access file
  722. *
  723. * @since 1.14
  724. */
  725. protected final void writeOut(byte [] data, int offset, int length)
  726. throws IOException {
  727. if (raf != null) {
  728. raf.write(data, offset, length);
  729. } else {
  730. out.write(data, offset, length);
  731. }
  732. }
  733. }