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.commons.compress.archivers.zip;
19
20 import java.io.File;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.io.RandomAccessFile;
25 import java.nio.ByteBuffer;
26 import java.util.HashMap;
27 import java.util.LinkedList;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.zip.CRC32;
31 import java.util.zip.Deflater;
32 import java.util.zip.ZipException;
33
34 import org.apache.commons.compress.archivers.ArchiveEntry;
35 import org.apache.commons.compress.archivers.ArchiveOutputStream;
36
37 import static org.apache.commons.compress.archivers.zip.ZipConstants.DATA_DESCRIPTOR_MIN_VERSION;
38 import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
39 import static org.apache.commons.compress.archivers.zip.ZipConstants.INITIAL_VERSION;
40 import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT;
41 import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
42 import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC;
43 import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC_SHORT;
44 import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MIN_VERSION;
45
46 /**
47 * Reimplementation of {@link java.util.zip.ZipOutputStream
48 * java.util.zip.ZipOutputStream} that does handle the extended
49 * functionality of this package, especially internal/external file
50 * attributes and extra fields with different layouts for local file
51 * data and central directory entries.
52 *
53 * <p>This class will try to use {@link java.io.RandomAccessFile
54 * RandomAccessFile} when you know that the output is going to go to a
55 * file.</p>
56 *
57 * <p>If RandomAccessFile cannot be used, this implementation will use
58 * a Data Descriptor to store size and CRC information for {@link
59 * #DEFLATED DEFLATED} entries, this means, you don't need to
60 * calculate them yourself. Unfortunately this is not possible for
61 * the {@link #STORED STORED} method, here setting the CRC and
62 * uncompressed size information is required before {@link
63 * #putArchiveEntry(ArchiveEntry)} can be called.</p>
64 *
65 * <p>As of Apache Commons Compress 1.3 it transparently supports Zip64
66 * extensions and thus individual entries and archives larger than 4
67 * GB or with more than 65536 entries in most cases but explicit
68 * control is provided via {@link #setUseZip64}. If the stream can not
69 * user RandomAccessFile and you try to write a ZipArchiveEntry of
70 * unknown size then Zip64 extensions will be disabled by default.</p>
71 *
72 * @NotThreadSafe
73 */
74 public class ZipArchiveOutputStream extends ArchiveOutputStream {
75
76 static final int BUFFER_SIZE = 512;
77
78 /** indicates if this archive is finished. protected for use in Jar implementation */
79 protected boolean finished = false;
80
81 /*
82 * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs
83 * when it gets handed a really big buffer. See
84 * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396
85 *
86 * Using a buffer size of 8 kB proved to be a good compromise
87 */
88 private static final int DEFLATER_BLOCK_SIZE = 8192;
89
90 /**
91 * Compression method for deflated entries.
92 */
93 public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
94
95 /**
96 * Default compression level for deflated entries.
97 */
98 public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
99
100 /**
101 * Compression method for stored entries.
102 */
103 public static final int STORED = java.util.zip.ZipEntry.STORED;
104
105 /**
106 * default encoding for file names and comment.
107 */
108 static final String DEFAULT_ENCODING = ZipEncodingHelper.UTF8;
109
110 /**
111 * General purpose flag, which indicates that filenames are
112 * written in utf-8.
113 * @deprecated use {@link GeneralPurposeBit#UFT8_NAMES_FLAG} instead
114 */
115 @Deprecated
116 public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG;
117
118 private static final byte[] EMPTY = new byte[0];
119
120 /**
121 * Current entry.
122 */
123 private CurrentEntry entry;
124
125 /**
126 * The file comment.
127 */
128 private String comment = "";
129
130 /**
131 * Compression level for next entry.
132 */
133 private int level = DEFAULT_COMPRESSION;
134
135 /**
136 * Has the compression level changed when compared to the last
137 * entry?
138 */
139 private boolean hasCompressionLevelChanged = false;
140
141 /**
142 * Default compression method for next entry.
143 */
144 private int method = java.util.zip.ZipEntry.DEFLATED;
145
146 /**
147 * List of ZipArchiveEntries written so far.
148 */
149 private final List<ZipArchiveEntry> entries =
150 new LinkedList<ZipArchiveEntry>();
151
152 /**
153 * CRC instance to avoid parsing DEFLATED data twice.
154 */
155 private final CRC32 crc = new CRC32();
156
157 /**
158 * Count the bytes written to out.
159 */
160 private long written = 0;
161
162 /**
163 * Start of central directory.
164 */
165 private long cdOffset = 0;
166
167 /**
168 * Length of central directory.
169 */
170 private long cdLength = 0;
171
172 /**
173 * Helper, a 0 as ZipShort.
174 */
175 private static final byte[] ZERO = {0, 0};
176
177 /**
178 * Helper, a 0 as ZipLong.
179 */
180 private static final byte[] LZERO = {0, 0, 0, 0};
181
182 /**
183 * Holds the offsets of the LFH starts for each entry.
184 */
185 private final Map<ZipArchiveEntry, Long> offsets =
186 new HashMap<ZipArchiveEntry, Long>();
187
188 /**
189 * The encoding to use for filenames and the file comment.
190 *
191 * <p>For a list of possible values see <a
192 * 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>.
193 * Defaults to UTF-8.</p>
194 */
195 private String encoding = DEFAULT_ENCODING;
196
197 /**
198 * The zip encoding to use for filenames and the file comment.
199 *
200 * This field is of internal use and will be set in {@link
201 * #setEncoding(String)}.
202 */
203 private ZipEncoding zipEncoding =
204 ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING);
205
206 /**
207 * This Deflater object is used for output.
208 *
209 */
210 protected final Deflater def = new Deflater(level, true);
211
212 /**
213 * This buffer serves as a Deflater.
214 *
215 */
216 private final byte[] buf = new byte[BUFFER_SIZE];
217
218 /**
219 * Optional random access output.
220 */
221 private final RandomAccessFile raf;
222
223 private final OutputStream out;
224
225 /**
226 * whether to use the general purpose bit flag when writing UTF-8
227 * filenames or not.
228 */
229 private boolean useUTF8Flag = true;
230
231 /**
232 * Whether to encode non-encodable file names as UTF-8.
233 */
234 private boolean fallbackToUTF8 = false;
235
236 /**
237 * whether to create UnicodePathExtraField-s for each entry.
238 */
239 private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER;
240
241 /**
242 * Whether anything inside this archive has used a ZIP64 feature.
243 *
244 * @since 1.3
245 */
246 private boolean hasUsedZip64 = false;
247
248 private Zip64Mode zip64Mode = Zip64Mode.AsNeeded;
249
250 /**
251 * Creates a new ZIP OutputStream filtering the underlying stream.
252 * @param out the outputstream to zip
253 */
254 public ZipArchiveOutputStream(OutputStream out) {
255 this.out = out;
256 this.raf = null;
257 }
258
259 /**
260 * Creates a new ZIP OutputStream writing to a File. Will use
261 * random access if possible.
262 * @param file the file to zip to
263 * @throws IOException on error
264 */
265 public ZipArchiveOutputStream(File file) throws IOException {
266 OutputStream o = null;
267 RandomAccessFile _raf = null;
268 try {
269 _raf = new RandomAccessFile(file, "rw");
270 _raf.setLength(0);
271 } catch (IOException e) {
272 if (_raf != null) {
273 try {
274 _raf.close();
275 } catch (IOException inner) { // NOPMD
276 // ignore
277 }
278 _raf = null;
279 }
280 o = new FileOutputStream(file);
281 }
282 out = o;
283 raf = _raf;
284 }
285
286 /**
287 * This method indicates whether this archive is writing to a
288 * seekable stream (i.e., to a random access file).
289 *
290 * <p>For seekable streams, you don't need to calculate the CRC or
291 * uncompressed size for {@link #STORED} entries before
292 * invoking {@link #putArchiveEntry(ArchiveEntry)}.
293 * @return true if seekable
294 */
295 public boolean isSeekable() {
296 return raf != null;
297 }
298
299 /**
300 * The encoding to use for filenames and the file comment.
301 *
302 * <p>For a list of possible values see <a
303 * 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>.
304 * Defaults to UTF-8.</p>
305 * @param encoding the encoding to use for file names, use null
306 * for the platform's default encoding
307 */
308 public void setEncoding(final String encoding) {
309 this.encoding = encoding;
310 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
311 if (useUTF8Flag && !ZipEncodingHelper.isUTF8(encoding)) {
312 useUTF8Flag = false;
313 }
314 }
315
316 /**
317 * The encoding to use for filenames and the file comment.
318 *
319 * @return null if using the platform's default character encoding.
320 */
321 public String getEncoding() {
322 return encoding;
323 }
324
325 /**
326 * Whether to set the language encoding flag if the file name
327 * encoding is UTF-8.
328 *
329 * <p>Defaults to true.</p>
330 */
331 public void setUseLanguageEncodingFlag(boolean b) {
332 useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding);
333 }
334
335 /**
336 * Whether to create Unicode Extra Fields.
337 *
338 * <p>Defaults to NEVER.</p>
339 */
340 public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) {
341 createUnicodeExtraFields = b;
342 }
343
344 /**
345 * Whether to fall back to UTF and the language encoding flag if
346 * the file name cannot be encoded using the specified encoding.
347 *
348 * <p>Defaults to false.</p>
349 */
350 public void setFallbackToUTF8(boolean b) {
351 fallbackToUTF8 = b;
352 }
353
354 /**
355 * Whether Zip64 extensions will be used.
356 *
357 * <p>When setting the mode to {@link Zip64Mode#Never Never},
358 * {@link #putArchiveEntry}, {@link #closeArchiveEntry}, {@link
359 * #finish} or {@link #close} may throw a {@link
360 * Zip64RequiredException} if the entry's size or the total size
361 * of the archive exceeds 4GB or there are more than 65536 entries
362 * inside the archive. Any archive created in this mode will be
363 * readable by implementations that don't support Zip64.</p>
364 *
365 * <p>When setting the mode to {@link Zip64Mode#Always Always},
366 * Zip64 extensions will be used for all entries. Any archive
367 * created in this mode may be unreadable by implementations that
368 * don't support Zip64 even if all its contents would be.</p>
369 *
370 * <p>When setting the mode to {@link Zip64Mode#AsNeeded
371 * AsNeeded}, Zip64 extensions will transparently be used for
372 * those entries that require them. This mode can only be used if
373 * the uncompressed size of the {@link ZipArchiveEntry} is known
374 * when calling {@link #putArchiveEntry} or the archive is written
375 * to a seekable output (i.e. you have used the {@link
376 * #ZipArchiveOutputStream(java.io.File) File-arg constructor}) -
377 * this mode is not valid when the output stream is not seekable
378 * and the uncompressed size is unknown when {@link
379 * #putArchiveEntry} is called.</p>
380 *
381 * <p>If no entry inside the resulting archive requires Zip64
382 * extensions then {@link Zip64Mode#Never Never} will create the
383 * smallest archive. {@link Zip64Mode#AsNeeded AsNeeded} will
384 * create a slightly bigger archive if the uncompressed size of
385 * any entry has initially been unknown and create an archive
386 * identical to {@link Zip64Mode#Never Never} otherwise. {@link
387 * Zip64Mode#Always Always} will create an archive that is at
388 * least 24 bytes per entry bigger than the one {@link
389 * Zip64Mode#Never Never} would create.</p>
390 *
391 * <p>Defaults to {@link Zip64Mode#AsNeeded AsNeeded} unless
392 * {@link #putArchiveEntry} is called with an entry of unknown
393 * size and data is written to a non-seekable stream - in this
394 * case the default is {@link Zip64Mode#Never Never}.</p>
395 *
396 * @since 1.3
397 */
398 public void setUseZip64(Zip64Mode mode) {
399 zip64Mode = mode;
400 }
401
402 /**
403 * {@inheritDoc}
404 * @throws Zip64RequiredException if the archive's size exceeds 4
405 * GByte or there are more than 65535 entries inside the archive
406 * and {@link #setUseZip64} is {@link Zip64Mode#Never}.
407 */
408 @Override
409 public void finish() throws IOException {
410 if (finished) {
411 throw new IOException("This archive has already been finished");
412 }
413
414 if (entry != null) {
415 throw new IOException("This archive contains unclosed entries.");
416 }
417
418 cdOffset = written;
419 for (ZipArchiveEntry ze : entries) {
420 writeCentralFileHeader(ze);
421 }
422 cdLength = written - cdOffset;
423 writeZip64CentralDirectory();
424 writeCentralDirectoryEnd();
425 offsets.clear();
426 entries.clear();
427 def.end();
428 finished = true;
429 }
430
431 /**
432 * Writes all necessary data for this entry.
433 * @throws IOException on error
434 * @throws Zip64RequiredException if the entry's uncompressed or
435 * compressed size exceeds 4 GByte and {@link #setUseZip64}
436 * is {@link Zip64Mode#Never}.
437 */
438 @Override
439 public void closeArchiveEntry() throws IOException {
440 if (finished) {
441 throw new IOException("Stream has already been finished");
442 }
443
444 if (entry == null) {
445 throw new IOException("No current entry to close");
446 }
447
448 if (!entry.hasWritten) {
449 write(EMPTY, 0, 0);
450 }
451
452 flushDeflater();
453
454 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
455 long bytesWritten = written - entry.dataStart;
456 long realCrc = crc.getValue();
457 crc.reset();
458
459 final boolean actuallyNeedsZip64 =
460 handleSizesAndCrc(bytesWritten, realCrc, effectiveMode);
461
462 if (raf != null) {
463 rewriteSizesAndCrc(actuallyNeedsZip64);
464 }
465
466 writeDataDescriptor(entry.entry);
467 entry = null;
468 }
469
470 /**
471 * Ensures all bytes sent to the deflater are written to the stream.
472 */
473 private void flushDeflater() throws IOException {
474 if (entry.entry.getMethod() == DEFLATED) {
475 def.finish();
476 while (!def.finished()) {
477 deflate();
478 }
479 }
480 }
481
482 /**
483 * Ensures the current entry's size and CRC information is set to
484 * the values just written, verifies it isn't too big in the
485 * Zip64Mode.Never case and returns whether the entry would
486 * require a Zip64 extra field.
487 */
488 private boolean handleSizesAndCrc(long bytesWritten, long crc,
489 Zip64Mode effectiveMode)
490 throws ZipException {
491 if (entry.entry.getMethod() == DEFLATED) {
492 /* It turns out def.getBytesRead() returns wrong values if
493 * the size exceeds 4 GB on Java < Java7
494 entry.entry.setSize(def.getBytesRead());
495 */
496 entry.entry.setSize(entry.bytesRead);
497 entry.entry.setCompressedSize(bytesWritten);
498 entry.entry.setCrc(crc);
499
500 def.reset();
501 } else if (raf == null) {
502 if (entry.entry.getCrc() != crc) {
503 throw new ZipException("bad CRC checksum for entry "
504 + entry.entry.getName() + ": "
505 + Long.toHexString(entry.entry.getCrc())
506 + " instead of "
507 + Long.toHexString(crc));
508 }
509
510 if (entry.entry.getSize() != bytesWritten) {
511 throw new ZipException("bad size for entry "
512 + entry.entry.getName() + ": "
513 + entry.entry.getSize()
514 + " instead of "
515 + bytesWritten);
516 }
517 } else { /* method is STORED and we used RandomAccessFile */
518 entry.entry.setSize(bytesWritten);
519 entry.entry.setCompressedSize(bytesWritten);
520 entry.entry.setCrc(crc);
521 }
522
523 final boolean actuallyNeedsZip64 = effectiveMode == Zip64Mode.Always
524 || entry.entry.getSize() >= ZIP64_MAGIC
525 || entry.entry.getCompressedSize() >= ZIP64_MAGIC;
526 if (actuallyNeedsZip64 && effectiveMode == Zip64Mode.Never) {
527 throw new Zip64RequiredException(Zip64RequiredException
528 .getEntryTooBigMessage(entry.entry));
529 }
530 return actuallyNeedsZip64;
531 }
532
533 /**
534 * When using random access output, write the local file header
535 * and potentiall the ZIP64 extra containing the correct CRC and
536 * compressed/uncompressed sizes.
537 */
538 private void rewriteSizesAndCrc(boolean actuallyNeedsZip64)
539 throws IOException {
540 long save = raf.getFilePointer();
541
542 raf.seek(entry.localDataStart);
543 writeOut(ZipLong.getBytes(entry.entry.getCrc()));
544 if (!hasZip64Extra(entry.entry) || !actuallyNeedsZip64) {
545 writeOut(ZipLong.getBytes(entry.entry.getCompressedSize()));
546 writeOut(ZipLong.getBytes(entry.entry.getSize()));
547 } else {
548 writeOut(ZipLong.ZIP64_MAGIC.getBytes());
549 writeOut(ZipLong.ZIP64_MAGIC.getBytes());
550 }
551
552 if (hasZip64Extra(entry.entry)) {
553 // seek to ZIP64 extra, skip header and size information
554 raf.seek(entry.localDataStart + 3 * WORD + 2 * SHORT
555 + getName(entry.entry).limit() + 2 * SHORT);
556 // inside the ZIP64 extra uncompressed size comes
557 // first, unlike the LFH, CD or data descriptor
558 writeOut(ZipEightByteInteger.getBytes(entry.entry.getSize()));
559 writeOut(ZipEightByteInteger.getBytes(entry.entry.getCompressedSize()));
560
561 if (!actuallyNeedsZip64) {
562 // do some cleanup:
563 // * rewrite version needed to extract
564 raf.seek(entry.localDataStart - 5 * SHORT);
565 writeOut(ZipShort.getBytes(INITIAL_VERSION));
566
567 // * remove ZIP64 extra so it doesn't get written
568 // to the central directory
569 entry.entry.removeExtraField(Zip64ExtendedInformationExtraField
570 .HEADER_ID);
571 entry.entry.setExtra();
572
573 // * reset hasUsedZip64 if it has been set because
574 // of this entry
575 if (entry.causedUseOfZip64) {
576 hasUsedZip64 = false;
577 }
578 }
579 }
580 raf.seek(save);
581 }
582
583 /**
584 * {@inheritDoc}
585 * @throws ClassCastException if entry is not an instance of ZipArchiveEntry
586 * @throws Zip64RequiredException if the entry's uncompressed or
587 * compressed size is known to exceed 4 GByte and {@link #setUseZip64}
588 * is {@link Zip64Mode#Never}.
589 */
590 @Override
591 public void putArchiveEntry(ArchiveEntry archiveEntry) throws IOException {
592 if (finished) {
593 throw new IOException("Stream has already been finished");
594 }
595
596 if (entry != null) {
597 closeArchiveEntry();
598 }
599
600 entry = new CurrentEntry((ZipArchiveEntry) archiveEntry);
601 entries.add(entry.entry);
602
603 setDefaults(entry.entry);
604
605 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
606 validateSizeInformation(effectiveMode);
607
608 if (shouldAddZip64Extra(entry.entry, effectiveMode)) {
609
610 Zip64ExtendedInformationExtraField z64 = getZip64Extra(entry.entry);
611
612 // just a placeholder, real data will be in data
613 // descriptor or inserted later via RandomAccessFile
614 ZipEightByteInteger size = ZipEightByteInteger.ZERO;
615 if (entry.entry.getMethod() == STORED
616 && entry.entry.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
617 // actually, we already know the sizes
618 size = new ZipEightByteInteger(entry.entry.getSize());
619 }
620 z64.setSize(size);
621 z64.setCompressedSize(size);
622 entry.entry.setExtra();
623 }
624
625 if (entry.entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
626 def.setLevel(level);
627 hasCompressionLevelChanged = false;
628 }
629 writeLocalFileHeader(entry.entry);
630 }
631
632 /**
633 * Provides default values for compression method and last
634 * modification time.
635 */
636 private void setDefaults(ZipArchiveEntry entry) {
637 if (entry.getMethod() == -1) { // not specified
638 entry.setMethod(method);
639 }
640
641 if (entry.getTime() == -1) { // not specified
642 entry.setTime(System.currentTimeMillis());
643 }
644 }
645
646 /**
647 * Throws an exception if the size is unknown for a stored entry
648 * that is written to a non-seekable output or the entry is too
649 * big to be written without Zip64 extra but the mode has been set
650 * to Never.
651 */
652 private void validateSizeInformation(Zip64Mode effectiveMode)
653 throws ZipException {
654 // Size/CRC not required if RandomAccessFile is used
655 if (entry.entry.getMethod() == STORED && raf == null) {
656 if (entry.entry.getSize() == ArchiveEntry.SIZE_UNKNOWN) {
657 throw new ZipException("uncompressed size is required for"
658 + " STORED method when not writing to a"
659 + " file");
660 }
661 if (entry.entry.getCrc() == -1) {
662 throw new ZipException("crc checksum is required for STORED"
663 + " method when not writing to a file");
664 }
665 entry.entry.setCompressedSize(entry.entry.getSize());
666 }
667
668 if ((entry.entry.getSize() >= ZIP64_MAGIC
669 || entry.entry.getCompressedSize() >= ZIP64_MAGIC)
670 && effectiveMode == Zip64Mode.Never) {
671 throw new Zip64RequiredException(Zip64RequiredException
672 .getEntryTooBigMessage(entry.entry));
673 }
674 }
675
676 /**
677 * Whether to addd a Zip64 extended information extra field to the
678 * local file header.
679 *
680 * <p>Returns true if</p>
681 *
682 * <ul>
683 * <li>mode is Always</li>
684 * <li>or we already know it is going to be needed</li>
685 * <li>or the size is unknown and we can ensure it won't hurt
686 * other implementations if we add it (i.e. we can erase its
687 * usage</li>
688 * </ul>
689 */
690 private boolean shouldAddZip64Extra(ZipArchiveEntry entry, Zip64Mode mode) {
691 return mode == Zip64Mode.Always
692 || entry.getSize() >= ZIP64_MAGIC
693 || entry.getCompressedSize() >= ZIP64_MAGIC
694 || (entry.getSize() == ArchiveEntry.SIZE_UNKNOWN
695 && raf != null && mode != Zip64Mode.Never);
696 }
697
698 /**
699 * Set the file comment.
700 * @param comment the comment
701 */
702 public void setComment(String comment) {
703 this.comment = comment;
704 }
705
706 /**
707 * Sets the compression level for subsequent entries.
708 *
709 * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>
710 * @param level the compression level.
711 * @throws IllegalArgumentException if an invalid compression
712 * level is specified.
713 */
714 public void setLevel(int level) {
715 if (level < Deflater.DEFAULT_COMPRESSION
716 || level > Deflater.BEST_COMPRESSION) {
717 throw new IllegalArgumentException("Invalid compression level: "
718 + level);
719 }
720 hasCompressionLevelChanged = (this.level != level);
721 this.level = level;
722 }
723
724 /**
725 * Sets the default compression method for subsequent entries.
726 *
727 * <p>Default is DEFLATED.</p>
728 * @param method an <code>int</code> from java.util.zip.ZipEntry
729 */
730 public void setMethod(int method) {
731 this.method = method;
732 }
733
734 /**
735 * Whether this stream is able to write the given entry.
736 *
737 * <p>May return false if it is set up to use encryption or a
738 * compression method that hasn't been implemented yet.</p>
739 * @since 1.1
740 */
741 @Override
742 public boolean canWriteEntryData(ArchiveEntry ae) {
743 if (ae instanceof ZipArchiveEntry) {
744 return ZipUtil.canHandleEntryData((ZipArchiveEntry) ae);
745 }
746 return false;
747 }
748
749 /**
750 * Writes bytes to ZIP entry.
751 * @param b the byte array to write
752 * @param offset the start position to write from
753 * @param length the number of bytes to write
754 * @throws IOException on error
755 */
756 @Override
757 public void write(byte[] b, int offset, int length) throws IOException {
758 ZipUtil.checkRequestedFeatures(entry.entry);
759 entry.hasWritten = true;
760 if (entry.entry.getMethod() == DEFLATED) {
761 writeDeflated(b, offset, length);
762 } else {
763 writeOut(b, offset, length);
764 written += length;
765 }
766 crc.update(b, offset, length);
767 count(length);
768 }
769
770 /**
771 * write implementation for DEFLATED entries.
772 */
773 private void writeDeflated(byte[]b, int offset, int length)
774 throws IOException {
775 if (length > 0 && !def.finished()) {
776 entry.bytesRead += length;
777 if (length <= DEFLATER_BLOCK_SIZE) {
778 def.setInput(b, offset, length);
779 deflateUntilInputIsNeeded();
780 } else {
781 final int fullblocks = length / DEFLATER_BLOCK_SIZE;
782 for (int i = 0; i < fullblocks; i++) {
783 def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE,
784 DEFLATER_BLOCK_SIZE);
785 deflateUntilInputIsNeeded();
786 }
787 final int done = fullblocks * DEFLATER_BLOCK_SIZE;
788 if (done < length) {
789 def.setInput(b, offset + done, length - done);
790 deflateUntilInputIsNeeded();
791 }
792 }
793 }
794 }
795
796 /**
797 * Closes this output stream and releases any system resources
798 * associated with the stream.
799 *
800 * @exception IOException if an I/O error occurs.
801 * @throws Zip64RequiredException if the archive's size exceeds 4
802 * GByte or there are more than 65535 entries inside the archive
803 * and {@link #setUseZip64} is {@link Zip64Mode#Never}.
804 */
805 @Override
806 public void close() throws IOException {
807 if (!finished) {
808 finish();
809 }
810 destroy();
811 }
812
813 /**
814 * Flushes this output stream and forces any buffered output bytes
815 * to be written out to the stream.
816 *
817 * @exception IOException if an I/O error occurs.
818 */
819 @Override
820 public void flush() throws IOException {
821 if (out != null) {
822 out.flush();
823 }
824 }
825
826 /*
827 * Various ZIP constants
828 */
829 /**
830 * local file header signature
831 */
832 static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes();
833 /**
834 * data descriptor signature
835 */
836 static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes();
837 /**
838 * central file header signature
839 */
840 static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes();
841 /**
842 * end of central dir signature
843 */
844 static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
845 /**
846 * ZIP64 end of central dir signature
847 */
848 static final byte[] ZIP64_EOCD_SIG = ZipLong.getBytes(0X06064B50L);
849 /**
850 * ZIP64 end of central dir locator signature
851 */
852 static final byte[] ZIP64_EOCD_LOC_SIG = ZipLong.getBytes(0X07064B50L);
853
854 /**
855 * Writes next block of compressed data to the output stream.
856 * @throws IOException on error
857 */
858 protected final void deflate() throws IOException {
859 int len = def.deflate(buf, 0, buf.length);
860 if (len > 0) {
861 writeOut(buf, 0, len);
862 written += len;
863 }
864 }
865
866 /**
867 * Writes the local file header entry
868 * @param ze the entry to write
869 * @throws IOException on error
870 */
871 protected void writeLocalFileHeader(ZipArchiveEntry ze) throws IOException {
872
873 boolean encodable = zipEncoding.canEncode(ze.getName());
874 ByteBuffer name = getName(ze);
875
876 if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
877 addUnicodeExtraFields(ze, encodable, name);
878 }
879
880 offsets.put(ze, Long.valueOf(written));
881
882 writeOut(LFH_SIG);
883 written += WORD;
884
885 //store method in local variable to prevent multiple method calls
886 final int zipMethod = ze.getMethod();
887
888 writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
889 !encodable
890 && fallbackToUTF8,
891 hasZip64Extra(ze));
892 written += WORD;
893
894 // compression method
895 writeOut(ZipShort.getBytes(zipMethod));
896 written += SHORT;
897
898 // last mod. time and date
899 writeOut(ZipUtil.toDosTime(ze.getTime()));
900 written += WORD;
901
902 // CRC
903 // compressed length
904 // uncompressed length
905 entry.localDataStart = written;
906 if (zipMethod == DEFLATED || raf != null) {
907 writeOut(LZERO);
908 if (hasZip64Extra(entry.entry)) {
909 // point to ZIP64 extended information extra field for
910 // sizes, may get rewritten once sizes are known if
911 // stream is seekable
912 writeOut(ZipLong.ZIP64_MAGIC.getBytes());
913 writeOut(ZipLong.ZIP64_MAGIC.getBytes());
914 } else {
915 writeOut(LZERO);
916 writeOut(LZERO);
917 }
918 } else {
919 writeOut(ZipLong.getBytes(ze.getCrc()));
920 byte[] size = ZipLong.ZIP64_MAGIC.getBytes();
921 if (!hasZip64Extra(ze)) {
922 size = ZipLong.getBytes(ze.getSize());
923 }
924 writeOut(size);
925 writeOut(size);
926 }
927 // CheckStyle:MagicNumber OFF
928 written += 12;
929 // CheckStyle:MagicNumber ON
930
931 // file name length
932 writeOut(ZipShort.getBytes(name.limit()));
933 written += SHORT;
934
935 // extra field length
936 byte[] extra = ze.getLocalFileDataExtra();
937 writeOut(ZipShort.getBytes(extra.length));
938 written += SHORT;
939
940 // file name
941 writeOut(name.array(), name.arrayOffset(),
942 name.limit() - name.position());
943 written += name.limit();
944
945 // extra field
946 writeOut(extra);
947 written += extra.length;
948
949 entry.dataStart = written;
950 }
951
952 /**
953 * Adds UnicodeExtra fields for name and file comment if mode is
954 * ALWAYS or the data cannot be encoded using the configured
955 * encoding.
956 */
957 private void addUnicodeExtraFields(ZipArchiveEntry ze, boolean encodable,
958 ByteBuffer name)
959 throws IOException {
960 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
961 || !encodable) {
962 ze.addExtraField(new UnicodePathExtraField(ze.getName(),
963 name.array(),
964 name.arrayOffset(),
965 name.limit()
966 - name.position()));
967 }
968
969 String comm = ze.getComment();
970 if (comm != null && !"".equals(comm)) {
971
972 boolean commentEncodable = zipEncoding.canEncode(comm);
973
974 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
975 || !commentEncodable) {
976 ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
977 ze.addExtraField(new UnicodeCommentExtraField(comm,
978 commentB.array(),
979 commentB.arrayOffset(),
980 commentB.limit()
981 - commentB.position())
982 );
983 }
984 }
985 }
986
987 /**
988 * Writes the data descriptor entry.
989 * @param ze the entry to write
990 * @throws IOException on error
991 */
992 protected void writeDataDescriptor(ZipArchiveEntry ze) throws IOException {
993 if (ze.getMethod() != DEFLATED || raf != null) {
994 return;
995 }
996 writeOut(DD_SIG);
997 writeOut(ZipLong.getBytes(ze.getCrc()));
998 int sizeFieldSize = WORD;
999 if (!hasZip64Extra(ze)) {
1000 writeOut(ZipLong.getBytes(ze.getCompressedSize()));
1001 writeOut(ZipLong.getBytes(ze.getSize()));
1002 } else {
1003 sizeFieldSize = DWORD;
1004 writeOut(ZipEightByteInteger.getBytes(ze.getCompressedSize()));
1005 writeOut(ZipEightByteInteger.getBytes(ze.getSize()));
1006 }
1007 written += 2 * WORD + 2 * sizeFieldSize;
1008 }
1009
1010 /**
1011 * Writes the central file header entry.
1012 * @param ze the entry to write
1013 * @throws IOException on error
1014 * @throws Zip64RequiredException if the archive's size exceeds 4
1015 * GByte and {@link Zip64Mode #setUseZip64} is {@link
1016 * Zip64Mode#Never}.
1017 */
1018 protected void writeCentralFileHeader(ZipArchiveEntry ze) throws IOException {
1019 writeOut(CFH_SIG);
1020 written += WORD;
1021
1022 final long lfhOffset = offsets.get(ze).longValue();
1023 final boolean needsZip64Extra = hasZip64Extra(ze)
1024 || ze.getCompressedSize() >= ZIP64_MAGIC
1025 || ze.getSize() >= ZIP64_MAGIC
1026 || lfhOffset >= ZIP64_MAGIC;
1027
1028 if (needsZip64Extra && zip64Mode == Zip64Mode.Never) {
1029 // must be the offset that is too big, otherwise an
1030 // exception would have been throw in putArchiveEntry or
1031 // closeArchiveEntry
1032 throw new Zip64RequiredException(Zip64RequiredException
1033 .ARCHIVE_TOO_BIG_MESSAGE);
1034 }
1035
1036 handleZip64Extra(ze, lfhOffset, needsZip64Extra);
1037
1038 // version made by
1039 // CheckStyle:MagicNumber OFF
1040 writeOut(ZipShort.getBytes((ze.getPlatform() << 8) |
1041 (!hasUsedZip64 ? DATA_DESCRIPTOR_MIN_VERSION
1042 : ZIP64_MIN_VERSION)));
1043 written += SHORT;
1044
1045 final int zipMethod = ze.getMethod();
1046 final boolean encodable = zipEncoding.canEncode(ze.getName());
1047 writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
1048 !encodable
1049 && fallbackToUTF8,
1050 needsZip64Extra);
1051 written += WORD;
1052
1053 // compression method
1054 writeOut(ZipShort.getBytes(zipMethod));
1055 written += SHORT;
1056
1057 // last mod. time and date
1058 writeOut(ZipUtil.toDosTime(ze.getTime()));
1059 written += WORD;
1060
1061 // CRC
1062 // compressed length
1063 // uncompressed length
1064 writeOut(ZipLong.getBytes(ze.getCrc()));
1065 if (ze.getCompressedSize() >= ZIP64_MAGIC
1066 || ze.getSize() >= ZIP64_MAGIC) {
1067 writeOut(ZipLong.ZIP64_MAGIC.getBytes());
1068 writeOut(ZipLong.ZIP64_MAGIC.getBytes());
1069 } else {
1070 writeOut(ZipLong.getBytes(ze.getCompressedSize()));
1071 writeOut(ZipLong.getBytes(ze.getSize()));
1072 }
1073 // CheckStyle:MagicNumber OFF
1074 written += 12;
1075 // CheckStyle:MagicNumber ON
1076
1077 ByteBuffer name = getName(ze);
1078
1079 writeOut(ZipShort.getBytes(name.limit()));
1080 written += SHORT;
1081
1082 // extra field length
1083 byte[] extra = ze.getCentralDirectoryExtra();
1084 writeOut(ZipShort.getBytes(extra.length));
1085 written += SHORT;
1086
1087 // file comment length
1088 String comm = ze.getComment();
1089 if (comm == null) {
1090 comm = "";
1091 }
1092
1093 ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
1094
1095 writeOut(ZipShort.getBytes(commentB.limit()));
1096 written += SHORT;
1097
1098 // disk number start
1099 writeOut(ZERO);
1100 written += SHORT;
1101
1102 // internal file attributes
1103 writeOut(ZipShort.getBytes(ze.getInternalAttributes()));
1104 written += SHORT;
1105
1106 // external file attributes
1107 writeOut(ZipLong.getBytes(ze.getExternalAttributes()));
1108 written += WORD;
1109
1110 // relative offset of LFH
1111 writeOut(ZipLong.getBytes(Math.min(lfhOffset, ZIP64_MAGIC)));
1112 written += WORD;
1113
1114 // file name
1115 writeOut(name.array(), name.arrayOffset(),
1116 name.limit() - name.position());
1117 written += name.limit();
1118
1119 // extra field
1120 writeOut(extra);
1121 written += extra.length;
1122
1123 // file comment
1124 writeOut(commentB.array(), commentB.arrayOffset(),
1125 commentB.limit() - commentB.position());
1126 written += commentB.limit();
1127 }
1128
1129 /**
1130 * If the entry needs Zip64 extra information inside the central
1131 * directory then configure its data.
1132 */
1133 private void handleZip64Extra(ZipArchiveEntry ze, long lfhOffset,
1134 boolean needsZip64Extra) {
1135 if (needsZip64Extra) {
1136 Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze);
1137 if (ze.getCompressedSize() >= ZIP64_MAGIC
1138 || ze.getSize() >= ZIP64_MAGIC) {
1139 z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
1140 z64.setSize(new ZipEightByteInteger(ze.getSize()));
1141 } else {
1142 // reset value that may have been set for LFH
1143 z64.setCompressedSize(null);
1144 z64.setSize(null);
1145 }
1146 if (lfhOffset >= ZIP64_MAGIC) {
1147 z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset));
1148 }
1149 ze.setExtra();
1150 }
1151 }
1152
1153 /**
1154 * Writes the "End of central dir record".
1155 * @throws IOException on error
1156 * @throws Zip64RequiredException if the archive's size exceeds 4
1157 * GByte or there are more than 65535 entries inside the archive
1158 * and {@link Zip64Mode #setUseZip64} is {@link Zip64Mode#Never}.
1159 */
1160 protected void writeCentralDirectoryEnd() throws IOException {
1161 writeOut(EOCD_SIG);
1162
1163 // disk numbers
1164 writeOut(ZERO);
1165 writeOut(ZERO);
1166
1167 // number of entries
1168 int numberOfEntries = entries.size();
1169 if (numberOfEntries > ZIP64_MAGIC_SHORT
1170 && zip64Mode == Zip64Mode.Never) {
1171 throw new Zip64RequiredException(Zip64RequiredException
1172 .TOO_MANY_ENTRIES_MESSAGE);
1173 }
1174 if (cdOffset > ZIP64_MAGIC && zip64Mode == Zip64Mode.Never) {
1175 throw new Zip64RequiredException(Zip64RequiredException
1176 .ARCHIVE_TOO_BIG_MESSAGE);
1177 }
1178
1179 byte[] num = ZipShort.getBytes(Math.min(numberOfEntries,
1180 ZIP64_MAGIC_SHORT));
1181 writeOut(num);
1182 writeOut(num);
1183
1184 // length and location of CD
1185 writeOut(ZipLong.getBytes(Math.min(cdLength, ZIP64_MAGIC)));
1186 writeOut(ZipLong.getBytes(Math.min(cdOffset, ZIP64_MAGIC)));
1187
1188 // ZIP file comment
1189 ByteBuffer data = this.zipEncoding.encode(comment);
1190 writeOut(ZipShort.getBytes(data.limit()));
1191 writeOut(data.array(), data.arrayOffset(),
1192 data.limit() - data.position());
1193 }
1194
1195 private static final byte[] ONE = ZipLong.getBytes(1L);
1196
1197 /**
1198 * Writes the "ZIP64 End of central dir record" and
1199 * "ZIP64 End of central dir locator".
1200 * @throws IOException on error
1201 * @since 1.3
1202 */
1203 protected void writeZip64CentralDirectory() throws IOException {
1204 if (zip64Mode == Zip64Mode.Never) {
1205 return;
1206 }
1207
1208 if (!hasUsedZip64
1209 && (cdOffset >= ZIP64_MAGIC || cdLength >= ZIP64_MAGIC
1210 || entries.size() >= ZIP64_MAGIC_SHORT)) {
1211 // actually "will use"
1212 hasUsedZip64 = true;
1213 }
1214
1215 if (!hasUsedZip64) {
1216 return;
1217 }
1218
1219 long offset = written;
1220
1221 writeOut(ZIP64_EOCD_SIG);
1222 // size, we don't have any variable length as we don't support
1223 // the extensible data sector, yet
1224 writeOut(ZipEightByteInteger
1225 .getBytes(SHORT /* version made by */
1226 + SHORT /* version needed to extract */
1227 + WORD /* disk number */
1228 + WORD /* disk with central directory */
1229 + DWORD /* number of entries in CD on this disk */
1230 + DWORD /* total number of entries */
1231 + DWORD /* size of CD */
1232 + DWORD /* offset of CD */
1233 ));
1234
1235 // version made by and version needed to extract
1236 writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION));
1237 writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION));
1238
1239 // disk numbers - four bytes this time
1240 writeOut(LZERO);
1241 writeOut(LZERO);
1242
1243 // number of entries
1244 byte[] num = ZipEightByteInteger.getBytes(entries.size());
1245 writeOut(num);
1246 writeOut(num);
1247
1248 // length and location of CD
1249 writeOut(ZipEightByteInteger.getBytes(cdLength));
1250 writeOut(ZipEightByteInteger.getBytes(cdOffset));
1251
1252 // no "zip64 extensible data sector" for now
1253
1254 // and now the "ZIP64 end of central directory locator"
1255 writeOut(ZIP64_EOCD_LOC_SIG);
1256
1257 // disk number holding the ZIP64 EOCD record
1258 writeOut(LZERO);
1259 // relative offset of ZIP64 EOCD record
1260 writeOut(ZipEightByteInteger.getBytes(offset));
1261 // total number of disks
1262 writeOut(ONE);
1263 }
1264
1265 /**
1266 * Write bytes to output or random access file.
1267 * @param data the byte array to write
1268 * @throws IOException on error
1269 */
1270 protected final void writeOut(byte[] data) throws IOException {
1271 writeOut(data, 0, data.length);
1272 }
1273
1274 /**
1275 * Write bytes to output or random access file.
1276 * @param data the byte array to write
1277 * @param offset the start position to write from
1278 * @param length the number of bytes to write
1279 * @throws IOException on error
1280 */
1281 protected final void writeOut(byte[] data, int offset, int length)
1282 throws IOException {
1283 if (raf != null) {
1284 raf.write(data, offset, length);
1285 } else {
1286 out.write(data, offset, length);
1287 }
1288 }
1289
1290 private void deflateUntilInputIsNeeded() throws IOException {
1291 while (!def.needsInput()) {
1292 deflate();
1293 }
1294 }
1295
1296 private void writeVersionNeededToExtractAndGeneralPurposeBits(final int
1297 zipMethod,
1298 final boolean
1299 utfFallback,
1300 final boolean
1301 zip64)
1302 throws IOException {
1303
1304 // CheckStyle:MagicNumber OFF
1305 int versionNeededToExtract = INITIAL_VERSION;
1306 GeneralPurposeBit b = new GeneralPurposeBit();
1307 b.useUTF8ForNames(useUTF8Flag || utfFallback);
1308 if (zipMethod == DEFLATED && raf == null) {
1309 // requires version 2 as we are going to store length info
1310 // in the data descriptor
1311 versionNeededToExtract = DATA_DESCRIPTOR_MIN_VERSION;
1312 b.useDataDescriptor(true);
1313 }
1314 if (zip64) {
1315 versionNeededToExtract = ZIP64_MIN_VERSION;
1316 }
1317 // CheckStyle:MagicNumber ON
1318
1319 // version needed to extract
1320 writeOut(ZipShort.getBytes(versionNeededToExtract));
1321 // general purpose bit flag
1322 writeOut(b.encode());
1323 }
1324
1325 /**
1326 * Creates a new zip entry taking some information from the given
1327 * file and using the provided name.
1328 *
1329 * <p>The name will be adjusted to end with a forward slash "/" if
1330 * the file is a directory. If the file is not a directory a
1331 * potential trailing forward slash will be stripped from the
1332 * entry name.</p>
1333 *
1334 * <p>Must not be used if the stream has already been closed.</p>
1335 */
1336 @Override
1337 public ArchiveEntry createArchiveEntry(File inputFile, String entryName)
1338 throws IOException {
1339 if (finished) {
1340 throw new IOException("Stream has already been finished");
1341 }
1342 return new ZipArchiveEntry(inputFile, entryName);
1343 }
1344
1345 /**
1346 * Get the existing ZIP64 extended information extra field or
1347 * create a new one and add it to the entry.
1348 *
1349 * @since 1.3
1350 */
1351 private Zip64ExtendedInformationExtraField
1352 getZip64Extra(ZipArchiveEntry ze) {
1353 if (entry != null) {
1354 entry.causedUseOfZip64 = !hasUsedZip64;
1355 }
1356 hasUsedZip64 = true;
1357 Zip64ExtendedInformationExtraField z64 =
1358 (Zip64ExtendedInformationExtraField)
1359 ze.getExtraField(Zip64ExtendedInformationExtraField
1360 .HEADER_ID);
1361 if (z64 == null) {
1362 /*
1363 System.err.println("Adding z64 for " + ze.getName()
1364 + ", method: " + ze.getMethod()
1365 + " (" + (ze.getMethod() == STORED) + ")"
1366 + ", raf: " + (raf != null));
1367 */
1368 z64 = new Zip64ExtendedInformationExtraField();
1369 }
1370
1371 // even if the field is there already, make sure it is the first one
1372 ze.addAsFirstExtraField(z64);
1373
1374 return z64;
1375 }
1376
1377 /**
1378 * Is there a ZIP64 extended information extra field for the
1379 * entry?
1380 *
1381 * @since 1.3
1382 */
1383 private boolean hasZip64Extra(ZipArchiveEntry ze) {
1384 return ze.getExtraField(Zip64ExtendedInformationExtraField
1385 .HEADER_ID)
1386 != null;
1387 }
1388
1389 /**
1390 * If the mode is AsNeeded and the entry is a compressed entry of
1391 * unknown size that gets written to a non-seekable stream the
1392 * change the default to Never.
1393 *
1394 * @since 1.3
1395 */
1396 private Zip64Mode getEffectiveZip64Mode(ZipArchiveEntry ze) {
1397 if (zip64Mode != Zip64Mode.AsNeeded
1398 || raf != null
1399 || ze.getMethod() != DEFLATED
1400 || ze.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
1401 return zip64Mode;
1402 }
1403 return Zip64Mode.Never;
1404 }
1405
1406 private ZipEncoding getEntryEncoding(ZipArchiveEntry ze) {
1407 boolean encodable = zipEncoding.canEncode(ze.getName());
1408 return !encodable && fallbackToUTF8
1409 ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
1410 }
1411
1412 private ByteBuffer getName(ZipArchiveEntry ze) throws IOException {
1413 return getEntryEncoding(ze).encode(ze.getName());
1414 }
1415
1416 /**
1417 * Closes the underlying stream/file without finishing the
1418 * archive, the result will likely be a corrupt archive.
1419 *
1420 * <p>This method only exists to support tests that generate
1421 * corrupt archives so they can clean up any temporary files.</p>
1422 */
1423 void destroy() throws IOException {
1424 if (raf != null) {
1425 raf.close();
1426 }
1427 if (out != null) {
1428 out.close();
1429 }
1430 }
1431
1432 /**
1433 * enum that represents the possible policies for creating Unicode
1434 * extra fields.
1435 */
1436 public static final class UnicodeExtraFieldPolicy {
1437 /**
1438 * Always create Unicode extra fields.
1439 */
1440 public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always");
1441 /**
1442 * Never create Unicode extra fields.
1443 */
1444 public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never");
1445 /**
1446 * Create Unicode extra fields for filenames that cannot be
1447 * encoded using the specified encoding.
1448 */
1449 public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE =
1450 new UnicodeExtraFieldPolicy("not encodeable");
1451
1452 private final String name;
1453 private UnicodeExtraFieldPolicy(String n) {
1454 name = n;
1455 }
1456 @Override
1457 public String toString() {
1458 return name;
1459 }
1460 }
1461
1462 /**
1463 * Structure collecting information for the entry that is
1464 * currently being written.
1465 */
1466 private static final class CurrentEntry {
1467 private CurrentEntry(ZipArchiveEntry entry) {
1468 this.entry = entry;
1469 }
1470 /**
1471 * Current ZIP entry.
1472 */
1473 private final ZipArchiveEntry entry;
1474 /**
1475 * Offset for CRC entry in the local file header data for the
1476 * current entry starts here.
1477 */
1478 private long localDataStart = 0;
1479 /**
1480 * Data for local header data
1481 */
1482 private long dataStart = 0;
1483 /**
1484 * Number of bytes read for the current entry (can't rely on
1485 * Deflater#getBytesRead) when using DEFLATED.
1486 */
1487 private long bytesRead = 0;
1488 /**
1489 * Whether current entry was the first one using ZIP64 features.
1490 */
1491 private boolean causedUseOfZip64 = false;
1492 /**
1493 * Whether write() has been called at all.
1494 *
1495 * <p>In order to create a valid archive {@link
1496 * #closeArchiveEntry closeArchiveEntry} will write an empty
1497 * array to get the CRC right if nothing has been written to
1498 * the stream at all.</p>
1499 */
1500 private boolean hasWritten;
1501 }
1502
1503 }