001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 */
018 package org.apache.commons.compress.archivers.zip;
019
020 import java.io.File;
021 import java.io.FileOutputStream;
022 import java.io.IOException;
023 import java.io.OutputStream;
024 import java.io.RandomAccessFile;
025 import java.nio.ByteBuffer;
026 import java.util.HashMap;
027 import java.util.Iterator;
028 import java.util.LinkedList;
029 import java.util.List;
030 import java.util.Map;
031 import java.util.zip.CRC32;
032 import java.util.zip.Deflater;
033 import java.util.zip.ZipException;
034
035 import org.apache.commons.compress.archivers.ArchiveEntry;
036 import org.apache.commons.compress.archivers.ArchiveOutputStream;
037
038 /**
039 * Reimplementation of {@link java.util.zip.ZipOutputStream
040 * java.util.zip.ZipOutputStream} that does handle the extended
041 * functionality of this package, especially internal/external file
042 * attributes and extra fields with different layouts for local file
043 * data and central directory entries.
044 *
045 * <p>This class will try to use {@link java.io.RandomAccessFile
046 * RandomAccessFile} when you know that the output is going to go to a
047 * file.</p>
048 *
049 * <p>If RandomAccessFile cannot be used, this implementation will use
050 * a Data Descriptor to store size and CRC information for {@link
051 * #DEFLATED DEFLATED} entries, this means, you don't need to
052 * calculate them yourself. Unfortunately this is not possible for
053 * the {@link #STORED STORED} method, here setting the CRC and
054 * uncompressed size information is required before {@link
055 * #putArchiveEntry(ArchiveEntry)} can be called.</p>
056 * @NotThreadSafe
057 */
058 public class ZipArchiveOutputStream extends ArchiveOutputStream {
059
060 static final int BYTE_MASK = 0xFF;
061 private static final int SHORT = 2;
062 private static final int WORD = 4;
063 static final int BUFFER_SIZE = 512;
064
065 /** indicates if this archive is finished. protected for use in Jar implementation */
066 protected boolean finished = false;
067
068 /*
069 * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs
070 * when it gets handed a really big buffer. See
071 * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396
072 *
073 * Using a buffer size of 8 kB proved to be a good compromise
074 */
075 private static final int DEFLATER_BLOCK_SIZE = 8192;
076
077 /**
078 * Compression method for deflated entries.
079 */
080 public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
081
082 /**
083 * Default compression level for deflated entries.
084 */
085 public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
086
087 /**
088 * Compression method for stored entries.
089 */
090 public static final int STORED = java.util.zip.ZipEntry.STORED;
091
092 /**
093 * default encoding for file names and comment.
094 */
095 static final String DEFAULT_ENCODING = ZipEncodingHelper.UTF8;
096
097 /**
098 * General purpose flag, which indicates that filenames are
099 * written in utf-8.
100 */
101 public static final int EFS_FLAG = 1 << 11;
102
103 /**
104 * Current entry.
105 */
106 private ZipArchiveEntry entry;
107
108 /**
109 * The file comment.
110 */
111 private String comment = "";
112
113 /**
114 * Compression level for next entry.
115 */
116 private int level = DEFAULT_COMPRESSION;
117
118 /**
119 * Has the compression level changed when compared to the last
120 * entry?
121 */
122 private boolean hasCompressionLevelChanged = false;
123
124 /**
125 * Default compression method for next entry.
126 */
127 private int method = java.util.zip.ZipEntry.DEFLATED;
128
129 /**
130 * List of ZipArchiveEntries written so far.
131 */
132 private final List entries = new LinkedList();
133
134 /**
135 * CRC instance to avoid parsing DEFLATED data twice.
136 */
137 private final CRC32 crc = new CRC32();
138
139 /**
140 * Count the bytes written to out.
141 */
142 private long written = 0;
143
144 /**
145 * Data for local header data
146 */
147 private long dataStart = 0;
148
149 /**
150 * Offset for CRC entry in the local file header data for the
151 * current entry starts here.
152 */
153 private long localDataStart = 0;
154
155 /**
156 * Start of central directory.
157 */
158 private long cdOffset = 0;
159
160 /**
161 * Length of central directory.
162 */
163 private long cdLength = 0;
164
165 /**
166 * Helper, a 0 as ZipShort.
167 */
168 private static final byte[] ZERO = {0, 0};
169
170 /**
171 * Helper, a 0 as ZipLong.
172 */
173 private static final byte[] LZERO = {0, 0, 0, 0};
174
175 /**
176 * Holds the offsets of the LFH starts for each entry.
177 */
178 private final Map offsets = new HashMap();
179
180 /**
181 * The encoding to use for filenames and the file comment.
182 *
183 * <p>For a list of possible values see <a
184 * 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>.
185 * Defaults to UTF-8.</p>
186 */
187 private String encoding = DEFAULT_ENCODING;
188
189 /**
190 * The zip encoding to use for filenames and the file comment.
191 *
192 * This field is of internal use and will be set in {@link
193 * #setEncoding(String)}.
194 */
195 private ZipEncoding zipEncoding =
196 ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING);
197
198 /**
199 * This Deflater object is used for output.
200 *
201 */
202 protected final Deflater def = new Deflater(level, true);
203
204 /**
205 * This buffer servers as a Deflater.
206 *
207 */
208 private final byte[] buf = new byte[BUFFER_SIZE];
209
210 /**
211 * Optional random access output.
212 */
213 private final RandomAccessFile raf;
214
215 private final OutputStream out;
216
217 /**
218 * whether to use the EFS flag when writing UTF-8 filenames or not.
219 */
220 private boolean useEFS = true;
221
222 /**
223 * Whether to encode non-encodable file names as UTF-8.
224 */
225 private boolean fallbackToUTF8 = false;
226
227 /**
228 * whether to create UnicodePathExtraField-s for each entry.
229 */
230 private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER;
231
232 /**
233 * Creates a new ZIP OutputStream filtering the underlying stream.
234 * @param out the outputstream to zip
235 */
236 public ZipArchiveOutputStream(OutputStream out) {
237 this.out = out;
238 this.raf = null;
239 }
240
241 /**
242 * Creates a new ZIP OutputStream writing to a File. Will use
243 * random access if possible.
244 * @param file the file to zip to
245 * @throws IOException on error
246 */
247 public ZipArchiveOutputStream(File file) throws IOException {
248 OutputStream o = null;
249 RandomAccessFile _raf = null;
250 try {
251 _raf = new RandomAccessFile(file, "rw");
252 _raf.setLength(0);
253 } catch (IOException e) {
254 if (_raf != null) {
255 try {
256 _raf.close();
257 } catch (IOException inner) {
258 // ignore
259 }
260 _raf = null;
261 }
262 o = new FileOutputStream(file);
263 }
264 out = o;
265 raf = _raf;
266 }
267
268 /**
269 * This method indicates whether this archive is writing to a
270 * seekable stream (i.e., to a random access file).
271 *
272 * <p>For seekable streams, you don't need to calculate the CRC or
273 * uncompressed size for {@link #STORED} entries before
274 * invoking {@link #putArchiveEntry(ArchiveEntry)}.
275 * @return true if seekable
276 */
277 public boolean isSeekable() {
278 return raf != null;
279 }
280
281 /**
282 * The encoding to use for filenames and the file comment.
283 *
284 * <p>For a list of possible values see <a
285 * 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>.
286 * Defaults to UTF-8.</p>
287 * @param encoding the encoding to use for file names, use null
288 * for the platform's default encoding
289 */
290 public void setEncoding(final String encoding) {
291 this.encoding = encoding;
292 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
293 useEFS &= ZipEncodingHelper.isUTF8(encoding);
294 }
295
296 /**
297 * The encoding to use for filenames and the file comment.
298 *
299 * @return null if using the platform's default character encoding.
300 */
301 public String getEncoding() {
302 return encoding;
303 }
304
305 /**
306 * Whether to set the language encoding flag if the file name
307 * encoding is UTF-8.
308 *
309 * <p>Defaults to true.</p>
310 */
311 public void setUseLanguageEncodingFlag(boolean b) {
312 useEFS = b && ZipEncodingHelper.isUTF8(encoding);
313 }
314
315 /**
316 * Whether to create Unicode Extra Fields.
317 *
318 * <p>Defaults to NEVER.</p>
319 */
320 public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) {
321 createUnicodeExtraFields = b;
322 }
323
324 /**
325 * Whether to fall back to UTF and the language encoding flag if
326 * the file name cannot be encoded using the specified encoding.
327 *
328 * <p>Defaults to false.</p>
329 */
330 public void setFallbackToUTF8(boolean b) {
331 fallbackToUTF8 = b;
332 }
333
334 /* (non-Javadoc)
335 * @see org.apache.commons.compress.archivers.ArchiveOutputStream#finish()
336 */
337 public void finish() throws IOException {
338 if (finished) {
339 throw new IOException("This archive has already been finished");
340 }
341
342 if(entry != null) {
343 throw new IOException("This archives contains unclosed entries.");
344 }
345
346 cdOffset = written;
347 for (Iterator i = entries.iterator(); i.hasNext(); ) {
348 writeCentralFileHeader((ZipArchiveEntry) i.next());
349 }
350 cdLength = written - cdOffset;
351 writeCentralDirectoryEnd();
352 offsets.clear();
353 entries.clear();
354 finished = true;
355 }
356
357 /**
358 * Writes all necessary data for this entry.
359 * @throws IOException on error
360 */
361 public void closeArchiveEntry() throws IOException {
362 if(finished) {
363 throw new IOException("Stream has already been finished");
364 }
365
366 if (entry == null) {
367 throw new IOException("No current entry to close");
368 }
369
370 long realCrc = crc.getValue();
371 crc.reset();
372
373 if (entry.getMethod() == DEFLATED) {
374 def.finish();
375 while (!def.finished()) {
376 deflate();
377 }
378
379 entry.setSize(ZipUtil.adjustToLong(def.getTotalIn()));
380 entry.setCompressedSize(ZipUtil.adjustToLong(def.getTotalOut()));
381 entry.setCrc(realCrc);
382
383 def.reset();
384
385 written += entry.getCompressedSize();
386 } else if (raf == null) {
387 if (entry.getCrc() != realCrc) {
388 throw new ZipException("bad CRC checksum for entry "
389 + entry.getName() + ": "
390 + Long.toHexString(entry.getCrc())
391 + " instead of "
392 + Long.toHexString(realCrc));
393 }
394
395 if (entry.getSize() != written - dataStart) {
396 throw new ZipException("bad size for entry "
397 + entry.getName() + ": "
398 + entry.getSize()
399 + " instead of "
400 + (written - dataStart));
401 }
402 } else { /* method is STORED and we used RandomAccessFile */
403 long size = written - dataStart;
404
405 entry.setSize(size);
406 entry.setCompressedSize(size);
407 entry.setCrc(realCrc);
408 }
409
410 // If random access output, write the local file header containing
411 // the correct CRC and compressed/uncompressed sizes
412 if (raf != null) {
413 long save = raf.getFilePointer();
414
415 raf.seek(localDataStart);
416 writeOut(ZipLong.getBytes(entry.getCrc()));
417 writeOut(ZipLong.getBytes(entry.getCompressedSize()));
418 writeOut(ZipLong.getBytes(entry.getSize()));
419 raf.seek(save);
420 }
421
422 writeDataDescriptor(entry);
423 entry = null;
424 }
425
426 /** {@inheritDoc} */
427 // @throws ClassCastException if entry is not an instance of ZipArchiveEntry
428 public void putArchiveEntry(ArchiveEntry archiveEntry) throws IOException {
429 if(finished) {
430 throw new IOException("Stream has already been finished");
431 }
432
433 if (entry != null) {
434 closeArchiveEntry();
435 }
436
437 entry = ((ZipArchiveEntry) archiveEntry);
438 entries.add(entry);
439
440 if (entry.getMethod() == -1) { // not specified
441 entry.setMethod(method);
442 }
443
444 if (entry.getTime() == -1) { // not specified
445 entry.setTime(System.currentTimeMillis());
446 }
447
448 // Size/CRC not required if RandomAccessFile is used
449 if (entry.getMethod() == STORED && raf == null) {
450 if (entry.getSize() == -1) {
451 throw new ZipException("uncompressed size is required for"
452 + " STORED method when not writing to a"
453 + " file");
454 }
455 if (entry.getCrc() == -1) {
456 throw new ZipException("crc checksum is required for STORED"
457 + " method when not writing to a file");
458 }
459 entry.setCompressedSize(entry.getSize());
460 }
461
462 if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
463 def.setLevel(level);
464 hasCompressionLevelChanged = false;
465 }
466 writeLocalFileHeader(entry);
467 }
468
469 /**
470 * Set the file comment.
471 * @param comment the comment
472 */
473 public void setComment(String comment) {
474 this.comment = comment;
475 }
476
477 /**
478 * Sets the compression level for subsequent entries.
479 *
480 * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>
481 * @param level the compression level.
482 * @throws IllegalArgumentException if an invalid compression
483 * level is specified.
484 */
485 public void setLevel(int level) {
486 if (level < Deflater.DEFAULT_COMPRESSION
487 || level > Deflater.BEST_COMPRESSION) {
488 throw new IllegalArgumentException("Invalid compression level: "
489 + level);
490 }
491 hasCompressionLevelChanged = (this.level != level);
492 this.level = level;
493 }
494
495 /**
496 * Sets the default compression method for subsequent entries.
497 *
498 * <p>Default is DEFLATED.</p>
499 * @param method an <code>int</code> from java.util.zip.ZipEntry
500 */
501 public void setMethod(int method) {
502 this.method = method;
503 }
504
505 /**
506 * Writes bytes to ZIP entry.
507 * @param b the byte array to write
508 * @param offset the start position to write from
509 * @param length the number of bytes to write
510 * @throws IOException on error
511 */
512 public void write(byte[] b, int offset, int length) throws IOException {
513 if (entry.getMethod() == DEFLATED) {
514 if (length > 0) {
515 if (!def.finished()) {
516 if (length <= DEFLATER_BLOCK_SIZE) {
517 def.setInput(b, offset, length);
518 deflateUntilInputIsNeeded();
519 } else {
520 final int fullblocks = length / DEFLATER_BLOCK_SIZE;
521 for (int i = 0; i < fullblocks; i++) {
522 def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE,
523 DEFLATER_BLOCK_SIZE);
524 deflateUntilInputIsNeeded();
525 }
526 final int done = fullblocks * DEFLATER_BLOCK_SIZE;
527 if (done < length) {
528 def.setInput(b, offset + done, length - done);
529 deflateUntilInputIsNeeded();
530 }
531 }
532 }
533 }
534 } else {
535 writeOut(b, offset, length);
536 written += length;
537 }
538 crc.update(b, offset, length);
539 count(length);
540 }
541
542 /**
543 * Closes this output stream and releases any system resources
544 * associated with the stream.
545 *
546 * @exception IOException if an I/O error occurs.
547 */
548 public void close() throws IOException {
549 if(!finished) {
550 finish();
551 }
552
553 if (raf != null) {
554 raf.close();
555 }
556 if (out != null) {
557 out.close();
558 }
559 }
560
561 /**
562 * Flushes this output stream and forces any buffered output bytes
563 * to be written out to the stream.
564 *
565 * @exception IOException if an I/O error occurs.
566 */
567 public void flush() throws IOException {
568 if (out != null) {
569 out.flush();
570 }
571 }
572
573 /*
574 * Various ZIP constants
575 */
576 /**
577 * local file header signature
578 */
579 static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes();
580 /**
581 * data descriptor signature
582 */
583 static final byte[] DD_SIG = ZipLong.getBytes(0X08074B50L);
584 /**
585 * central file header signature
586 */
587 static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes();
588 /**
589 * end of central dir signature
590 */
591 static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
592
593 /**
594 * Writes next block of compressed data to the output stream.
595 * @throws IOException on error
596 */
597 protected final void deflate() throws IOException {
598 int len = def.deflate(buf, 0, buf.length);
599 if (len > 0) {
600 writeOut(buf, 0, len);
601 }
602 }
603
604 /**
605 * Writes the local file header entry
606 * @param ze the entry to write
607 * @throws IOException on error
608 */
609 protected void writeLocalFileHeader(ZipArchiveEntry ze) throws IOException {
610
611 boolean encodable = zipEncoding.canEncode(ze.getName());
612
613 final ZipEncoding entryEncoding;
614
615 if (!encodable && fallbackToUTF8) {
616 entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;
617 } else {
618 entryEncoding = zipEncoding;
619 }
620
621 ByteBuffer name = entryEncoding.encode(ze.getName());
622
623 if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
624
625 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
626 || !encodable) {
627 ze.addExtraField(new UnicodePathExtraField(ze.getName(),
628 name.array(),
629 name.arrayOffset(),
630 name.limit()));
631 }
632
633 String comm = ze.getComment();
634 if (comm != null && !"".equals(comm)) {
635
636 boolean commentEncodable = this.zipEncoding.canEncode(comm);
637
638 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
639 || !commentEncodable) {
640 ByteBuffer commentB = entryEncoding.encode(comm);
641 ze.addExtraField(new UnicodeCommentExtraField(comm,
642 commentB.array(),
643 commentB.arrayOffset(),
644 commentB.limit())
645 );
646 }
647 }
648 }
649
650 offsets.put(ze, ZipLong.getBytes(written));
651
652 writeOut(LFH_SIG);
653 written += WORD;
654
655 //store method in local variable to prevent multiple method calls
656 final int zipMethod = ze.getMethod();
657
658 writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
659 !encodable
660 && fallbackToUTF8);
661 written += WORD;
662
663 // compression method
664 writeOut(ZipShort.getBytes(zipMethod));
665 written += SHORT;
666
667 // last mod. time and date
668 writeOut(ZipUtil.toDosTime(ze.getTime()));
669 written += WORD;
670
671 // CRC
672 // compressed length
673 // uncompressed length
674 localDataStart = written;
675 if (zipMethod == DEFLATED || raf != null) {
676 writeOut(LZERO);
677 writeOut(LZERO);
678 writeOut(LZERO);
679 } else {
680 writeOut(ZipLong.getBytes(ze.getCrc()));
681 writeOut(ZipLong.getBytes(ze.getSize()));
682 writeOut(ZipLong.getBytes(ze.getSize()));
683 }
684 // CheckStyle:MagicNumber OFF
685 written += 12;
686 // CheckStyle:MagicNumber ON
687
688 // file name length
689 writeOut(ZipShort.getBytes(name.limit()));
690 written += SHORT;
691
692 // extra field length
693 byte[] extra = ze.getLocalFileDataExtra();
694 writeOut(ZipShort.getBytes(extra.length));
695 written += SHORT;
696
697 // file name
698 writeOut(name.array(), name.arrayOffset(), name.limit());
699 written += name.limit();
700
701 // extra field
702 writeOut(extra);
703 written += extra.length;
704
705 dataStart = written;
706 }
707
708 /**
709 * Writes the data descriptor entry.
710 * @param ze the entry to write
711 * @throws IOException on error
712 */
713 protected void writeDataDescriptor(ZipArchiveEntry ze) throws IOException {
714 if (ze.getMethod() != DEFLATED || raf != null) {
715 return;
716 }
717 writeOut(DD_SIG);
718 writeOut(ZipLong.getBytes(entry.getCrc()));
719 writeOut(ZipLong.getBytes(entry.getCompressedSize()));
720 writeOut(ZipLong.getBytes(entry.getSize()));
721 // CheckStyle:MagicNumber OFF
722 written += 16;
723 // CheckStyle:MagicNumber ON
724 }
725
726 /**
727 * Writes the central file header entry.
728 * @param ze the entry to write
729 * @throws IOException on error
730 */
731 protected void writeCentralFileHeader(ZipArchiveEntry ze) throws IOException {
732 writeOut(CFH_SIG);
733 written += WORD;
734
735 // version made by
736 // CheckStyle:MagicNumber OFF
737 writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));
738 written += SHORT;
739
740 final int zipMethod = ze.getMethod();
741 final boolean encodable = zipEncoding.canEncode(ze.getName());
742 writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
743 !encodable
744 && fallbackToUTF8);
745 written += WORD;
746
747 // compression method
748 writeOut(ZipShort.getBytes(zipMethod));
749 written += SHORT;
750
751 // last mod. time and date
752 writeOut(ZipUtil.toDosTime(ze.getTime()));
753 written += WORD;
754
755 // CRC
756 // compressed length
757 // uncompressed length
758 writeOut(ZipLong.getBytes(ze.getCrc()));
759 writeOut(ZipLong.getBytes(ze.getCompressedSize()));
760 writeOut(ZipLong.getBytes(ze.getSize()));
761 // CheckStyle:MagicNumber OFF
762 written += 12;
763 // CheckStyle:MagicNumber ON
764
765 // file name length
766 final ZipEncoding entryEncoding;
767
768 if (!encodable && fallbackToUTF8) {
769 entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;
770 } else {
771 entryEncoding = zipEncoding;
772 }
773
774 ByteBuffer name = entryEncoding.encode(ze.getName());
775
776 writeOut(ZipShort.getBytes(name.limit()));
777 written += SHORT;
778
779 // extra field length
780 byte[] extra = ze.getCentralDirectoryExtra();
781 writeOut(ZipShort.getBytes(extra.length));
782 written += SHORT;
783
784 // file comment length
785 String comm = ze.getComment();
786 if (comm == null) {
787 comm = "";
788 }
789
790 ByteBuffer commentB = entryEncoding.encode(comm);
791
792 writeOut(ZipShort.getBytes(commentB.limit()));
793 written += SHORT;
794
795 // disk number start
796 writeOut(ZERO);
797 written += SHORT;
798
799 // internal file attributes
800 writeOut(ZipShort.getBytes(ze.getInternalAttributes()));
801 written += SHORT;
802
803 // external file attributes
804 writeOut(ZipLong.getBytes(ze.getExternalAttributes()));
805 written += WORD;
806
807 // relative offset of LFH
808 writeOut((byte[]) offsets.get(ze));
809 written += WORD;
810
811 // file name
812 writeOut(name.array(), name.arrayOffset(), name.limit());
813 written += name.limit();
814
815 // extra field
816 writeOut(extra);
817 written += extra.length;
818
819 // file comment
820 writeOut(commentB.array(), commentB.arrayOffset(), commentB.limit());
821 written += commentB.limit();
822 }
823
824 /**
825 * Writes the "End of central dir record".
826 * @throws IOException on error
827 */
828 protected void writeCentralDirectoryEnd() throws IOException {
829 writeOut(EOCD_SIG);
830
831 // disk numbers
832 writeOut(ZERO);
833 writeOut(ZERO);
834
835 // number of entries
836 byte[] num = ZipShort.getBytes(entries.size());
837 writeOut(num);
838 writeOut(num);
839
840 // length and location of CD
841 writeOut(ZipLong.getBytes(cdLength));
842 writeOut(ZipLong.getBytes(cdOffset));
843
844 // ZIP file comment
845 ByteBuffer data = this.zipEncoding.encode(comment);
846 writeOut(ZipShort.getBytes(data.limit()));
847 writeOut(data.array(), data.arrayOffset(), data.limit());
848 }
849
850 /**
851 * Write bytes to output or random access file.
852 * @param data the byte array to write
853 * @throws IOException on error
854 */
855 protected final void writeOut(byte[] data) throws IOException {
856 writeOut(data, 0, data.length);
857 }
858
859 /**
860 * Write bytes to output or random access file.
861 * @param data the byte array to write
862 * @param offset the start position to write from
863 * @param length the number of bytes to write
864 * @throws IOException on error
865 */
866 protected final void writeOut(byte[] data, int offset, int length)
867 throws IOException {
868 if (raf != null) {
869 raf.write(data, offset, length);
870 } else {
871 out.write(data, offset, length);
872 }
873 }
874
875 private void deflateUntilInputIsNeeded() throws IOException {
876 while (!def.needsInput()) {
877 deflate();
878 }
879 }
880
881 private void writeVersionNeededToExtractAndGeneralPurposeBits(final int
882 zipMethod,
883 final boolean
884 utfFallback)
885 throws IOException {
886
887 // CheckStyle:MagicNumber OFF
888 int versionNeededToExtract = 10;
889 int generalPurposeFlag = (useEFS || utfFallback) ? EFS_FLAG : 0;
890 if (zipMethod == DEFLATED && raf == null) {
891 // requires version 2 as we are going to store length info
892 // in the data descriptor
893 versionNeededToExtract = 20;
894 // bit3 set to signal, we use a data descriptor
895 generalPurposeFlag |= 8;
896 }
897 // CheckStyle:MagicNumber ON
898
899 // version needed to extract
900 writeOut(ZipShort.getBytes(versionNeededToExtract));
901 // general purpose bit flag
902 writeOut(ZipShort.getBytes(generalPurposeFlag));
903 }
904
905 /**
906 * enum that represents the possible policies for creating Unicode
907 * extra fields.
908 */
909 public static final class UnicodeExtraFieldPolicy {
910 /**
911 * Always create Unicode extra fields.
912 */
913 public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always");
914 /**
915 * Never create Unicode extra fields.
916 */
917 public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never");
918 /**
919 * Create Unicode extra fields for filenames that cannot be
920 * encoded using the specified encoding.
921 */
922 public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE =
923 new UnicodeExtraFieldPolicy("not encodeable");
924
925 private final String name;
926 private UnicodeExtraFieldPolicy(String n) {
927 name = n;
928 }
929 public String toString() {
930 return name;
931 }
932 }
933
934 public ArchiveEntry createArchiveEntry(File inputFile, String entryName)
935 throws IOException {
936 if(finished) {
937 throw new IOException("Stream has already been finished");
938 }
939 return new ZipArchiveEntry(inputFile, entryName);
940 }
941 }