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: Always allow use of Zip64. 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 |= USE_UTF8; 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 if (current != null) { 362 closeEntry(); 363 } 364 // write central directory 365 long off = written; 366 for (XEntry xentry : xentries) 367 writeCEN(xentry); 368 writeEND(off, written - off); 369 finished = true; 370 } 371 372 /** 373 * Closes the ZIP output stream as well as the stream being filtered. 374 * @exception ZipException if a ZIP file error has occurred 375 * @exception IOException if an I/O error has occurred 376 */ close()377 public void close() throws IOException { 378 if (!closed) { 379 super.close(); 380 closed = true; 381 } 382 } 383 384 /* 385 * Writes local file (LOC) header for specified entry. 386 */ writeLOC(XEntry xentry)387 private void writeLOC(XEntry xentry) throws IOException { 388 ZipEntry e = xentry.entry; 389 int flag = e.flag; 390 boolean hasZip64 = false; 391 int elen = getExtraLen(e.extra); 392 393 writeInt(LOCSIG); // LOC header signature 394 if ((flag & 8) == 8) { 395 writeShort(version(e)); // version needed to extract 396 writeShort(flag); // general purpose bit flag 397 writeShort(e.method); // compression method 398 writeInt(e.xdostime); // last modification time 399 // store size, uncompressed size, and crc-32 in data descriptor 400 // immediately following compressed entry data 401 writeInt(0); 402 writeInt(0); 403 writeInt(0); 404 } else { 405 if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) { 406 hasZip64 = true; 407 writeShort(45); // ver 4.5 for zip64 408 } else { 409 writeShort(version(e)); // version needed to extract 410 } 411 writeShort(flag); // general purpose bit flag 412 writeShort(e.method); // compression method 413 writeInt(e.xdostime); // last modification time 414 writeInt(e.crc); // crc-32 415 if (hasZip64) { 416 writeInt(ZIP64_MAGICVAL); 417 writeInt(ZIP64_MAGICVAL); 418 elen += 20; //headid(2) + size(2) + size(8) + csize(8) 419 } else { 420 writeInt(e.csize); // compressed size 421 writeInt(e.size); // uncompressed size 422 } 423 } 424 byte[] nameBytes = zc.getBytes(e.name); 425 writeShort(nameBytes.length); 426 427 int elenEXTT = 0; // info-zip extended timestamp 428 int flagEXTT = 0; 429 if (e.mtime != null) { 430 elenEXTT += 4; 431 flagEXTT |= EXTT_FLAG_LMT; 432 } 433 if (e.atime != null) { 434 elenEXTT += 4; 435 flagEXTT |= EXTT_FLAG_LAT; 436 } 437 if (e.ctime != null) { 438 elenEXTT += 4; 439 flagEXTT |= EXTT_FLAT_CT; 440 } 441 if (flagEXTT != 0) 442 elen += (elenEXTT + 5); // headid(2) + size(2) + flag(1) + data 443 writeShort(elen); 444 writeBytes(nameBytes, 0, nameBytes.length); 445 if (hasZip64) { 446 writeShort(ZIP64_EXTID); 447 writeShort(16); 448 writeLong(e.size); 449 writeLong(e.csize); 450 } 451 if (flagEXTT != 0) { 452 writeShort(EXTID_EXTT); 453 writeShort(elenEXTT + 1); // flag + data 454 writeByte(flagEXTT); 455 if (e.mtime != null) 456 writeInt(fileTimeToUnixTime(e.mtime)); 457 if (e.atime != null) 458 writeInt(fileTimeToUnixTime(e.atime)); 459 if (e.ctime != null) 460 writeInt(fileTimeToUnixTime(e.ctime)); 461 } 462 writeExtra(e.extra); 463 locoff = written; 464 } 465 466 /* 467 * Writes extra data descriptor (EXT) for specified entry. 468 */ writeEXT(ZipEntry e)469 private void writeEXT(ZipEntry e) throws IOException { 470 writeInt(EXTSIG); // EXT header signature 471 writeInt(e.crc); // crc-32 472 if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) { 473 writeLong(e.csize); 474 writeLong(e.size); 475 } else { 476 writeInt(e.csize); // compressed size 477 writeInt(e.size); // uncompressed size 478 } 479 } 480 481 /* 482 * Write central directory (CEN) header for specified entry. 483 * REMIND: add support for file attributes 484 */ writeCEN(XEntry xentry)485 private void writeCEN(XEntry xentry) throws IOException { 486 ZipEntry e = xentry.entry; 487 int flag = e.flag; 488 int version = version(e); 489 long csize = e.csize; 490 long size = e.size; 491 long offset = xentry.offset; 492 int elenZIP64 = 0; 493 boolean hasZip64 = false; 494 495 if (e.csize >= ZIP64_MAGICVAL) { 496 csize = ZIP64_MAGICVAL; 497 elenZIP64 += 8; // csize(8) 498 hasZip64 = true; 499 } 500 if (e.size >= ZIP64_MAGICVAL) { 501 size = ZIP64_MAGICVAL; // size(8) 502 elenZIP64 += 8; 503 hasZip64 = true; 504 } 505 if (xentry.offset >= ZIP64_MAGICVAL) { 506 offset = ZIP64_MAGICVAL; 507 elenZIP64 += 8; // offset(8) 508 hasZip64 = true; 509 } 510 writeInt(CENSIG); // CEN header signature 511 if (hasZip64) { 512 writeShort(45); // ver 4.5 for zip64 513 writeShort(45); 514 } else { 515 writeShort(version); // version made by 516 writeShort(version); // version needed to extract 517 } 518 writeShort(flag); // general purpose bit flag 519 writeShort(e.method); // compression method 520 writeInt(e.xdostime); // last modification time 521 writeInt(e.crc); // crc-32 522 writeInt(csize); // compressed size 523 writeInt(size); // uncompressed size 524 byte[] nameBytes = zc.getBytes(e.name); 525 writeShort(nameBytes.length); 526 527 int elen = getExtraLen(e.extra); 528 if (hasZip64) { 529 elen += (elenZIP64 + 4);// + headid(2) + datasize(2) 530 } 531 // cen info-zip extended timestamp only outputs mtime 532 // but set the flag for a/ctime, if present in loc 533 int flagEXTT = 0; 534 if (e.mtime != null) { 535 elen += 4; // + mtime(4) 536 flagEXTT |= EXTT_FLAG_LMT; 537 } 538 if (e.atime != null) { 539 flagEXTT |= EXTT_FLAG_LAT; 540 } 541 if (e.ctime != null) { 542 flagEXTT |= EXTT_FLAT_CT; 543 } 544 if (flagEXTT != 0) { 545 elen += 5; // headid + sz + flag 546 } 547 writeShort(elen); 548 byte[] commentBytes; 549 if (e.comment != null) { 550 commentBytes = zc.getBytes(e.comment); 551 writeShort(Math.min(commentBytes.length, 0xffff)); 552 } else { 553 commentBytes = null; 554 writeShort(0); 555 } 556 writeShort(0); // starting disk number 557 writeShort(0); // internal file attributes (unused) 558 writeInt(0); // external file attributes (unused) 559 writeInt(offset); // relative offset of local header 560 writeBytes(nameBytes, 0, nameBytes.length); 561 562 // take care of EXTID_ZIP64 and EXTID_EXTT 563 if (hasZip64) { 564 writeShort(ZIP64_EXTID);// Zip64 extra 565 writeShort(elenZIP64); 566 if (size == ZIP64_MAGICVAL) 567 writeLong(e.size); 568 if (csize == ZIP64_MAGICVAL) 569 writeLong(e.csize); 570 if (offset == ZIP64_MAGICVAL) 571 writeLong(xentry.offset); 572 } 573 if (flagEXTT != 0) { 574 writeShort(EXTID_EXTT); 575 if (e.mtime != null) { 576 writeShort(5); // flag + mtime 577 writeByte(flagEXTT); 578 writeInt(fileTimeToUnixTime(e.mtime)); 579 } else { 580 writeShort(1); // flag only 581 writeByte(flagEXTT); 582 } 583 } 584 writeExtra(e.extra); 585 if (commentBytes != null) { 586 writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff)); 587 } 588 } 589 590 /* 591 * Writes end of central directory (END) header. 592 */ writeEND(long off, long len)593 private void writeEND(long off, long len) throws IOException { 594 boolean hasZip64 = false; 595 long xlen = len; 596 long xoff = off; 597 if (xlen >= ZIP64_MAGICVAL) { 598 xlen = ZIP64_MAGICVAL; 599 hasZip64 = true; 600 } 601 if (xoff >= ZIP64_MAGICVAL) { 602 xoff = ZIP64_MAGICVAL; 603 hasZip64 = true; 604 } 605 int count = xentries.size(); 606 if (count >= ZIP64_MAGICCOUNT) { 607 hasZip64 |= !inhibitZip64; 608 if (hasZip64) { 609 count = ZIP64_MAGICCOUNT; 610 } 611 } 612 if (hasZip64) { 613 long off64 = written; 614 //zip64 end of central directory record 615 writeInt(ZIP64_ENDSIG); // zip64 END record signature 616 writeLong(ZIP64_ENDHDR - 12); // size of zip64 end 617 writeShort(45); // version made by 618 writeShort(45); // version needed to extract 619 writeInt(0); // number of this disk 620 writeInt(0); // central directory start disk 621 writeLong(xentries.size()); // number of directory entires on disk 622 writeLong(xentries.size()); // number of directory entires 623 writeLong(len); // length of central directory 624 writeLong(off); // offset of central directory 625 626 //zip64 end of central directory locator 627 writeInt(ZIP64_LOCSIG); // zip64 END locator signature 628 writeInt(0); // zip64 END start disk 629 writeLong(off64); // offset of zip64 END 630 writeInt(1); // total number of disks (?) 631 } 632 writeInt(ENDSIG); // END record signature 633 writeShort(0); // number of this disk 634 writeShort(0); // central directory start disk 635 writeShort(count); // number of directory entries on disk 636 writeShort(count); // total number of directory entries 637 writeInt(xlen); // length of central directory 638 writeInt(xoff); // offset of central directory 639 if (comment != null) { // zip file comment 640 writeShort(comment.length); 641 writeBytes(comment, 0, comment.length); 642 } else { 643 writeShort(0); 644 } 645 } 646 647 /* 648 * Returns the length of extra data without EXTT and ZIP64. 649 */ getExtraLen(byte[] extra)650 private int getExtraLen(byte[] extra) { 651 if (extra == null) 652 return 0; 653 int skipped = 0; 654 int len = extra.length; 655 int off = 0; 656 while (off + 4 <= len) { 657 int tag = get16(extra, off); 658 int sz = get16(extra, off + 2); 659 if (sz < 0 || (off + 4 + sz) > len) { 660 break; 661 } 662 if (tag == EXTID_EXTT || tag == EXTID_ZIP64) { 663 skipped += (sz + 4); 664 } 665 off += (sz + 4); 666 } 667 return len - skipped; 668 } 669 670 /* 671 * Writes extra data without EXTT and ZIP64. 672 * 673 * Extra timestamp and ZIP64 data is handled/output separately 674 * in writeLOC and writeCEN. 675 */ writeExtra(byte[] extra)676 private void writeExtra(byte[] extra) throws IOException { 677 if (extra != null) { 678 int len = extra.length; 679 int off = 0; 680 while (off + 4 <= len) { 681 int tag = get16(extra, off); 682 int sz = get16(extra, off + 2); 683 if (sz < 0 || (off + 4 + sz) > len) { 684 writeBytes(extra, off, len - off); 685 return; 686 } 687 if (tag != EXTID_EXTT && tag != EXTID_ZIP64) { 688 writeBytes(extra, off, sz + 4); 689 } 690 off += (sz + 4); 691 } 692 if (off < len) { 693 writeBytes(extra, off, len - off); 694 } 695 } 696 } 697 698 /* 699 * Writes a 8-bit byte to the output stream. 700 */ writeByte(int v)701 private void writeByte(int v) throws IOException { 702 OutputStream out = this.out; 703 out.write(v & 0xff); 704 written += 1; 705 } 706 707 /* 708 * Writes a 16-bit short to the output stream in little-endian byte order. 709 */ writeShort(int v)710 private void writeShort(int v) throws IOException { 711 OutputStream out = this.out; 712 out.write((v >>> 0) & 0xff); 713 out.write((v >>> 8) & 0xff); 714 written += 2; 715 } 716 717 /* 718 * Writes a 32-bit int to the output stream in little-endian byte order. 719 */ writeInt(long v)720 private void writeInt(long v) throws IOException { 721 OutputStream out = this.out; 722 out.write((int)((v >>> 0) & 0xff)); 723 out.write((int)((v >>> 8) & 0xff)); 724 out.write((int)((v >>> 16) & 0xff)); 725 out.write((int)((v >>> 24) & 0xff)); 726 written += 4; 727 } 728 729 /* 730 * Writes a 64-bit int to the output stream in little-endian byte order. 731 */ writeLong(long v)732 private void writeLong(long v) throws IOException { 733 OutputStream out = this.out; 734 out.write((int)((v >>> 0) & 0xff)); 735 out.write((int)((v >>> 8) & 0xff)); 736 out.write((int)((v >>> 16) & 0xff)); 737 out.write((int)((v >>> 24) & 0xff)); 738 out.write((int)((v >>> 32) & 0xff)); 739 out.write((int)((v >>> 40) & 0xff)); 740 out.write((int)((v >>> 48) & 0xff)); 741 out.write((int)((v >>> 56) & 0xff)); 742 written += 8; 743 } 744 745 /* 746 * Writes an array of bytes to the output stream. 747 */ writeBytes(byte[] b, int off, int len)748 private void writeBytes(byte[] b, int off, int len) throws IOException { 749 super.out.write(b, off, len); 750 written += len; 751 } 752 } 753