1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved.
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This code is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 2 only, as
8  * published by the Free Software Foundation.  Oracle designates this
9  * particular file as subject to the "Classpath" exception as provided
10  * by Oracle in the LICENSE file that accompanied this code.
11  *
12  * This code is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15  * version 2 for more details (a copy is included in the LICENSE file that
16  * accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License version
19  * 2 along with this work; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23  * or visit www.oracle.com if you need additional information or have any
24  * questions.
25  */
26 
27 package java.util.zip;
28 
29 import java.io.OutputStream;
30 import java.io.IOException;
31 import java.nio.charset.Charset;
32 import java.nio.charset.StandardCharsets;
33 import java.util.Vector;
34 import java.util.HashSet;
35 import static java.util.zip.ZipConstants64.*;
36 import static java.util.zip.ZipUtils.*;
37 import sun.security.action.GetPropertyAction;
38 
39 /**
40  * This class implements an output stream filter for writing files in the
41  * ZIP file format. Includes support for both compressed and uncompressed
42  * entries.
43  *
44  * @author      David Connelly
45  * @since 1.1
46  */
47 public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
48 
49     /**
50      * Whether to use ZIP64 for zip files with more than 64k entries.
51      * Until ZIP64 support in zip implementations is ubiquitous, this
52      * system property allows the creation of zip files which can be
53      * read by legacy zip implementations which tolerate "incorrect"
54      * total entry count fields, such as the ones in jdk6, and even
55      * some in jdk7.
56      */
57     // Android-changed: Always allow use of Zip64.
58     private static final boolean inhibitZip64 = false;
59         // Boolean.parseBoolean(
60         //    GetPropertyAction.privilegedGetProperty("jdk.util.zip.inhibitZip64"));
61 
62     private static class XEntry {
63         final ZipEntry entry;
64         final long offset;
XEntry(ZipEntry entry, long offset)65         public XEntry(ZipEntry entry, long offset) {
66             this.entry = entry;
67             this.offset = offset;
68         }
69     }
70 
71     private XEntry current;
72     private Vector<XEntry> xentries = new Vector<>();
73     private HashSet<String> names = new HashSet<>();
74     private CRC32 crc = new CRC32();
75     private long written = 0;
76     private long locoff = 0;
77     private byte[] comment;
78     private int method = DEFLATED;
79     private boolean finished;
80 
81     private boolean closed = false;
82 
83     private final ZipCoder zc;
84 
version(ZipEntry e)85     private static int version(ZipEntry e) throws ZipException {
86         return switch (e.method) {
87         case DEFLATED -> 20;
88         case STORED   -> 10;
89         default       -> throw new ZipException("unsupported compression method");
90         };
91     }
92 
93     /**
94      * Checks to make sure that this stream has not been closed.
95      */
ensureOpen()96     private void ensureOpen() throws IOException {
97         if (closed) {
98             throw new IOException("Stream closed");
99         }
100     }
101 
102     /**
103      * Compression method for uncompressed (STORED) entries.
104      */
105     public static final int STORED = ZipEntry.STORED;
106 
107     /**
108      * Compression method for compressed (DEFLATED) entries.
109      */
110     public static final int DEFLATED = ZipEntry.DEFLATED;
111 
112     /**
113      * Creates a new ZIP output stream.
114      *
115      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used
116      * to encode the entry names and comments.
117      *
118      * @param out the actual output stream
119      */
ZipOutputStream(OutputStream out)120     public ZipOutputStream(OutputStream out) {
121         this(out, StandardCharsets.UTF_8);
122     }
123 
124     /**
125      * Creates a new ZIP output stream.
126      *
127      * @param out the actual output stream
128      *
129      * @param charset the {@linkplain java.nio.charset.Charset charset}
130      *                to be used to encode the entry names and comments
131      *
132      * @since 1.7
133      */
ZipOutputStream(OutputStream out, Charset charset)134     public ZipOutputStream(OutputStream out, Charset charset) {
135         super(out, out != null ? new Deflater(Deflater.DEFAULT_COMPRESSION, true) : null);
136         if (charset == null)
137             throw new NullPointerException("charset is null");
138         this.zc = ZipCoder.get(charset);
139         usesDefaultDeflater = true;
140     }
141 
142     /**
143      * Sets the ZIP file comment.
144      * @param     comment the comment string
145      * @throws    IllegalArgumentException if the length of the specified
146      *            ZIP file comment is greater than 0xFFFF bytes
147      */
setComment(String comment)148     public void setComment(String comment) {
149         byte[] bytes = null;
150         if (comment != null) {
151             bytes = zc.getBytes(comment);
152             if (bytes.length > 0xffff) {
153                 throw new IllegalArgumentException("ZIP file comment too long");
154             }
155         }
156         this.comment = bytes;
157     }
158 
159     /**
160      * Sets the default compression method for subsequent entries. This
161      * default will be used whenever the compression method is not specified
162      * for an individual ZIP file entry, and is initially set to DEFLATED.
163      * @param     method the default compression method
164      * @throws    IllegalArgumentException if the specified compression method
165      *            is invalid
166      */
setMethod(int method)167     public void setMethod(int method) {
168         if (method != DEFLATED && method != STORED) {
169             throw new IllegalArgumentException("invalid compression method");
170         }
171         this.method = method;
172     }
173 
174     /**
175      * Sets the compression level for subsequent entries which are DEFLATED.
176      * The default setting is DEFAULT_COMPRESSION.
177      * @param     level the compression level (0-9)
178      * @throws    IllegalArgumentException if the compression level is invalid
179      */
setLevel(int level)180     public void setLevel(int level) {
181         def.setLevel(level);
182     }
183 
184     /**
185      * Begins writing a new ZIP file entry and positions the stream to the
186      * start of the entry data. Closes the current entry if still active.
187      * <p>
188      * The default compression method will be used if no compression method
189      * was specified for the entry. When writing a compressed (DEFLATED)
190      * entry, and the compressed size has not been explicitly set with the
191      * {@link ZipEntry#setCompressedSize(long)} method, then the compressed
192      * size will be set to the actual compressed size after deflation.
193      * <p>
194      * The current time will be used if the entry has no set modification time.
195      *
196      * @param     e the ZIP entry to be written
197      * @throws    ZipException if a ZIP format error has occurred
198      * @throws    IOException if an I/O error has occurred
199      */
putNextEntry(ZipEntry e)200     public void putNextEntry(ZipEntry e) throws IOException {
201         ensureOpen();
202         if (current != null) {
203             closeEntry();       // close previous entry
204         }
205         if (e.xdostime == -1) {
206             // by default, do NOT use extended timestamps in extra
207             // data, for now.
208             e.setTime(System.currentTimeMillis());
209         }
210         if (e.method == -1) {
211             e.method = method;  // use default method
212         }
213         // store size, compressed size, and crc-32 in LOC header
214         e.flag = 0;
215         switch (e.method) {
216         case DEFLATED:
217             // If not set, store size, compressed size, and crc-32 in data
218             // descriptor immediately following the compressed entry data.
219             // Ignore the compressed size of a ZipEntry if it was implcitely set
220             // while reading that ZipEntry from a  ZipFile or ZipInputStream because
221             // we can't know the compression level of the source zip file/stream.
222             if (e.size  == -1 || e.csize == -1 || e.crc   == -1 || !e.csizeSet) {
223                 e.flag = 8;
224             }
225             break;
226         case STORED:
227             // compressed size, uncompressed size, and crc-32 must all be
228             // set for entries using STORED compression method
229             if (e.size == -1) {
230                 e.size = e.csize;
231             } else if (e.csize == -1) {
232                 e.csize = e.size;
233             } else if (e.size != e.csize) {
234                 throw new ZipException(
235                     "STORED entry where compressed != uncompressed size");
236             }
237             if (e.size == -1 || e.crc == -1) {
238                 throw new ZipException(
239                     "STORED entry missing size, compressed size, or crc-32");
240             }
241             break;
242         default:
243             throw new ZipException("unsupported compression method");
244         }
245         if (! names.add(e.name)) {
246             throw new ZipException("duplicate entry: " + e.name);
247         }
248         if (zc.isUTF8())
249             e.flag |= USE_UTF8;
250         current = new XEntry(e, written);
251         xentries.add(current);
252         writeLOC(current);
253     }
254 
255     /**
256      * Closes the current ZIP entry and positions the stream for writing
257      * the next entry.
258      * @throws    ZipException if a ZIP format error has occurred
259      * @throws    IOException if an I/O error has occurred
260      */
closeEntry()261     public void closeEntry() throws IOException {
262         ensureOpen();
263         if (current != null) {
264             try {
265                 ZipEntry e = current.entry;
266                 switch (e.method) {
267                     case DEFLATED -> {
268                         def.finish();
269                         while (!def.finished()) {
270                             deflate();
271                         }
272                         if ((e.flag & 8) == 0) {
273                             // verify size, compressed size, and crc-32 settings
274                             if (e.size != def.getBytesRead()) {
275                                 throw new ZipException(
276                                     "invalid entry size (expected " + e.size +
277                                     " but got " + def.getBytesRead() + " bytes)");
278                             }
279                             if (e.csize != def.getBytesWritten()) {
280                                 throw new ZipException(
281                                     "invalid entry compressed size (expected " +
282                                     e.csize + " but got " + def.getBytesWritten() + " bytes)");
283                             }
284                             if (e.crc != crc.getValue()) {
285                                 throw new ZipException(
286                                     "invalid entry CRC-32 (expected 0x" +
287                                     Long.toHexString(e.crc) + " but got 0x" +
288                                     Long.toHexString(crc.getValue()) + ")");
289                             }
290                         } else {
291                             e.size = def.getBytesRead();
292                             e.csize = def.getBytesWritten();
293                             e.crc = crc.getValue();
294                             writeEXT(e);
295                         }
296                         def.reset();
297                         written += e.csize;
298                     }
299                     case STORED -> {
300                         // we already know that both e.size and e.csize are the same
301                         if (e.size != written - locoff) {
302                             throw new ZipException(
303                                 "invalid entry size (expected " + e.size +
304                                 " but got " + (written - locoff) + " bytes)");
305                         }
306                         if (e.crc != crc.getValue()) {
307                             throw new ZipException(
308                                 "invalid entry crc-32 (expected 0x" +
309                                 Long.toHexString(e.crc) + " but got 0x" +
310                                 Long.toHexString(crc.getValue()) + ")");
311                         }
312                     }
313                     default -> throw new ZipException("invalid compression method");
314                 }
315                 crc.reset();
316                 current = null;
317             } catch (IOException e) {
318                 if (def.shouldFinish() && usesDefaultDeflater && !(e instanceof ZipException))
319                     def.end();
320                 throw e;
321             }
322         }
323     }
324 
325     /**
326      * Writes an array of bytes to the current ZIP entry data. This method
327      * will block until all the bytes are written.
328      * @param     b the data to be written
329      * @param     off the start offset in the data
330      * @param     len the number of bytes that are written
331      * @throws    ZipException if a ZIP file error has occurred
332      * @throws    IOException if an I/O error has occurred
333      */
write(byte[] b, int off, int len)334     public synchronized void write(byte[] b, int off, int len)
335         throws IOException
336     {
337         ensureOpen();
338         if (off < 0 || len < 0 || off > b.length - len) {
339             throw new IndexOutOfBoundsException();
340         } else if (len == 0) {
341             return;
342         }
343 
344         if (current == null) {
345             throw new ZipException("no current ZIP entry");
346         }
347         ZipEntry entry = current.entry;
348         switch (entry.method) {
349             case DEFLATED -> super.write(b, off, len);
350             case STORED -> {
351                 written += len;
352                 if (written - locoff > entry.size) {
353                     throw new ZipException(
354                         "attempt to write past end of STORED entry");
355                 }
356                 out.write(b, off, len);
357             }
358             default -> throw new ZipException("invalid compression method");
359         }
360         crc.update(b, off, len);
361     }
362 
363     /**
364      * Finishes writing the contents of the ZIP output stream without closing
365      * the underlying stream. Use this method when applying multiple filters
366      * in succession to the same output stream.
367      * @throws    ZipException if a ZIP file error has occurred
368      * @throws    IOException if an I/O exception has occurred
369      */
finish()370     public void finish() throws IOException {
371         ensureOpen();
372         if (finished) {
373             return;
374         }
375         if (current != null) {
376             closeEntry();
377         }
378         // write central directory
379         long off = written;
380         for (XEntry xentry : xentries)
381             writeCEN(xentry);
382         writeEND(off, written - off);
383         finished = true;
384     }
385 
386     /**
387      * Closes the ZIP output stream as well as the stream being filtered.
388      * @throws    ZipException if a ZIP file error has occurred
389      * @throws    IOException if an I/O error has occurred
390      */
close()391     public void close() throws IOException {
392         if (!closed) {
393             super.close();
394             closed = true;
395         }
396     }
397 
398     /*
399      * Writes local file (LOC) header for specified entry.
400      */
writeLOC(XEntry xentry)401     private void writeLOC(XEntry xentry) throws IOException {
402         ZipEntry e = xentry.entry;
403         int flag = e.flag;
404         boolean hasZip64 = false;
405         int elen = getExtraLen(e.extra);
406 
407         writeInt(LOCSIG);               // LOC header signature
408         if ((flag & 8) == 8) {
409             writeShort(version(e));     // version needed to extract
410             writeShort(flag);           // general purpose bit flag
411             writeShort(e.method);       // compression method
412             writeInt(e.xdostime);       // last modification time
413             // store size, uncompressed size, and crc-32 in data descriptor
414             // immediately following compressed entry data
415             writeInt(0);
416             writeInt(0);
417             writeInt(0);
418         } else {
419             if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
420                 hasZip64 = true;
421                 writeShort(45);         // ver 4.5 for zip64
422             } else {
423                 writeShort(version(e)); // version needed to extract
424             }
425             writeShort(flag);           // general purpose bit flag
426             writeShort(e.method);       // compression method
427             writeInt(e.xdostime);       // last modification time
428             writeInt(e.crc);            // crc-32
429             if (hasZip64) {
430                 writeInt(ZIP64_MAGICVAL);
431                 writeInt(ZIP64_MAGICVAL);
432                 elen += 20;        //headid(2) + size(2) + size(8) + csize(8)
433             } else {
434                 writeInt(e.csize);  // compressed size
435                 writeInt(e.size);   // uncompressed size
436             }
437         }
438         byte[] nameBytes = zc.getBytes(e.name);
439         writeShort(nameBytes.length);
440 
441         int elenEXTT = 0;         // info-zip extended timestamp
442         int flagEXTT = 0;
443         long umtime = -1;
444         long uatime = -1;
445         long uctime = -1;
446         if (e.mtime != null) {
447             elenEXTT += 4;
448             flagEXTT |= EXTT_FLAG_LMT;
449             umtime = fileTimeToUnixTime(e.mtime);
450         }
451         if (e.atime != null) {
452             elenEXTT += 4;
453             flagEXTT |= EXTT_FLAG_LAT;
454             uatime = fileTimeToUnixTime(e.atime);
455         }
456         if (e.ctime != null) {
457             elenEXTT += 4;
458             flagEXTT |= EXTT_FLAT_CT;
459             uctime = fileTimeToUnixTime(e.ctime);
460         }
461         if (flagEXTT != 0) {
462             // to use ntfs time if any m/a/ctime is beyond unixtime upper bound
463             if (umtime > UPPER_UNIXTIME_BOUND ||
464                 uatime > UPPER_UNIXTIME_BOUND ||
465                 uctime > UPPER_UNIXTIME_BOUND) {
466                 elen += 36;                // NTFS time, total 36 bytes
467             } else {
468                 elen += (elenEXTT + 5);    // headid(2) + size(2) + flag(1) + data
469             }
470         }
471         writeShort(elen);
472         writeBytes(nameBytes, 0, nameBytes.length);
473         if (hasZip64) {
474             writeShort(ZIP64_EXTID);
475             writeShort(16);
476             writeLong(e.size);
477             writeLong(e.csize);
478         }
479         if (flagEXTT != 0) {
480             if (umtime > UPPER_UNIXTIME_BOUND ||
481                 uatime > UPPER_UNIXTIME_BOUND ||
482                 uctime > UPPER_UNIXTIME_BOUND) {
483                 writeShort(EXTID_NTFS);    // id
484                 writeShort(32);            // data size
485                 writeInt(0);               // reserved
486                 writeShort(0x0001);        // NTFS attr tag
487                 writeShort(24);
488                 writeLong(e.mtime == null ? WINDOWS_TIME_NOT_AVAILABLE
489                                           : fileTimeToWinTime(e.mtime));
490                 writeLong(e.atime == null ? WINDOWS_TIME_NOT_AVAILABLE
491                                           : fileTimeToWinTime(e.atime));
492                 writeLong(e.ctime == null ? WINDOWS_TIME_NOT_AVAILABLE
493                                           : fileTimeToWinTime(e.ctime));
494             } else {
495                 writeShort(EXTID_EXTT);
496                 writeShort(elenEXTT + 1);  // flag + data
497                 writeByte(flagEXTT);
498                 if (e.mtime != null)
499                     writeInt(umtime);
500                 if (e.atime != null)
501                     writeInt(uatime);
502                 if (e.ctime != null)
503                     writeInt(uctime);
504             }
505         }
506         writeExtra(e.extra);
507         locoff = written;
508     }
509 
510     /*
511      * Writes extra data descriptor (EXT) for specified entry.
512      */
writeEXT(ZipEntry e)513     private void writeEXT(ZipEntry e) throws IOException {
514         writeInt(EXTSIG);           // EXT header signature
515         writeInt(e.crc);            // crc-32
516         if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
517             writeLong(e.csize);
518             writeLong(e.size);
519         } else {
520             writeInt(e.csize);          // compressed size
521             writeInt(e.size);           // uncompressed size
522         }
523     }
524 
525     /**
526      * Adds information about compatibility of file attribute information
527      * to a version value.
528      */
versionMadeBy(ZipEntry e, int version)529     private int versionMadeBy(ZipEntry e, int version) {
530         return (e.extraAttributes < 0) ? version :
531                 VERSION_MADE_BY_BASE_UNIX | (version & 0xff);
532     }
533 
534     /*
535      * Write central directory (CEN) header for specified entry.
536      * REMIND: add support for file attributes
537      */
writeCEN(XEntry xentry)538     private void writeCEN(XEntry xentry) throws IOException {
539         ZipEntry e  = xentry.entry;
540         int flag = e.flag;
541         int version = version(e);
542         long csize = e.csize;
543         long size = e.size;
544         long offset = xentry.offset;
545         int elenZIP64 = 0;
546         boolean hasZip64 = false;
547 
548         if (e.csize >= ZIP64_MAGICVAL) {
549             csize = ZIP64_MAGICVAL;
550             elenZIP64 += 8;              // csize(8)
551             hasZip64 = true;
552         }
553         if (e.size >= ZIP64_MAGICVAL) {
554             size = ZIP64_MAGICVAL;    // size(8)
555             elenZIP64 += 8;
556             hasZip64 = true;
557         }
558         if (xentry.offset >= ZIP64_MAGICVAL) {
559             offset = ZIP64_MAGICVAL;
560             elenZIP64 += 8;              // offset(8)
561             hasZip64 = true;
562         }
563         writeInt(CENSIG);           // CEN header signature
564         if (hasZip64) {
565             writeShort(versionMadeBy(e,45));         // ver 4.5 for zip64
566             writeShort(45);
567         } else {
568             writeShort(versionMadeBy(e, version));    // version made by
569             writeShort(version);    // version needed to extract
570         }
571         writeShort(flag);           // general purpose bit flag
572         writeShort(e.method);       // compression method
573         writeInt(e.xdostime);       // last modification time
574         writeInt(e.crc);            // crc-32
575         writeInt(csize);            // compressed size
576         writeInt(size);             // uncompressed size
577         byte[] nameBytes = zc.getBytes(e.name);
578         writeShort(nameBytes.length);
579 
580         int elen = getExtraLen(e.extra);
581         if (hasZip64) {
582             elen += (elenZIP64 + 4);// + headid(2) + datasize(2)
583         }
584         // cen info-zip extended timestamp only outputs mtime
585         // but set the flag for a/ctime, if present in loc
586         int flagEXTT = 0;
587         long umtime = -1;
588         long uatime = -1;
589         long uctime = -1;
590         if (e.mtime != null) {
591             flagEXTT |= EXTT_FLAG_LMT;
592             umtime = fileTimeToUnixTime(e.mtime);
593         }
594         if (e.atime != null) {
595             flagEXTT |= EXTT_FLAG_LAT;
596             uatime = fileTimeToUnixTime(e.atime);
597         }
598         if (e.ctime != null) {
599             flagEXTT |= EXTT_FLAT_CT;
600             uctime = fileTimeToUnixTime(e.ctime);
601         }
602         if (flagEXTT != 0) {
603             // to use ntfs time if any m/a/ctime is beyond unixtime upper bound
604             if (umtime > UPPER_UNIXTIME_BOUND ||
605                 uatime > UPPER_UNIXTIME_BOUND ||
606                 uctime > UPPER_UNIXTIME_BOUND) {
607                 elen += 36;         // NTFS time total 36 bytes
608             } else {
609                 elen += 5;          // headid(2) + sz(2) + flag(1)
610                 if (e.mtime != null)
611                     elen += 4;      // + mtime (4)
612             }
613         }
614         writeShort(elen);
615         byte[] commentBytes;
616         if (e.comment != null) {
617             commentBytes = zc.getBytes(e.comment);
618             writeShort(Math.min(commentBytes.length, 0xffff));
619         } else {
620             commentBytes = null;
621             writeShort(0);
622         }
623         writeShort(0);              // starting disk number
624         writeShort(0);              // internal file attributes (unused)
625         // extra file attributes, used for storing posix permissions etc.
626         writeInt(e.extraAttributes > 0 ? e.extraAttributes << 16 : 0);
627         writeInt(offset);           // relative offset of local header
628         writeBytes(nameBytes, 0, nameBytes.length);
629 
630         // take care of EXTID_ZIP64 and EXTID_EXTT
631         if (hasZip64) {
632             writeShort(ZIP64_EXTID);// Zip64 extra
633             writeShort(elenZIP64);
634             if (size == ZIP64_MAGICVAL)
635                 writeLong(e.size);
636             if (csize == ZIP64_MAGICVAL)
637                 writeLong(e.csize);
638             if (offset == ZIP64_MAGICVAL)
639                 writeLong(xentry.offset);
640         }
641         if (flagEXTT != 0) {
642             if (umtime > UPPER_UNIXTIME_BOUND ||
643                 uatime > UPPER_UNIXTIME_BOUND ||
644                 uctime > UPPER_UNIXTIME_BOUND) {
645                 writeShort(EXTID_NTFS);    // id
646                 writeShort(32);            // data size
647                 writeInt(0);               // reserved
648                 writeShort(0x0001);        // NTFS attr tag
649                 writeShort(24);
650                 writeLong(e.mtime == null ? WINDOWS_TIME_NOT_AVAILABLE
651                                           : fileTimeToWinTime(e.mtime));
652                 writeLong(e.atime == null ? WINDOWS_TIME_NOT_AVAILABLE
653                                           : fileTimeToWinTime(e.atime));
654                 writeLong(e.ctime == null ? WINDOWS_TIME_NOT_AVAILABLE
655                                           : fileTimeToWinTime(e.ctime));
656             } else {
657                 writeShort(EXTID_EXTT);
658                 if (e.mtime != null) {
659                     writeShort(5);      // flag + mtime
660                     writeByte(flagEXTT);
661                     writeInt(umtime);
662                 } else {
663                     writeShort(1);      // flag only
664                     writeByte(flagEXTT);
665                 }
666             }
667         }
668         writeExtra(e.extra);
669         if (commentBytes != null) {
670             writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff));
671         }
672     }
673 
674     /*
675      * Writes end of central directory (END) header.
676      */
writeEND(long off, long len)677     private void writeEND(long off, long len) throws IOException {
678         boolean hasZip64 = false;
679         long xlen = len;
680         long xoff = off;
681         if (xlen >= ZIP64_MAGICVAL) {
682             xlen = ZIP64_MAGICVAL;
683             hasZip64 = true;
684         }
685         if (xoff >= ZIP64_MAGICVAL) {
686             xoff = ZIP64_MAGICVAL;
687             hasZip64 = true;
688         }
689         int count = xentries.size();
690         if (count >= ZIP64_MAGICCOUNT) {
691             hasZip64 |= !inhibitZip64;
692             if (hasZip64) {
693                 count = ZIP64_MAGICCOUNT;
694             }
695         }
696         if (hasZip64) {
697             long off64 = written;
698             //zip64 end of central directory record
699             writeInt(ZIP64_ENDSIG);        // zip64 END record signature
700             writeLong(ZIP64_ENDHDR - 12);  // size of zip64 end
701             writeShort(45);                // version made by
702             writeShort(45);                // version needed to extract
703             writeInt(0);                   // number of this disk
704             writeInt(0);                   // central directory start disk
705             writeLong(xentries.size());    // number of directory entires on disk
706             writeLong(xentries.size());    // number of directory entires
707             writeLong(len);                // length of central directory
708             writeLong(off);                // offset of central directory
709 
710             //zip64 end of central directory locator
711             writeInt(ZIP64_LOCSIG);        // zip64 END locator signature
712             writeInt(0);                   // zip64 END start disk
713             writeLong(off64);              // offset of zip64 END
714             writeInt(1);                   // total number of disks (?)
715         }
716         writeInt(ENDSIG);                 // END record signature
717         writeShort(0);                    // number of this disk
718         writeShort(0);                    // central directory start disk
719         writeShort(count);                // number of directory entries on disk
720         writeShort(count);                // total number of directory entries
721         writeInt(xlen);                   // length of central directory
722         writeInt(xoff);                   // offset of central directory
723         if (comment != null) {            // zip file comment
724             writeShort(comment.length);
725             writeBytes(comment, 0, comment.length);
726         } else {
727             writeShort(0);
728         }
729     }
730 
731     /*
732      * Returns the length of extra data without EXTT and ZIP64.
733      */
getExtraLen(byte[] extra)734     private int getExtraLen(byte[] extra) {
735         if (extra == null)
736             return 0;
737         int skipped = 0;
738         int len = extra.length;
739         int off = 0;
740         while (off + 4 <= len) {
741             int tag = get16(extra, off);
742             int sz = get16(extra, off + 2);
743             if (sz < 0 || (off + 4 + sz) > len) {
744                 break;
745             }
746             if (tag == EXTID_EXTT || tag == EXTID_ZIP64) {
747                 skipped += (sz + 4);
748             }
749             off += (sz + 4);
750         }
751         return len - skipped;
752     }
753 
754     /*
755      * Writes extra data without EXTT and ZIP64.
756      *
757      * Extra timestamp and ZIP64 data is handled/output separately
758      * in writeLOC and writeCEN.
759      */
writeExtra(byte[] extra)760     private void writeExtra(byte[] extra) throws IOException {
761         if (extra != null) {
762             int len = extra.length;
763             int off = 0;
764             while (off + 4 <= len) {
765                 int tag = get16(extra, off);
766                 int sz = get16(extra, off + 2);
767                 if (sz < 0 || (off + 4 + sz) > len) {
768                     writeBytes(extra, off, len - off);
769                     return;
770                 }
771                 if (tag != EXTID_EXTT && tag != EXTID_ZIP64) {
772                     writeBytes(extra, off, sz + 4);
773                 }
774                 off += (sz + 4);
775             }
776             if (off < len) {
777                 writeBytes(extra, off, len - off);
778             }
779         }
780     }
781 
782     /*
783      * Writes a 8-bit byte to the output stream.
784      */
writeByte(int v)785     private void writeByte(int v) throws IOException {
786         OutputStream out = this.out;
787         out.write(v & 0xff);
788         written += 1;
789     }
790 
791     /*
792      * Writes a 16-bit short to the output stream in little-endian byte order.
793      */
writeShort(int v)794     private void writeShort(int v) throws IOException {
795         OutputStream out = this.out;
796         out.write((v >>> 0) & 0xff);
797         out.write((v >>> 8) & 0xff);
798         written += 2;
799     }
800 
801     /*
802      * Writes a 32-bit int to the output stream in little-endian byte order.
803      */
writeInt(long v)804     private void writeInt(long v) throws IOException {
805         OutputStream out = this.out;
806         out.write((int)((v >>>  0) & 0xff));
807         out.write((int)((v >>>  8) & 0xff));
808         out.write((int)((v >>> 16) & 0xff));
809         out.write((int)((v >>> 24) & 0xff));
810         written += 4;
811     }
812 
813     /*
814      * Writes a 64-bit int to the output stream in little-endian byte order.
815      */
writeLong(long v)816     private void writeLong(long v) throws IOException {
817         OutputStream out = this.out;
818         out.write((int)((v >>>  0) & 0xff));
819         out.write((int)((v >>>  8) & 0xff));
820         out.write((int)((v >>> 16) & 0xff));
821         out.write((int)((v >>> 24) & 0xff));
822         out.write((int)((v >>> 32) & 0xff));
823         out.write((int)((v >>> 40) & 0xff));
824         out.write((int)((v >>> 48) & 0xff));
825         out.write((int)((v >>> 56) & 0xff));
826         written += 8;
827     }
828 
829     /*
830      * Writes an array of bytes to the output stream.
831      */
writeBytes(byte[] b, int off, int len)832     private void writeBytes(byte[] b, int off, int len) throws IOException {
833         super.out.write(b, off, len);
834         written += len;
835     }
836 }
837