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