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