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