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