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 27package java.util.zip; 28 29import java.io.OutputStream; 30import java.io.IOException; 31import java.nio.charset.Charset; 32import java.nio.charset.StandardCharsets; 33import java.util.Vector; 34import java.util.HashSet; 35import static java.util.zip.ZipConstants64.*; 36import 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 */ 45public 46class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { 47 48 /** 49 * Whether to use ZIP64 for zip files with more than 64k entries. 50 * Until ZIP64 support in zip implementations is ubiquitous, this 51 * system property allows the creation of zip files which can be 52 * read by legacy zip implementations which tolerate "incorrect" 53 * total entry count fields, such as the ones in jdk6, and even 54 * some in jdk7. 55 */ 56 // Android-changed: Force to false. 57 private static final boolean inhibitZip64 = false; 58 // Boolean.parseBoolean( 59 // java.security.AccessController.doPrivileged( 60 // new sun.security.action.GetPropertyAction( 61 // "jdk.util.zip.inhibitZip64", "false"))); 62 63 private static class XEntry { 64 final ZipEntry entry; 65 final long offset; 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 191 public void putNextEntry(ZipEntry e) throws IOException { 192 ensureOpen(); 193 if (current != null) { 194 closeEntry(); // close previous entry 195 } 196 if (e.xdostime == -1) { 197 // by default, do NOT use extended timestamps in extra 198 // data, for now. 199 e.setTime(System.currentTimeMillis()); 200 } 201 if (e.method == -1) { 202 e.method = method; // use default method 203 } 204 // store size, compressed size, and crc-32 in LOC header 205 e.flag = 0; 206 switch (e.method) { 207 case DEFLATED: 208 // store size, compressed size, and crc-32 in data descriptor 209 // immediately following the compressed entry data 210 if (e.size == -1 || e.csize == -1 || e.crc == -1) 211 e.flag = 8; 212 213 break; 214 case STORED: 215 // compressed size, uncompressed size, and crc-32 must all be 216 // set for entries using STORED compression method 217 if (e.size == -1) { 218 e.size = e.csize; 219 } else if (e.csize == -1) { 220 e.csize = e.size; 221 } else if (e.size != e.csize) { 222 throw new ZipException( 223 "STORED entry where compressed != uncompressed size"); 224 } 225 if (e.size == -1 || e.crc == -1) { 226 throw new ZipException( 227 "STORED entry missing size, compressed size, or crc-32"); 228 } 229 break; 230 default: 231 throw new ZipException("unsupported compression method"); 232 } 233 if (! names.add(e.name)) { 234 throw new ZipException("duplicate entry: " + e.name); 235 } 236 if (zc.isUTF8()) 237 e.flag |= EFS; 238 current = new XEntry(e, written); 239 xentries.add(current); 240 writeLOC(current); 241 } 242 243 /** 244 * Closes the current ZIP entry and positions the stream for writing 245 * the next entry. 246 * @exception ZipException if a ZIP format error has occurred 247 * @exception IOException if an I/O error has occurred 248 */ 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 */ 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 */ 356 public void finish() throws IOException { 357 ensureOpen(); 358 if (finished) { 359 return; 360 } 361 // Android-changed: Fix for ZipOutputStreamTest#testCreateEmpty 362 if (xentries.isEmpty()) { 363 throw new ZipException("No entries"); 364 } 365 if (current != null) { 366 closeEntry(); 367 } 368 // write central directory 369 long off = written; 370 for (XEntry xentry : xentries) 371 writeCEN(xentry); 372 writeEND(off, written - off); 373 finished = true; 374 } 375 376 /** 377 * Closes the ZIP output stream as well as the stream being filtered. 378 * @exception ZipException if a ZIP file error has occurred 379 * @exception IOException if an I/O error has occurred 380 */ 381 public void close() throws IOException { 382 if (!closed) { 383 super.close(); 384 closed = true; 385 } 386 } 387 388 /* 389 * Writes local file (LOC) header for specified entry. 390 */ 391 private void writeLOC(XEntry xentry) throws IOException { 392 ZipEntry e = xentry.entry; 393 int flag = e.flag; 394 boolean hasZip64 = false; 395 int elen = getExtraLen(e.extra); 396 397 writeInt(LOCSIG); // LOC header signature 398 if ((flag & 8) == 8) { 399 writeShort(version(e)); // version needed to extract 400 writeShort(flag); // general purpose bit flag 401 writeShort(e.method); // compression method 402 writeInt(e.xdostime); // last modification time 403 // store size, uncompressed size, and crc-32 in data descriptor 404 // immediately following compressed entry data 405 writeInt(0); 406 writeInt(0); 407 writeInt(0); 408 } else { 409 if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) { 410 hasZip64 = true; 411 writeShort(45); // ver 4.5 for zip64 412 } else { 413 writeShort(version(e)); // version needed to extract 414 } 415 writeShort(flag); // general purpose bit flag 416 writeShort(e.method); // compression method 417 writeInt(e.xdostime); // last modification time 418 writeInt(e.crc); // crc-32 419 if (hasZip64) { 420 writeInt(ZIP64_MAGICVAL); 421 writeInt(ZIP64_MAGICVAL); 422 elen += 20; //headid(2) + size(2) + size(8) + csize(8) 423 } else { 424 writeInt(e.csize); // compressed size 425 writeInt(e.size); // uncompressed size 426 } 427 } 428 byte[] nameBytes = zc.getBytes(e.name); 429 writeShort(nameBytes.length); 430 431 int elenEXTT = 0; // info-zip extended timestamp 432 int flagEXTT = 0; 433 if (e.mtime != null) { 434 elenEXTT += 4; 435 flagEXTT |= EXTT_FLAG_LMT; 436 } 437 if (e.atime != null) { 438 elenEXTT += 4; 439 flagEXTT |= EXTT_FLAG_LAT; 440 } 441 if (e.ctime != null) { 442 elenEXTT += 4; 443 flagEXTT |= EXTT_FLAT_CT; 444 } 445 if (flagEXTT != 0) 446 elen += (elenEXTT + 5); // headid(2) + size(2) + flag(1) + data 447 writeShort(elen); 448 writeBytes(nameBytes, 0, nameBytes.length); 449 if (hasZip64) { 450 writeShort(ZIP64_EXTID); 451 writeShort(16); 452 writeLong(e.size); 453 writeLong(e.csize); 454 } 455 if (flagEXTT != 0) { 456 writeShort(EXTID_EXTT); 457 writeShort(elenEXTT + 1); // flag + data 458 writeByte(flagEXTT); 459 if (e.mtime != null) 460 writeInt(fileTimeToUnixTime(e.mtime)); 461 if (e.atime != null) 462 writeInt(fileTimeToUnixTime(e.atime)); 463 if (e.ctime != null) 464 writeInt(fileTimeToUnixTime(e.ctime)); 465 } 466 writeExtra(e.extra); 467 locoff = written; 468 } 469 470 /* 471 * Writes extra data descriptor (EXT) for specified entry. 472 */ 473 private void writeEXT(ZipEntry e) throws IOException { 474 writeInt(EXTSIG); // EXT header signature 475 writeInt(e.crc); // crc-32 476 if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) { 477 writeLong(e.csize); 478 writeLong(e.size); 479 } else { 480 writeInt(e.csize); // compressed size 481 writeInt(e.size); // uncompressed size 482 } 483 } 484 485 /* 486 * Write central directory (CEN) header for specified entry. 487 * REMIND: add support for file attributes 488 */ 489 private void writeCEN(XEntry xentry) throws IOException { 490 ZipEntry e = xentry.entry; 491 int flag = e.flag; 492 int version = version(e); 493 long csize = e.csize; 494 long size = e.size; 495 long offset = xentry.offset; 496 int elenZIP64 = 0; 497 boolean hasZip64 = false; 498 499 if (e.csize >= ZIP64_MAGICVAL) { 500 csize = ZIP64_MAGICVAL; 501 elenZIP64 += 8; // csize(8) 502 hasZip64 = true; 503 } 504 if (e.size >= ZIP64_MAGICVAL) { 505 size = ZIP64_MAGICVAL; // size(8) 506 elenZIP64 += 8; 507 hasZip64 = true; 508 } 509 if (xentry.offset >= ZIP64_MAGICVAL) { 510 offset = ZIP64_MAGICVAL; 511 elenZIP64 += 8; // offset(8) 512 hasZip64 = true; 513 } 514 writeInt(CENSIG); // CEN header signature 515 if (hasZip64) { 516 writeShort(45); // ver 4.5 for zip64 517 writeShort(45); 518 } else { 519 writeShort(version); // version made by 520 writeShort(version); // version needed to extract 521 } 522 writeShort(flag); // general purpose bit flag 523 writeShort(e.method); // compression method 524 writeInt(e.xdostime); // last modification time 525 writeInt(e.crc); // crc-32 526 writeInt(csize); // compressed size 527 writeInt(size); // uncompressed size 528 byte[] nameBytes = zc.getBytes(e.name); 529 writeShort(nameBytes.length); 530 531 int elen = getExtraLen(e.extra); 532 if (hasZip64) { 533 elen += (elenZIP64 + 4);// + headid(2) + datasize(2) 534 } 535 // cen info-zip extended timestamp only outputs mtime 536 // but set the flag for a/ctime, if present in loc 537 int flagEXTT = 0; 538 if (e.mtime != null) { 539 elen += 4; // + mtime(4) 540 flagEXTT |= EXTT_FLAG_LMT; 541 } 542 if (e.atime != null) { 543 flagEXTT |= EXTT_FLAG_LAT; 544 } 545 if (e.ctime != null) { 546 flagEXTT |= EXTT_FLAT_CT; 547 } 548 if (flagEXTT != 0) { 549 elen += 5; // headid + sz + flag 550 } 551 writeShort(elen); 552 byte[] commentBytes; 553 if (e.comment != null) { 554 commentBytes = zc.getBytes(e.comment); 555 writeShort(Math.min(commentBytes.length, 0xffff)); 556 } else { 557 commentBytes = null; 558 writeShort(0); 559 } 560 writeShort(0); // starting disk number 561 writeShort(0); // internal file attributes (unused) 562 writeInt(0); // external file attributes (unused) 563 writeInt(offset); // relative offset of local header 564 writeBytes(nameBytes, 0, nameBytes.length); 565 566 // take care of EXTID_ZIP64 and EXTID_EXTT 567 if (hasZip64) { 568 writeShort(ZIP64_EXTID);// Zip64 extra 569 writeShort(elenZIP64); 570 if (size == ZIP64_MAGICVAL) 571 writeLong(e.size); 572 if (csize == ZIP64_MAGICVAL) 573 writeLong(e.csize); 574 if (offset == ZIP64_MAGICVAL) 575 writeLong(xentry.offset); 576 } 577 if (flagEXTT != 0) { 578 writeShort(EXTID_EXTT); 579 if (e.mtime != null) { 580 writeShort(5); // flag + mtime 581 writeByte(flagEXTT); 582 writeInt(fileTimeToUnixTime(e.mtime)); 583 } else { 584 writeShort(1); // flag only 585 writeByte(flagEXTT); 586 } 587 } 588 writeExtra(e.extra); 589 if (commentBytes != null) { 590 writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff)); 591 } 592 } 593 594 /* 595 * Writes end of central directory (END) header. 596 */ 597 private void writeEND(long off, long len) throws IOException { 598 boolean hasZip64 = false; 599 long xlen = len; 600 long xoff = off; 601 if (xlen >= ZIP64_MAGICVAL) { 602 xlen = ZIP64_MAGICVAL; 603 hasZip64 = true; 604 } 605 if (xoff >= ZIP64_MAGICVAL) { 606 xoff = ZIP64_MAGICVAL; 607 hasZip64 = true; 608 } 609 int count = xentries.size(); 610 if (count >= ZIP64_MAGICCOUNT) { 611 hasZip64 |= !inhibitZip64; 612 if (hasZip64) { 613 count = ZIP64_MAGICCOUNT; 614 } 615 } 616 if (hasZip64) { 617 long off64 = written; 618 //zip64 end of central directory record 619 writeInt(ZIP64_ENDSIG); // zip64 END record signature 620 writeLong(ZIP64_ENDHDR - 12); // size of zip64 end 621 writeShort(45); // version made by 622 writeShort(45); // version needed to extract 623 writeInt(0); // number of this disk 624 writeInt(0); // central directory start disk 625 writeLong(xentries.size()); // number of directory entires on disk 626 writeLong(xentries.size()); // number of directory entires 627 writeLong(len); // length of central directory 628 writeLong(off); // offset of central directory 629 630 //zip64 end of central directory locator 631 writeInt(ZIP64_LOCSIG); // zip64 END locator signature 632 writeInt(0); // zip64 END start disk 633 writeLong(off64); // offset of zip64 END 634 writeInt(1); // total number of disks (?) 635 } 636 writeInt(ENDSIG); // END record signature 637 writeShort(0); // number of this disk 638 writeShort(0); // central directory start disk 639 writeShort(count); // number of directory entries on disk 640 writeShort(count); // total number of directory entries 641 writeInt(xlen); // length of central directory 642 writeInt(xoff); // offset of central directory 643 if (comment != null) { // zip file comment 644 writeShort(comment.length); 645 writeBytes(comment, 0, comment.length); 646 } else { 647 writeShort(0); 648 } 649 } 650 651 /* 652 * Returns the length of extra data without EXTT and ZIP64. 653 */ 654 private int getExtraLen(byte[] extra) { 655 if (extra == null) 656 return 0; 657 int skipped = 0; 658 int len = extra.length; 659 int off = 0; 660 while (off + 4 <= len) { 661 int tag = get16(extra, off); 662 int sz = get16(extra, off + 2); 663 if (sz < 0 || (off + 4 + sz) > len) { 664 break; 665 } 666 if (tag == EXTID_EXTT || tag == EXTID_ZIP64) { 667 skipped += (sz + 4); 668 } 669 off += (sz + 4); 670 } 671 return len - skipped; 672 } 673 674 /* 675 * Writes extra data without EXTT and ZIP64. 676 * 677 * Extra timestamp and ZIP64 data is handled/output separately 678 * in writeLOC and writeCEN. 679 */ 680 private void writeExtra(byte[] extra) throws IOException { 681 if (extra != null) { 682 int len = extra.length; 683 int off = 0; 684 while (off + 4 <= len) { 685 int tag = get16(extra, off); 686 int sz = get16(extra, off + 2); 687 if (sz < 0 || (off + 4 + sz) > len) { 688 writeBytes(extra, off, len - off); 689 return; 690 } 691 if (tag != EXTID_EXTT && tag != EXTID_ZIP64) { 692 writeBytes(extra, off, sz + 4); 693 } 694 off += (sz + 4); 695 } 696 if (off < len) { 697 writeBytes(extra, off, len - off); 698 } 699 } 700 } 701 702 /* 703 * Writes a 8-bit byte to the output stream. 704 */ 705 private void writeByte(int v) throws IOException { 706 OutputStream out = this.out; 707 out.write(v & 0xff); 708 written += 1; 709 } 710 711 /* 712 * Writes a 16-bit short to the output stream in little-endian byte order. 713 */ 714 private void writeShort(int v) throws IOException { 715 OutputStream out = this.out; 716 out.write((v >>> 0) & 0xff); 717 out.write((v >>> 8) & 0xff); 718 written += 2; 719 } 720 721 /* 722 * Writes a 32-bit int to the output stream in little-endian byte order. 723 */ 724 private void writeInt(long v) throws IOException { 725 OutputStream out = this.out; 726 out.write((int)((v >>> 0) & 0xff)); 727 out.write((int)((v >>> 8) & 0xff)); 728 out.write((int)((v >>> 16) & 0xff)); 729 out.write((int)((v >>> 24) & 0xff)); 730 written += 4; 731 } 732 733 /* 734 * Writes a 64-bit int to the output stream in little-endian byte order. 735 */ 736 private void writeLong(long v) throws IOException { 737 OutputStream out = this.out; 738 out.write((int)((v >>> 0) & 0xff)); 739 out.write((int)((v >>> 8) & 0xff)); 740 out.write((int)((v >>> 16) & 0xff)); 741 out.write((int)((v >>> 24) & 0xff)); 742 out.write((int)((v >>> 32) & 0xff)); 743 out.write((int)((v >>> 40) & 0xff)); 744 out.write((int)((v >>> 48) & 0xff)); 745 out.write((int)((v >>> 56) & 0xff)); 746 written += 8; 747 } 748 749 /* 750 * Writes an array of bytes to the output stream. 751 */ 752 private void writeBytes(byte[] b, int off, int len) throws IOException { 753 super.out.write(b, off, len); 754 written += len; 755 } 756} 757