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 
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 
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  */
45 public
46 class 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;
XEntry(ZipEntry entry, 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 
version(ZipEntry e)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      */
ensureOpen()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      */
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, 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      */
setComment(String comment)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      */
setMethod(int method)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      */
setLevel(int level)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      */
putNextEntry(ZipEntry e)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      */
closeEntry()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      */
write(byte[] b, int off, int len)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      */
finish()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      */
close()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      */
writeLOC(XEntry xentry)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      */
writeEXT(ZipEntry e)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      */
writeCEN(XEntry xentry)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      */
writeEND(long off, long len)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      */
getExtraLen(byte[] extra)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      */
writeExtra(byte[] extra)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      */
writeByte(int v)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      */
writeShort(int v)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      */
writeInt(long v)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      */
writeLong(long v)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      */
writeBytes(byte[] b, int off, int len)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