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