1 /* 2 * Copyright (c) 1996, 2015, 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 26 package java.util.zip; 27 28 import java.io.OutputStream; 29 import java.io.IOException; 30 import java.nio.charset.Charset; 31 import java.nio.charset.StandardCharsets; 32 import java.util.Vector; 33 import java.util.HashSet; 34 import static java.util.zip.ZipConstants64.*; 35 import static java.util.zip.ZipUtils.*; 36 37 /** 38 * This class implements an output stream filter for writing files in the 39 * ZIP file format. Includes support for both compressed and uncompressed 40 * entries. 41 * 42 * @author David Connelly 43 */ 44 public 45 class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { 46 47 /** 48 * Whether to use ZIP64 for zip files with more than 64k entries. 49 * Until ZIP64 support in zip implementations is ubiquitous, this 50 * system property allows the creation of zip files which can be 51 * read by legacy zip implementations which tolerate "incorrect" 52 * total entry count fields, such as the ones in jdk6, and even 53 * some in jdk7. 54 */ 55 private static final boolean inhibitZip64 = 56 Boolean.parseBoolean( 57 java.security.AccessController.doPrivileged( 58 new sun.security.action.GetPropertyAction( 59 "jdk.util.zip.inhibitZip64", "false"))); 60 61 private static class XEntry { 62 final ZipEntry entry; 63 final long offset; XEntry(ZipEntry entry, 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 version(ZipEntry e)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 */ ensureOpen()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 */ ZipOutputStream(OutputStream out)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 */ ZipOutputStream(OutputStream out, Charset charset)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 */ setComment(String comment)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 */ setMethod(int method)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 */ setLevel(int level)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 */ putNextEntry(ZipEntry e)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 */ closeEntry()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 */ write(byte[] b, int off, int len)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 */ finish()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 */ close()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 */ writeLOC(XEntry xentry)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 if (e.mtime != null) { 428 elenEXTT += 4; 429 flagEXTT |= EXTT_FLAG_LMT; 430 } 431 if (e.atime != null) { 432 elenEXTT += 4; 433 flagEXTT |= EXTT_FLAG_LAT; 434 } 435 if (e.ctime != null) { 436 elenEXTT += 4; 437 flagEXTT |= EXTT_FLAT_CT; 438 } 439 if (flagEXTT != 0) 440 elen += (elenEXTT + 5); // headid(2) + size(2) + flag(1) + data 441 writeShort(elen); 442 writeBytes(nameBytes, 0, nameBytes.length); 443 if (hasZip64) { 444 writeShort(ZIP64_EXTID); 445 writeShort(16); 446 writeLong(e.size); 447 writeLong(e.csize); 448 } 449 if (flagEXTT != 0) { 450 writeShort(EXTID_EXTT); 451 writeShort(elenEXTT + 1); // flag + data 452 writeByte(flagEXTT); 453 if (e.mtime != null) 454 writeInt(fileTimeToUnixTime(e.mtime)); 455 if (e.atime != null) 456 writeInt(fileTimeToUnixTime(e.atime)); 457 if (e.ctime != null) 458 writeInt(fileTimeToUnixTime(e.ctime)); 459 } 460 writeExtra(e.extra); 461 locoff = written; 462 } 463 464 /* 465 * Writes extra data descriptor (EXT) for specified entry. 466 */ writeEXT(ZipEntry e)467 private void writeEXT(ZipEntry e) throws IOException { 468 writeInt(EXTSIG); // EXT header signature 469 writeInt(e.crc); // crc-32 470 if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) { 471 writeLong(e.csize); 472 writeLong(e.size); 473 } else { 474 writeInt(e.csize); // compressed size 475 writeInt(e.size); // uncompressed size 476 } 477 } 478 479 /* 480 * Write central directory (CEN) header for specified entry. 481 * REMIND: add support for file attributes 482 */ writeCEN(XEntry xentry)483 private void writeCEN(XEntry xentry) throws IOException { 484 ZipEntry e = xentry.entry; 485 int flag = e.flag; 486 int version = version(e); 487 long csize = e.csize; 488 long size = e.size; 489 long offset = xentry.offset; 490 int elenZIP64 = 0; 491 boolean hasZip64 = false; 492 493 if (e.csize >= ZIP64_MAGICVAL) { 494 csize = ZIP64_MAGICVAL; 495 elenZIP64 += 8; // csize(8) 496 hasZip64 = true; 497 } 498 if (e.size >= ZIP64_MAGICVAL) { 499 size = ZIP64_MAGICVAL; // size(8) 500 elenZIP64 += 8; 501 hasZip64 = true; 502 } 503 if (xentry.offset >= ZIP64_MAGICVAL) { 504 offset = ZIP64_MAGICVAL; 505 elenZIP64 += 8; // offset(8) 506 hasZip64 = true; 507 } 508 writeInt(CENSIG); // CEN header signature 509 if (hasZip64) { 510 writeShort(45); // ver 4.5 for zip64 511 writeShort(45); 512 } else { 513 writeShort(version); // version made by 514 writeShort(version); // version needed to extract 515 } 516 writeShort(flag); // general purpose bit flag 517 writeShort(e.method); // compression method 518 writeInt(e.xdostime); // last modification time 519 writeInt(e.crc); // crc-32 520 writeInt(csize); // compressed size 521 writeInt(size); // uncompressed size 522 byte[] nameBytes = zc.getBytes(e.name); 523 writeShort(nameBytes.length); 524 525 int elen = getExtraLen(e.extra); 526 if (hasZip64) { 527 elen += (elenZIP64 + 4);// + headid(2) + datasize(2) 528 } 529 // cen info-zip extended timestamp only outputs mtime 530 // but set the flag for a/ctime, if present in loc 531 int flagEXTT = 0; 532 if (e.mtime != null) { 533 elen += 4; // + mtime(4) 534 flagEXTT |= EXTT_FLAG_LMT; 535 } 536 if (e.atime != null) { 537 flagEXTT |= EXTT_FLAG_LAT; 538 } 539 if (e.ctime != null) { 540 flagEXTT |= EXTT_FLAT_CT; 541 } 542 if (flagEXTT != 0) { 543 elen += 5; // headid + sz + flag 544 } 545 writeShort(elen); 546 byte[] commentBytes; 547 if (e.comment != null) { 548 commentBytes = zc.getBytes(e.comment); 549 writeShort(Math.min(commentBytes.length, 0xffff)); 550 } else { 551 commentBytes = null; 552 writeShort(0); 553 } 554 writeShort(0); // starting disk number 555 writeShort(0); // internal file attributes (unused) 556 writeInt(0); // external file attributes (unused) 557 writeInt(offset); // relative offset of local header 558 writeBytes(nameBytes, 0, nameBytes.length); 559 560 // take care of EXTID_ZIP64 and EXTID_EXTT 561 if (hasZip64) { 562 writeShort(ZIP64_EXTID);// Zip64 extra 563 writeShort(elenZIP64); 564 if (size == ZIP64_MAGICVAL) 565 writeLong(e.size); 566 if (csize == ZIP64_MAGICVAL) 567 writeLong(e.csize); 568 if (offset == ZIP64_MAGICVAL) 569 writeLong(xentry.offset); 570 } 571 if (flagEXTT != 0) { 572 writeShort(EXTID_EXTT); 573 if (e.mtime != null) { 574 writeShort(5); // flag + mtime 575 writeByte(flagEXTT); 576 writeInt(fileTimeToUnixTime(e.mtime)); 577 } else { 578 writeShort(1); // flag only 579 writeByte(flagEXTT); 580 } 581 } 582 writeExtra(e.extra); 583 if (commentBytes != null) { 584 writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff)); 585 } 586 } 587 588 /* 589 * Writes end of central directory (END) header. 590 */ writeEND(long off, long len)591 private void writeEND(long off, long len) throws IOException { 592 boolean hasZip64 = false; 593 long xlen = len; 594 long xoff = off; 595 if (xlen >= ZIP64_MAGICVAL) { 596 xlen = ZIP64_MAGICVAL; 597 hasZip64 = true; 598 } 599 if (xoff >= ZIP64_MAGICVAL) { 600 xoff = ZIP64_MAGICVAL; 601 hasZip64 = true; 602 } 603 int count = xentries.size(); 604 if (count >= ZIP64_MAGICCOUNT) { 605 hasZip64 |= !inhibitZip64; 606 if (hasZip64) { 607 count = ZIP64_MAGICCOUNT; 608 } 609 } 610 if (hasZip64) { 611 long off64 = written; 612 //zip64 end of central directory record 613 writeInt(ZIP64_ENDSIG); // zip64 END record signature 614 writeLong(ZIP64_ENDHDR - 12); // size of zip64 end 615 writeShort(45); // version made by 616 writeShort(45); // version needed to extract 617 writeInt(0); // number of this disk 618 writeInt(0); // central directory start disk 619 writeLong(xentries.size()); // number of directory entires on disk 620 writeLong(xentries.size()); // number of directory entires 621 writeLong(len); // length of central directory 622 writeLong(off); // offset of central directory 623 624 //zip64 end of central directory locator 625 writeInt(ZIP64_LOCSIG); // zip64 END locator signature 626 writeInt(0); // zip64 END start disk 627 writeLong(off64); // offset of zip64 END 628 writeInt(1); // total number of disks (?) 629 } 630 writeInt(ENDSIG); // END record signature 631 writeShort(0); // number of this disk 632 writeShort(0); // central directory start disk 633 writeShort(count); // number of directory entries on disk 634 writeShort(count); // total number of directory entries 635 writeInt(xlen); // length of central directory 636 writeInt(xoff); // offset of central directory 637 if (comment != null) { // zip file comment 638 writeShort(comment.length); 639 writeBytes(comment, 0, comment.length); 640 } else { 641 writeShort(0); 642 } 643 } 644 645 /* 646 * Returns the length of extra data without EXTT and ZIP64. 647 */ getExtraLen(byte[] extra)648 private int getExtraLen(byte[] extra) { 649 if (extra == null) 650 return 0; 651 int skipped = 0; 652 int len = extra.length; 653 int off = 0; 654 while (off + 4 <= len) { 655 int tag = get16(extra, off); 656 int sz = get16(extra, off + 2); 657 if (sz < 0 || (off + 4 + sz) > len) { 658 break; 659 } 660 if (tag == EXTID_EXTT || tag == EXTID_ZIP64) { 661 skipped += (sz + 4); 662 } 663 off += (sz + 4); 664 } 665 return len - skipped; 666 } 667 668 /* 669 * Writes extra data without EXTT and ZIP64. 670 * 671 * Extra timestamp and ZIP64 data is handled/output separately 672 * in writeLOC and writeCEN. 673 */ writeExtra(byte[] extra)674 private void writeExtra(byte[] extra) throws IOException { 675 if (extra != null) { 676 int len = extra.length; 677 int off = 0; 678 while (off + 4 <= len) { 679 int tag = get16(extra, off); 680 int sz = get16(extra, off + 2); 681 if (sz < 0 || (off + 4 + sz) > len) { 682 writeBytes(extra, off, len - off); 683 return; 684 } 685 if (tag != EXTID_EXTT && tag != EXTID_ZIP64) { 686 writeBytes(extra, off, sz + 4); 687 } 688 off += (sz + 4); 689 } 690 if (off < len) { 691 writeBytes(extra, off, len - off); 692 } 693 } 694 } 695 696 /* 697 * Writes a 8-bit byte to the output stream. 698 */ writeByte(int v)699 private void writeByte(int v) throws IOException { 700 OutputStream out = this.out; 701 out.write(v & 0xff); 702 written += 1; 703 } 704 705 /* 706 * Writes a 16-bit short to the output stream in little-endian byte order. 707 */ writeShort(int v)708 private void writeShort(int v) throws IOException { 709 OutputStream out = this.out; 710 out.write((v >>> 0) & 0xff); 711 out.write((v >>> 8) & 0xff); 712 written += 2; 713 } 714 715 /* 716 * Writes a 32-bit int to the output stream in little-endian byte order. 717 */ writeInt(long v)718 private void writeInt(long v) throws IOException { 719 OutputStream out = this.out; 720 out.write((int)((v >>> 0) & 0xff)); 721 out.write((int)((v >>> 8) & 0xff)); 722 out.write((int)((v >>> 16) & 0xff)); 723 out.write((int)((v >>> 24) & 0xff)); 724 written += 4; 725 } 726 727 /* 728 * Writes a 64-bit int to the output stream in little-endian byte order. 729 */ writeLong(long v)730 private void writeLong(long v) throws IOException { 731 OutputStream out = this.out; 732 out.write((int)((v >>> 0) & 0xff)); 733 out.write((int)((v >>> 8) & 0xff)); 734 out.write((int)((v >>> 16) & 0xff)); 735 out.write((int)((v >>> 24) & 0xff)); 736 out.write((int)((v >>> 32) & 0xff)); 737 out.write((int)((v >>> 40) & 0xff)); 738 out.write((int)((v >>> 48) & 0xff)); 739 out.write((int)((v >>> 56) & 0xff)); 740 written += 8; 741 } 742 743 /* 744 * Writes an array of bytes to the output stream. 745 */ writeBytes(byte[] b, int off, int len)746 private void writeBytes(byte[] b, int off, int len) throws IOException { 747 super.out.write(b, off, len); 748 written += len; 749 } 750 } 751