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