1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 * Copyright (c) 1996, 2015, 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
27package java.util.zip;
28
29import java.io.OutputStream;
30import java.io.IOException;
31import java.nio.charset.Charset;
32import java.nio.charset.StandardCharsets;
33import java.util.Vector;
34import java.util.HashSet;
35import static java.util.zip.ZipConstants64.*;
36import static java.util.zip.ZipUtils.*;
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 */
45public
46class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
47
48    /**
49     * Whether to use ZIP64 for zip files with more than 64k entries.
50     * Until ZIP64 support in zip implementations is ubiquitous, this
51     * system property allows the creation of zip files which can be
52     * read by legacy zip implementations which tolerate "incorrect"
53     * total entry count fields, such as the ones in jdk6, and even
54     * some in jdk7.
55     */
56    // Android-changed: Force to false.
57    private static final boolean inhibitZip64 = false;
58    //  Boolean.parseBoolean(
59    //      java.security.AccessController.doPrivileged(
60    //          new sun.security.action.GetPropertyAction(
61    //              "jdk.util.zip.inhibitZip64", "false")));
62
63    private static class XEntry {
64        final ZipEntry entry;
65        final long offset;
66        public XEntry(ZipEntry entry, long offset) {
67            this.entry = entry;
68            this.offset = offset;
69        }
70    }
71
72    private XEntry current;
73    private Vector<XEntry> xentries = new Vector<>();
74    private HashSet<String> names = new HashSet<>();
75    private CRC32 crc = new CRC32();
76    private long written = 0;
77    private long locoff = 0;
78    private byte[] comment;
79    private int method = DEFLATED;
80    private boolean finished;
81
82    private boolean closed = false;
83
84    private final ZipCoder zc;
85
86    private static int version(ZipEntry e) throws ZipException {
87        switch (e.method) {
88        case DEFLATED: return 20;
89        case STORED:   return 10;
90        default: throw new ZipException("unsupported compression method");
91        }
92    }
93
94    /**
95     * Checks to make sure that this stream has not been closed.
96     */
97    private void ensureOpen() throws IOException {
98        if (closed) {
99            throw new IOException("Stream closed");
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     */
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     */
134    public ZipOutputStream(OutputStream out, Charset charset) {
135        super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
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     * @exception IllegalArgumentException if the length of the specified
146     *            ZIP file comment is greater than 0xFFFF bytes
147     */
148    public void setComment(String comment) {
149        if (comment != null) {
150            this.comment = zc.getBytes(comment);
151            if (this.comment.length > 0xffff)
152                throw new IllegalArgumentException("ZIP file comment too long.");
153        }
154    }
155
156    /**
157     * Sets the default compression method for subsequent entries. This
158     * default will be used whenever the compression method is not specified
159     * for an individual ZIP file entry, and is initially set to DEFLATED.
160     * @param method the default compression method
161     * @exception IllegalArgumentException if the specified compression method
162     *            is invalid
163     */
164    public void setMethod(int method) {
165        if (method != DEFLATED && method != STORED) {
166            throw new IllegalArgumentException("invalid compression method");
167        }
168        this.method = method;
169    }
170
171    /**
172     * Sets the compression level for subsequent entries which are DEFLATED.
173     * The default setting is DEFAULT_COMPRESSION.
174     * @param level the compression level (0-9)
175     * @exception IllegalArgumentException if the compression level is invalid
176     */
177    public void setLevel(int level) {
178        def.setLevel(level);
179    }
180
181    /**
182     * Begins writing a new ZIP file entry and positions the stream to the
183     * start of the entry data. Closes the current entry if still active.
184     * The default compression method will be used if no compression method
185     * was specified for the entry, and the current time will be used if
186     * the entry has no set modification time.
187     * @param e the ZIP entry to be written
188     * @exception ZipException if a ZIP format error has occurred
189     * @exception IOException if an I/O error has occurred
190     */
191    public void putNextEntry(ZipEntry e) throws IOException {
192        ensureOpen();
193        if (current != null) {
194            closeEntry();       // close previous entry
195        }
196        if (e.xdostime == -1) {
197            // by default, do NOT use extended timestamps in extra
198            // data, for now.
199            e.setTime(System.currentTimeMillis());
200        }
201        if (e.method == -1) {
202            e.method = method;  // use default method
203        }
204        // store size, compressed size, and crc-32 in LOC header
205        e.flag = 0;
206        switch (e.method) {
207        case DEFLATED:
208            // store size, compressed size, and crc-32 in data descriptor
209            // immediately following the compressed entry data
210            if (e.size  == -1 || e.csize == -1 || e.crc   == -1)
211                e.flag = 8;
212
213            break;
214        case STORED:
215            // compressed size, uncompressed size, and crc-32 must all be
216            // set for entries using STORED compression method
217            if (e.size == -1) {
218                e.size = e.csize;
219            } else if (e.csize == -1) {
220                e.csize = e.size;
221            } else if (e.size != e.csize) {
222                throw new ZipException(
223                    "STORED entry where compressed != uncompressed size");
224            }
225            if (e.size == -1 || e.crc == -1) {
226                throw new ZipException(
227                    "STORED entry missing size, compressed size, or crc-32");
228            }
229            break;
230        default:
231            throw new ZipException("unsupported compression method");
232        }
233        if (! names.add(e.name)) {
234            throw new ZipException("duplicate entry: " + e.name);
235        }
236        if (zc.isUTF8())
237            e.flag |= EFS;
238        current = new XEntry(e, written);
239        xentries.add(current);
240        writeLOC(current);
241    }
242
243    /**
244     * Closes the current ZIP entry and positions the stream for writing
245     * the next entry.
246     * @exception ZipException if a ZIP format error has occurred
247     * @exception IOException if an I/O error has occurred
248     */
249    public void closeEntry() throws IOException {
250        ensureOpen();
251        if (current != null) {
252            ZipEntry e = current.entry;
253            switch (e.method) {
254            case DEFLATED:
255                def.finish();
256                while (!def.finished()) {
257                    deflate();
258                }
259                if ((e.flag & 8) == 0) {
260                    // verify size, compressed size, and crc-32 settings
261                    if (e.size != def.getBytesRead()) {
262                        throw new ZipException(
263                            "invalid entry size (expected " + e.size +
264                            " but got " + def.getBytesRead() + " bytes)");
265                    }
266                    if (e.csize != def.getBytesWritten()) {
267                        throw new ZipException(
268                            "invalid entry compressed size (expected " +
269                            e.csize + " but got " + def.getBytesWritten() + " bytes)");
270                    }
271                    if (e.crc != crc.getValue()) {
272                        throw new ZipException(
273                            "invalid entry CRC-32 (expected 0x" +
274                            Long.toHexString(e.crc) + " but got 0x" +
275                            Long.toHexString(crc.getValue()) + ")");
276                    }
277                } else {
278                    e.size  = def.getBytesRead();
279                    e.csize = def.getBytesWritten();
280                    e.crc = crc.getValue();
281                    writeEXT(e);
282                }
283                def.reset();
284                written += e.csize;
285                break;
286            case STORED:
287                // we already know that both e.size and e.csize are the same
288                if (e.size != written - locoff) {
289                    throw new ZipException(
290                        "invalid entry size (expected " + e.size +
291                        " but got " + (written - locoff) + " bytes)");
292                }
293                if (e.crc != crc.getValue()) {
294                    throw new ZipException(
295                         "invalid entry crc-32 (expected 0x" +
296                         Long.toHexString(e.crc) + " but got 0x" +
297                         Long.toHexString(crc.getValue()) + ")");
298                }
299                break;
300            default:
301                throw new ZipException("invalid compression method");
302            }
303            crc.reset();
304            current = null;
305        }
306    }
307
308    /**
309     * Writes an array of bytes to the current ZIP entry data. This method
310     * will block until all the bytes are written.
311     * @param b the data to be written
312     * @param off the start offset in the data
313     * @param len the number of bytes that are written
314     * @exception ZipException if a ZIP file error has occurred
315     * @exception IOException if an I/O error has occurred
316     */
317    public synchronized void write(byte[] b, int off, int len)
318        throws IOException
319    {
320        ensureOpen();
321        if (off < 0 || len < 0 || off > b.length - len) {
322            throw new IndexOutOfBoundsException();
323        } else if (len == 0) {
324            return;
325        }
326
327        if (current == null) {
328            throw new ZipException("no current ZIP entry");
329        }
330        ZipEntry entry = current.entry;
331        switch (entry.method) {
332        case DEFLATED:
333            super.write(b, off, len);
334            break;
335        case STORED:
336            written += len;
337            if (written - locoff > entry.size) {
338                throw new ZipException(
339                    "attempt to write past end of STORED entry");
340            }
341            out.write(b, off, len);
342            break;
343        default:
344            throw new ZipException("invalid compression method");
345        }
346        crc.update(b, off, len);
347    }
348
349    /**
350     * Finishes writing the contents of the ZIP output stream without closing
351     * the underlying stream. Use this method when applying multiple filters
352     * in succession to the same output stream.
353     * @exception ZipException if a ZIP file error has occurred
354     * @exception IOException if an I/O exception has occurred
355     */
356    public void finish() throws IOException {
357        ensureOpen();
358        if (finished) {
359            return;
360        }
361        // Android-changed: Fix for ZipOutputStreamTest#testCreateEmpty
362        if (xentries.isEmpty()) {
363            throw new ZipException("No entries");
364        }
365        if (current != null) {
366            closeEntry();
367        }
368        // write central directory
369        long off = written;
370        for (XEntry xentry : xentries)
371            writeCEN(xentry);
372        writeEND(off, written - off);
373        finished = true;
374    }
375
376    /**
377     * Closes the ZIP output stream as well as the stream being filtered.
378     * @exception ZipException if a ZIP file error has occurred
379     * @exception IOException if an I/O error has occurred
380     */
381    public void close() throws IOException {
382        if (!closed) {
383            super.close();
384            closed = true;
385        }
386    }
387
388    /*
389     * Writes local file (LOC) header for specified entry.
390     */
391    private void writeLOC(XEntry xentry) throws IOException {
392        ZipEntry e = xentry.entry;
393        int flag = e.flag;
394        boolean hasZip64 = false;
395        int elen = getExtraLen(e.extra);
396
397        writeInt(LOCSIG);               // LOC header signature
398        if ((flag & 8) == 8) {
399            writeShort(version(e));     // version needed to extract
400            writeShort(flag);           // general purpose bit flag
401            writeShort(e.method);       // compression method
402            writeInt(e.xdostime);       // last modification time
403            // store size, uncompressed size, and crc-32 in data descriptor
404            // immediately following compressed entry data
405            writeInt(0);
406            writeInt(0);
407            writeInt(0);
408        } else {
409            if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
410                hasZip64 = true;
411                writeShort(45);         // ver 4.5 for zip64
412            } else {
413                writeShort(version(e)); // version needed to extract
414            }
415            writeShort(flag);           // general purpose bit flag
416            writeShort(e.method);       // compression method
417            writeInt(e.xdostime);       // last modification time
418            writeInt(e.crc);            // crc-32
419            if (hasZip64) {
420                writeInt(ZIP64_MAGICVAL);
421                writeInt(ZIP64_MAGICVAL);
422                elen += 20;        //headid(2) + size(2) + size(8) + csize(8)
423            } else {
424                writeInt(e.csize);  // compressed size
425                writeInt(e.size);   // uncompressed size
426            }
427        }
428        byte[] nameBytes = zc.getBytes(e.name);
429        writeShort(nameBytes.length);
430
431        int elenEXTT = 0;               // info-zip extended timestamp
432        int flagEXTT = 0;
433        if (e.mtime != null) {
434            elenEXTT += 4;
435            flagEXTT |= EXTT_FLAG_LMT;
436        }
437        if (e.atime != null) {
438            elenEXTT += 4;
439            flagEXTT |= EXTT_FLAG_LAT;
440        }
441        if (e.ctime != null) {
442            elenEXTT += 4;
443            flagEXTT |= EXTT_FLAT_CT;
444        }
445        if (flagEXTT != 0)
446            elen += (elenEXTT + 5);    // headid(2) + size(2) + flag(1) + data
447        writeShort(elen);
448        writeBytes(nameBytes, 0, nameBytes.length);
449        if (hasZip64) {
450            writeShort(ZIP64_EXTID);
451            writeShort(16);
452            writeLong(e.size);
453            writeLong(e.csize);
454        }
455        if (flagEXTT != 0) {
456            writeShort(EXTID_EXTT);
457            writeShort(elenEXTT + 1);      // flag + data
458            writeByte(flagEXTT);
459            if (e.mtime != null)
460                writeInt(fileTimeToUnixTime(e.mtime));
461            if (e.atime != null)
462                writeInt(fileTimeToUnixTime(e.atime));
463            if (e.ctime != null)
464                writeInt(fileTimeToUnixTime(e.ctime));
465        }
466        writeExtra(e.extra);
467        locoff = written;
468    }
469
470    /*
471     * Writes extra data descriptor (EXT) for specified entry.
472     */
473    private void writeEXT(ZipEntry e) throws IOException {
474        writeInt(EXTSIG);           // EXT header signature
475        writeInt(e.crc);            // crc-32
476        if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
477            writeLong(e.csize);
478            writeLong(e.size);
479        } else {
480            writeInt(e.csize);          // compressed size
481            writeInt(e.size);           // uncompressed size
482        }
483    }
484
485    /*
486     * Write central directory (CEN) header for specified entry.
487     * REMIND: add support for file attributes
488     */
489    private void writeCEN(XEntry xentry) throws IOException {
490        ZipEntry e  = xentry.entry;
491        int flag = e.flag;
492        int version = version(e);
493        long csize = e.csize;
494        long size = e.size;
495        long offset = xentry.offset;
496        int elenZIP64 = 0;
497        boolean hasZip64 = false;
498
499        if (e.csize >= ZIP64_MAGICVAL) {
500            csize = ZIP64_MAGICVAL;
501            elenZIP64 += 8;              // csize(8)
502            hasZip64 = true;
503        }
504        if (e.size >= ZIP64_MAGICVAL) {
505            size = ZIP64_MAGICVAL;    // size(8)
506            elenZIP64 += 8;
507            hasZip64 = true;
508        }
509        if (xentry.offset >= ZIP64_MAGICVAL) {
510            offset = ZIP64_MAGICVAL;
511            elenZIP64 += 8;              // offset(8)
512            hasZip64 = true;
513        }
514        writeInt(CENSIG);           // CEN header signature
515        if (hasZip64) {
516            writeShort(45);         // ver 4.5 for zip64
517            writeShort(45);
518        } else {
519            writeShort(version);    // version made by
520            writeShort(version);    // version needed to extract
521        }
522        writeShort(flag);           // general purpose bit flag
523        writeShort(e.method);       // compression method
524        writeInt(e.xdostime);       // last modification time
525        writeInt(e.crc);            // crc-32
526        writeInt(csize);            // compressed size
527        writeInt(size);             // uncompressed size
528        byte[] nameBytes = zc.getBytes(e.name);
529        writeShort(nameBytes.length);
530
531        int elen = getExtraLen(e.extra);
532        if (hasZip64) {
533            elen += (elenZIP64 + 4);// + headid(2) + datasize(2)
534        }
535        // cen info-zip extended timestamp only outputs mtime
536        // but set the flag for a/ctime, if present in loc
537        int flagEXTT = 0;
538        if (e.mtime != null) {
539            elen += 4;              // + mtime(4)
540            flagEXTT |= EXTT_FLAG_LMT;
541        }
542        if (e.atime != null) {
543            flagEXTT |= EXTT_FLAG_LAT;
544        }
545        if (e.ctime != null) {
546            flagEXTT |= EXTT_FLAT_CT;
547        }
548        if (flagEXTT != 0) {
549            elen += 5;             // headid + sz + flag
550        }
551        writeShort(elen);
552        byte[] commentBytes;
553        if (e.comment != null) {
554            commentBytes = zc.getBytes(e.comment);
555            writeShort(Math.min(commentBytes.length, 0xffff));
556        } else {
557            commentBytes = null;
558            writeShort(0);
559        }
560        writeShort(0);              // starting disk number
561        writeShort(0);              // internal file attributes (unused)
562        writeInt(0);                // external file attributes (unused)
563        writeInt(offset);           // relative offset of local header
564        writeBytes(nameBytes, 0, nameBytes.length);
565
566        // take care of EXTID_ZIP64 and EXTID_EXTT
567        if (hasZip64) {
568            writeShort(ZIP64_EXTID);// Zip64 extra
569            writeShort(elenZIP64);
570            if (size == ZIP64_MAGICVAL)
571                writeLong(e.size);
572            if (csize == ZIP64_MAGICVAL)
573                writeLong(e.csize);
574            if (offset == ZIP64_MAGICVAL)
575                writeLong(xentry.offset);
576        }
577        if (flagEXTT != 0) {
578            writeShort(EXTID_EXTT);
579            if (e.mtime != null) {
580                writeShort(5);      // flag + mtime
581                writeByte(flagEXTT);
582                writeInt(fileTimeToUnixTime(e.mtime));
583            } else {
584                writeShort(1);      // flag only
585                writeByte(flagEXTT);
586            }
587        }
588        writeExtra(e.extra);
589        if (commentBytes != null) {
590            writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff));
591        }
592    }
593
594    /*
595     * Writes end of central directory (END) header.
596     */
597    private void writeEND(long off, long len) throws IOException {
598        boolean hasZip64 = false;
599        long xlen = len;
600        long xoff = off;
601        if (xlen >= ZIP64_MAGICVAL) {
602            xlen = ZIP64_MAGICVAL;
603            hasZip64 = true;
604        }
605        if (xoff >= ZIP64_MAGICVAL) {
606            xoff = ZIP64_MAGICVAL;
607            hasZip64 = true;
608        }
609        int count = xentries.size();
610        if (count >= ZIP64_MAGICCOUNT) {
611            hasZip64 |= !inhibitZip64;
612            if (hasZip64) {
613                count = ZIP64_MAGICCOUNT;
614            }
615        }
616        if (hasZip64) {
617            long off64 = written;
618            //zip64 end of central directory record
619            writeInt(ZIP64_ENDSIG);        // zip64 END record signature
620            writeLong(ZIP64_ENDHDR - 12);  // size of zip64 end
621            writeShort(45);                // version made by
622            writeShort(45);                // version needed to extract
623            writeInt(0);                   // number of this disk
624            writeInt(0);                   // central directory start disk
625            writeLong(xentries.size());    // number of directory entires on disk
626            writeLong(xentries.size());    // number of directory entires
627            writeLong(len);                // length of central directory
628            writeLong(off);                // offset of central directory
629
630            //zip64 end of central directory locator
631            writeInt(ZIP64_LOCSIG);        // zip64 END locator signature
632            writeInt(0);                   // zip64 END start disk
633            writeLong(off64);              // offset of zip64 END
634            writeInt(1);                   // total number of disks (?)
635        }
636        writeInt(ENDSIG);                 // END record signature
637        writeShort(0);                    // number of this disk
638        writeShort(0);                    // central directory start disk
639        writeShort(count);                // number of directory entries on disk
640        writeShort(count);                // total number of directory entries
641        writeInt(xlen);                   // length of central directory
642        writeInt(xoff);                   // offset of central directory
643        if (comment != null) {            // zip file comment
644            writeShort(comment.length);
645            writeBytes(comment, 0, comment.length);
646        } else {
647            writeShort(0);
648        }
649    }
650
651    /*
652     * Returns the length of extra data without EXTT and ZIP64.
653     */
654    private int getExtraLen(byte[] extra) {
655        if (extra == null)
656            return 0;
657        int skipped = 0;
658        int len = extra.length;
659        int off = 0;
660        while (off + 4 <= len) {
661            int tag = get16(extra, off);
662            int sz = get16(extra, off + 2);
663            if (sz < 0 || (off + 4 + sz) > len) {
664                break;
665            }
666            if (tag == EXTID_EXTT || tag == EXTID_ZIP64) {
667                skipped += (sz + 4);
668            }
669            off += (sz + 4);
670        }
671        return len - skipped;
672    }
673
674    /*
675     * Writes extra data without EXTT and ZIP64.
676     *
677     * Extra timestamp and ZIP64 data is handled/output separately
678     * in writeLOC and writeCEN.
679     */
680    private void writeExtra(byte[] extra) throws IOException {
681        if (extra != null) {
682            int len = extra.length;
683            int off = 0;
684            while (off + 4 <= len) {
685                int tag = get16(extra, off);
686                int sz = get16(extra, off + 2);
687                if (sz < 0 || (off + 4 + sz) > len) {
688                    writeBytes(extra, off, len - off);
689                    return;
690                }
691                if (tag != EXTID_EXTT && tag != EXTID_ZIP64) {
692                    writeBytes(extra, off, sz + 4);
693                }
694                off += (sz + 4);
695            }
696            if (off < len) {
697                writeBytes(extra, off, len - off);
698            }
699        }
700    }
701
702    /*
703     * Writes a 8-bit byte to the output stream.
704     */
705    private void writeByte(int v) throws IOException {
706        OutputStream out = this.out;
707        out.write(v & 0xff);
708        written += 1;
709    }
710
711    /*
712     * Writes a 16-bit short to the output stream in little-endian byte order.
713     */
714    private void writeShort(int v) throws IOException {
715        OutputStream out = this.out;
716        out.write((v >>> 0) & 0xff);
717        out.write((v >>> 8) & 0xff);
718        written += 2;
719    }
720
721    /*
722     * Writes a 32-bit int to the output stream in little-endian byte order.
723     */
724    private void writeInt(long v) throws IOException {
725        OutputStream out = this.out;
726        out.write((int)((v >>>  0) & 0xff));
727        out.write((int)((v >>>  8) & 0xff));
728        out.write((int)((v >>> 16) & 0xff));
729        out.write((int)((v >>> 24) & 0xff));
730        written += 4;
731    }
732
733    /*
734     * Writes a 64-bit int to the output stream in little-endian byte order.
735     */
736    private void writeLong(long v) throws IOException {
737        OutputStream out = this.out;
738        out.write((int)((v >>>  0) & 0xff));
739        out.write((int)((v >>>  8) & 0xff));
740        out.write((int)((v >>> 16) & 0xff));
741        out.write((int)((v >>> 24) & 0xff));
742        out.write((int)((v >>> 32) & 0xff));
743        out.write((int)((v >>> 40) & 0xff));
744        out.write((int)((v >>> 48) & 0xff));
745        out.write((int)((v >>> 56) & 0xff));
746        written += 8;
747    }
748
749    /*
750     * Writes an array of bytes to the output stream.
751     */
752    private void writeBytes(byte[] b, int off, int len) throws IOException {
753        super.out.write(b, off, len);
754        written += len;
755    }
756}
757