001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 */ 018 package org.apache.commons.compress.archivers.zip; 019 020 import java.io.File; 021 import java.io.FileOutputStream; 022 import java.io.IOException; 023 import java.io.OutputStream; 024 import java.io.RandomAccessFile; 025 import java.nio.ByteBuffer; 026 import java.util.HashMap; 027 import java.util.Iterator; 028 import java.util.LinkedList; 029 import java.util.List; 030 import java.util.Map; 031 import java.util.zip.CRC32; 032 import java.util.zip.Deflater; 033 import java.util.zip.ZipException; 034 035 import org.apache.commons.compress.archivers.ArchiveEntry; 036 import org.apache.commons.compress.archivers.ArchiveOutputStream; 037 038 /** 039 * Reimplementation of {@link java.util.zip.ZipOutputStream 040 * java.util.zip.ZipOutputStream} that does handle the extended 041 * functionality of this package, especially internal/external file 042 * attributes and extra fields with different layouts for local file 043 * data and central directory entries. 044 * 045 * <p>This class will try to use {@link java.io.RandomAccessFile 046 * RandomAccessFile} when you know that the output is going to go to a 047 * file.</p> 048 * 049 * <p>If RandomAccessFile cannot be used, this implementation will use 050 * a Data Descriptor to store size and CRC information for {@link 051 * #DEFLATED DEFLATED} entries, this means, you don't need to 052 * calculate them yourself. Unfortunately this is not possible for 053 * the {@link #STORED STORED} method, here setting the CRC and 054 * uncompressed size information is required before {@link 055 * #putArchiveEntry(ArchiveEntry)} can be called.</p> 056 * @NotThreadSafe 057 */ 058 public class ZipArchiveOutputStream extends ArchiveOutputStream { 059 060 static final int BYTE_MASK = 0xFF; 061 private static final int SHORT = 2; 062 private static final int WORD = 4; 063 static final int BUFFER_SIZE = 512; 064 065 /** indicates if this archive is finished. protected for use in Jar implementation */ 066 protected boolean finished = false; 067 068 /* 069 * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs 070 * when it gets handed a really big buffer. See 071 * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396 072 * 073 * Using a buffer size of 8 kB proved to be a good compromise 074 */ 075 private static final int DEFLATER_BLOCK_SIZE = 8192; 076 077 /** 078 * Compression method for deflated entries. 079 */ 080 public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED; 081 082 /** 083 * Default compression level for deflated entries. 084 */ 085 public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION; 086 087 /** 088 * Compression method for stored entries. 089 */ 090 public static final int STORED = java.util.zip.ZipEntry.STORED; 091 092 /** 093 * default encoding for file names and comment. 094 */ 095 static final String DEFAULT_ENCODING = ZipEncodingHelper.UTF8; 096 097 /** 098 * General purpose flag, which indicates that filenames are 099 * written in utf-8. 100 * @deprecated use {@link GeneralPurposeBit#UFT8_NAMES_FLAG} instead 101 */ 102 public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG; 103 104 /** 105 * Current entry. 106 */ 107 private ZipArchiveEntry entry; 108 109 /** 110 * The file comment. 111 */ 112 private String comment = ""; 113 114 /** 115 * Compression level for next entry. 116 */ 117 private int level = DEFAULT_COMPRESSION; 118 119 /** 120 * Has the compression level changed when compared to the last 121 * entry? 122 */ 123 private boolean hasCompressionLevelChanged = false; 124 125 /** 126 * Default compression method for next entry. 127 */ 128 private int method = java.util.zip.ZipEntry.DEFLATED; 129 130 /** 131 * List of ZipArchiveEntries written so far. 132 */ 133 private final List entries = new LinkedList(); 134 135 /** 136 * CRC instance to avoid parsing DEFLATED data twice. 137 */ 138 private final CRC32 crc = new CRC32(); 139 140 /** 141 * Count the bytes written to out. 142 */ 143 private long written = 0; 144 145 /** 146 * Data for local header data 147 */ 148 private long dataStart = 0; 149 150 /** 151 * Offset for CRC entry in the local file header data for the 152 * current entry starts here. 153 */ 154 private long localDataStart = 0; 155 156 /** 157 * Start of central directory. 158 */ 159 private long cdOffset = 0; 160 161 /** 162 * Length of central directory. 163 */ 164 private long cdLength = 0; 165 166 /** 167 * Helper, a 0 as ZipShort. 168 */ 169 private static final byte[] ZERO = {0, 0}; 170 171 /** 172 * Helper, a 0 as ZipLong. 173 */ 174 private static final byte[] LZERO = {0, 0, 0, 0}; 175 176 /** 177 * Holds the offsets of the LFH starts for each entry. 178 */ 179 private final Map offsets = new HashMap(); 180 181 /** 182 * The encoding to use for filenames and the file comment. 183 * 184 * <p>For a list of possible values see <a 185 * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>. 186 * Defaults to UTF-8.</p> 187 */ 188 private String encoding = DEFAULT_ENCODING; 189 190 /** 191 * The zip encoding to use for filenames and the file comment. 192 * 193 * This field is of internal use and will be set in {@link 194 * #setEncoding(String)}. 195 */ 196 private ZipEncoding zipEncoding = 197 ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING); 198 199 /** 200 * This Deflater object is used for output. 201 * 202 */ 203 protected final Deflater def = new Deflater(level, true); 204 205 /** 206 * This buffer servers as a Deflater. 207 * 208 */ 209 private final byte[] buf = new byte[BUFFER_SIZE]; 210 211 /** 212 * Optional random access output. 213 */ 214 private final RandomAccessFile raf; 215 216 private final OutputStream out; 217 218 /** 219 * whether to use the general purpose bit flag when writing UTF-8 220 * filenames or not. 221 */ 222 private boolean useUTF8Flag = true; 223 224 /** 225 * Whether to encode non-encodable file names as UTF-8. 226 */ 227 private boolean fallbackToUTF8 = false; 228 229 /** 230 * whether to create UnicodePathExtraField-s for each entry. 231 */ 232 private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER; 233 234 /** 235 * Creates a new ZIP OutputStream filtering the underlying stream. 236 * @param out the outputstream to zip 237 */ 238 public ZipArchiveOutputStream(OutputStream out) { 239 this.out = out; 240 this.raf = null; 241 } 242 243 /** 244 * Creates a new ZIP OutputStream writing to a File. Will use 245 * random access if possible. 246 * @param file the file to zip to 247 * @throws IOException on error 248 */ 249 public ZipArchiveOutputStream(File file) throws IOException { 250 OutputStream o = null; 251 RandomAccessFile _raf = null; 252 try { 253 _raf = new RandomAccessFile(file, "rw"); 254 _raf.setLength(0); 255 } catch (IOException e) { 256 if (_raf != null) { 257 try { 258 _raf.close(); 259 } catch (IOException inner) { 260 // ignore 261 } 262 _raf = null; 263 } 264 o = new FileOutputStream(file); 265 } 266 out = o; 267 raf = _raf; 268 } 269 270 /** 271 * This method indicates whether this archive is writing to a 272 * seekable stream (i.e., to a random access file). 273 * 274 * <p>For seekable streams, you don't need to calculate the CRC or 275 * uncompressed size for {@link #STORED} entries before 276 * invoking {@link #putArchiveEntry(ArchiveEntry)}. 277 * @return true if seekable 278 */ 279 public boolean isSeekable() { 280 return raf != null; 281 } 282 283 /** 284 * The encoding to use for filenames and the file comment. 285 * 286 * <p>For a list of possible values see <a 287 * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>. 288 * Defaults to UTF-8.</p> 289 * @param encoding the encoding to use for file names, use null 290 * for the platform's default encoding 291 */ 292 public void setEncoding(final String encoding) { 293 this.encoding = encoding; 294 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); 295 useUTF8Flag &= ZipEncodingHelper.isUTF8(encoding); 296 } 297 298 /** 299 * The encoding to use for filenames and the file comment. 300 * 301 * @return null if using the platform's default character encoding. 302 */ 303 public String getEncoding() { 304 return encoding; 305 } 306 307 /** 308 * Whether to set the language encoding flag if the file name 309 * encoding is UTF-8. 310 * 311 * <p>Defaults to true.</p> 312 */ 313 public void setUseLanguageEncodingFlag(boolean b) { 314 useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding); 315 } 316 317 /** 318 * Whether to create Unicode Extra Fields. 319 * 320 * <p>Defaults to NEVER.</p> 321 */ 322 public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) { 323 createUnicodeExtraFields = b; 324 } 325 326 /** 327 * Whether to fall back to UTF and the language encoding flag if 328 * the file name cannot be encoded using the specified encoding. 329 * 330 * <p>Defaults to false.</p> 331 */ 332 public void setFallbackToUTF8(boolean b) { 333 fallbackToUTF8 = b; 334 } 335 336 /** {@inheritDoc} */ 337 public void finish() throws IOException { 338 if (finished) { 339 throw new IOException("This archive has already been finished"); 340 } 341 342 if (entry != null) { 343 throw new IOException("This archives contains unclosed entries."); 344 } 345 346 cdOffset = written; 347 for (Iterator i = entries.iterator(); i.hasNext(); ) { 348 writeCentralFileHeader((ZipArchiveEntry) i.next()); 349 } 350 cdLength = written - cdOffset; 351 writeCentralDirectoryEnd(); 352 offsets.clear(); 353 entries.clear(); 354 finished = true; 355 } 356 357 /** 358 * Writes all necessary data for this entry. 359 * @throws IOException on error 360 */ 361 public void closeArchiveEntry() throws IOException { 362 if (finished) { 363 throw new IOException("Stream has already been finished"); 364 } 365 366 if (entry == null) { 367 throw new IOException("No current entry to close"); 368 } 369 370 long realCrc = crc.getValue(); 371 crc.reset(); 372 373 if (entry.getMethod() == DEFLATED) { 374 def.finish(); 375 while (!def.finished()) { 376 deflate(); 377 } 378 379 entry.setSize(ZipUtil.adjustToLong(def.getTotalIn())); 380 entry.setCompressedSize(ZipUtil.adjustToLong(def.getTotalOut())); 381 entry.setCrc(realCrc); 382 383 def.reset(); 384 385 written += entry.getCompressedSize(); 386 } else if (raf == null) { 387 if (entry.getCrc() != realCrc) { 388 throw new ZipException("bad CRC checksum for entry " 389 + entry.getName() + ": " 390 + Long.toHexString(entry.getCrc()) 391 + " instead of " 392 + Long.toHexString(realCrc)); 393 } 394 395 if (entry.getSize() != written - dataStart) { 396 throw new ZipException("bad size for entry " 397 + entry.getName() + ": " 398 + entry.getSize() 399 + " instead of " 400 + (written - dataStart)); 401 } 402 } else { /* method is STORED and we used RandomAccessFile */ 403 long size = written - dataStart; 404 405 entry.setSize(size); 406 entry.setCompressedSize(size); 407 entry.setCrc(realCrc); 408 } 409 410 // If random access output, write the local file header containing 411 // the correct CRC and compressed/uncompressed sizes 412 if (raf != null) { 413 long save = raf.getFilePointer(); 414 415 raf.seek(localDataStart); 416 writeOut(ZipLong.getBytes(entry.getCrc())); 417 writeOut(ZipLong.getBytes(entry.getCompressedSize())); 418 writeOut(ZipLong.getBytes(entry.getSize())); 419 raf.seek(save); 420 } 421 422 writeDataDescriptor(entry); 423 entry = null; 424 } 425 426 /** 427 * {@inheritDoc} 428 * @throws ClassCastException if entry is not an instance of ZipArchiveEntry 429 */ 430 public void putArchiveEntry(ArchiveEntry archiveEntry) throws IOException { 431 if (finished) { 432 throw new IOException("Stream has already been finished"); 433 } 434 435 if (entry != null) { 436 closeArchiveEntry(); 437 } 438 439 entry = ((ZipArchiveEntry) archiveEntry); 440 entries.add(entry); 441 442 if (entry.getMethod() == -1) { // not specified 443 entry.setMethod(method); 444 } 445 446 if (entry.getTime() == -1) { // not specified 447 entry.setTime(System.currentTimeMillis()); 448 } 449 450 // Size/CRC not required if RandomAccessFile is used 451 if (entry.getMethod() == STORED && raf == null) { 452 if (entry.getSize() == -1) { 453 throw new ZipException("uncompressed size is required for" 454 + " STORED method when not writing to a" 455 + " file"); 456 } 457 if (entry.getCrc() == -1) { 458 throw new ZipException("crc checksum is required for STORED" 459 + " method when not writing to a file"); 460 } 461 entry.setCompressedSize(entry.getSize()); 462 } 463 464 if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) { 465 def.setLevel(level); 466 hasCompressionLevelChanged = false; 467 } 468 writeLocalFileHeader(entry); 469 } 470 471 /** 472 * Set the file comment. 473 * @param comment the comment 474 */ 475 public void setComment(String comment) { 476 this.comment = comment; 477 } 478 479 /** 480 * Sets the compression level for subsequent entries. 481 * 482 * <p>Default is Deflater.DEFAULT_COMPRESSION.</p> 483 * @param level the compression level. 484 * @throws IllegalArgumentException if an invalid compression 485 * level is specified. 486 */ 487 public void setLevel(int level) { 488 if (level < Deflater.DEFAULT_COMPRESSION 489 || level > Deflater.BEST_COMPRESSION) { 490 throw new IllegalArgumentException("Invalid compression level: " 491 + level); 492 } 493 hasCompressionLevelChanged = (this.level != level); 494 this.level = level; 495 } 496 497 /** 498 * Sets the default compression method for subsequent entries. 499 * 500 * <p>Default is DEFLATED.</p> 501 * @param method an <code>int</code> from java.util.zip.ZipEntry 502 */ 503 public void setMethod(int method) { 504 this.method = method; 505 } 506 507 /** 508 * Whether this stream is able to write the given entry. 509 * 510 * <p>May return false if it is set up to use encryption or a 511 * compression method that hasn't been implemented yet.</p> 512 * @since Apache Commons Compress 1.1 513 */ 514 public boolean canWriteEntryData(ArchiveEntry ae) { 515 if (ae instanceof ZipArchiveEntry) { 516 return ZipUtil.canHandleEntryData((ZipArchiveEntry) ae); 517 } 518 return false; 519 } 520 521 /** 522 * Writes bytes to ZIP entry. 523 * @param b the byte array to write 524 * @param offset the start position to write from 525 * @param length the number of bytes to write 526 * @throws IOException on error 527 */ 528 public void write(byte[] b, int offset, int length) throws IOException { 529 ZipUtil.checkRequestedFeatures(entry); 530 if (entry.getMethod() == DEFLATED) { 531 if (length > 0 && !def.finished()) { 532 if (length <= DEFLATER_BLOCK_SIZE) { 533 def.setInput(b, offset, length); 534 deflateUntilInputIsNeeded(); 535 } else { 536 final int fullblocks = length / DEFLATER_BLOCK_SIZE; 537 for (int i = 0; i < fullblocks; i++) { 538 def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE, 539 DEFLATER_BLOCK_SIZE); 540 deflateUntilInputIsNeeded(); 541 } 542 final int done = fullblocks * DEFLATER_BLOCK_SIZE; 543 if (done < length) { 544 def.setInput(b, offset + done, length - done); 545 deflateUntilInputIsNeeded(); 546 } 547 } 548 } 549 } else { 550 writeOut(b, offset, length); 551 written += length; 552 } 553 crc.update(b, offset, length); 554 count(length); 555 } 556 557 /** 558 * Closes this output stream and releases any system resources 559 * associated with the stream. 560 * 561 * @exception IOException if an I/O error occurs. 562 */ 563 public void close() throws IOException { 564 if (!finished) { 565 finish(); 566 } 567 568 if (raf != null) { 569 raf.close(); 570 } 571 if (out != null) { 572 out.close(); 573 } 574 } 575 576 /** 577 * Flushes this output stream and forces any buffered output bytes 578 * to be written out to the stream. 579 * 580 * @exception IOException if an I/O error occurs. 581 */ 582 public void flush() throws IOException { 583 if (out != null) { 584 out.flush(); 585 } 586 } 587 588 /* 589 * Various ZIP constants 590 */ 591 /** 592 * local file header signature 593 */ 594 static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes(); 595 /** 596 * data descriptor signature 597 */ 598 static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes(); 599 /** 600 * central file header signature 601 */ 602 static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes(); 603 /** 604 * end of central dir signature 605 */ 606 static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L); 607 608 /** 609 * Writes next block of compressed data to the output stream. 610 * @throws IOException on error 611 */ 612 protected final void deflate() throws IOException { 613 int len = def.deflate(buf, 0, buf.length); 614 if (len > 0) { 615 writeOut(buf, 0, len); 616 } 617 } 618 619 /** 620 * Writes the local file header entry 621 * @param ze the entry to write 622 * @throws IOException on error 623 */ 624 protected void writeLocalFileHeader(ZipArchiveEntry ze) throws IOException { 625 626 boolean encodable = zipEncoding.canEncode(ze.getName()); 627 628 final ZipEncoding entryEncoding; 629 630 if (!encodable && fallbackToUTF8) { 631 entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING; 632 } else { 633 entryEncoding = zipEncoding; 634 } 635 636 ByteBuffer name = entryEncoding.encode(ze.getName()); 637 638 if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) { 639 640 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS 641 || !encodable) { 642 ze.addExtraField(new UnicodePathExtraField(ze.getName(), 643 name.array(), 644 name.arrayOffset(), 645 name.limit())); 646 } 647 648 String comm = ze.getComment(); 649 if (comm != null && !"".equals(comm)) { 650 651 boolean commentEncodable = this.zipEncoding.canEncode(comm); 652 653 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS 654 || !commentEncodable) { 655 ByteBuffer commentB = entryEncoding.encode(comm); 656 ze.addExtraField(new UnicodeCommentExtraField(comm, 657 commentB.array(), 658 commentB.arrayOffset(), 659 commentB.limit()) 660 ); 661 } 662 } 663 } 664 665 offsets.put(ze, ZipLong.getBytes(written)); 666 667 writeOut(LFH_SIG); 668 written += WORD; 669 670 //store method in local variable to prevent multiple method calls 671 final int zipMethod = ze.getMethod(); 672 673 writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod, 674 !encodable 675 && fallbackToUTF8); 676 written += WORD; 677 678 // compression method 679 writeOut(ZipShort.getBytes(zipMethod)); 680 written += SHORT; 681 682 // last mod. time and date 683 writeOut(ZipUtil.toDosTime(ze.getTime())); 684 written += WORD; 685 686 // CRC 687 // compressed length 688 // uncompressed length 689 localDataStart = written; 690 if (zipMethod == DEFLATED || raf != null) { 691 writeOut(LZERO); 692 writeOut(LZERO); 693 writeOut(LZERO); 694 } else { 695 writeOut(ZipLong.getBytes(ze.getCrc())); 696 writeOut(ZipLong.getBytes(ze.getSize())); 697 writeOut(ZipLong.getBytes(ze.getSize())); 698 } 699 // CheckStyle:MagicNumber OFF 700 written += 12; 701 // CheckStyle:MagicNumber ON 702 703 // file name length 704 writeOut(ZipShort.getBytes(name.limit())); 705 written += SHORT; 706 707 // extra field length 708 byte[] extra = ze.getLocalFileDataExtra(); 709 writeOut(ZipShort.getBytes(extra.length)); 710 written += SHORT; 711 712 // file name 713 writeOut(name.array(), name.arrayOffset(), name.limit()); 714 written += name.limit(); 715 716 // extra field 717 writeOut(extra); 718 written += extra.length; 719 720 dataStart = written; 721 } 722 723 /** 724 * Writes the data descriptor entry. 725 * @param ze the entry to write 726 * @throws IOException on error 727 */ 728 protected void writeDataDescriptor(ZipArchiveEntry ze) throws IOException { 729 if (ze.getMethod() != DEFLATED || raf != null) { 730 return; 731 } 732 writeOut(DD_SIG); 733 writeOut(ZipLong.getBytes(entry.getCrc())); 734 writeOut(ZipLong.getBytes(entry.getCompressedSize())); 735 writeOut(ZipLong.getBytes(entry.getSize())); 736 // CheckStyle:MagicNumber OFF 737 written += 16; 738 // CheckStyle:MagicNumber ON 739 } 740 741 /** 742 * Writes the central file header entry. 743 * @param ze the entry to write 744 * @throws IOException on error 745 */ 746 protected void writeCentralFileHeader(ZipArchiveEntry ze) throws IOException { 747 writeOut(CFH_SIG); 748 written += WORD; 749 750 // version made by 751 // CheckStyle:MagicNumber OFF 752 writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20)); 753 written += SHORT; 754 755 final int zipMethod = ze.getMethod(); 756 final boolean encodable = zipEncoding.canEncode(ze.getName()); 757 writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod, 758 !encodable 759 && fallbackToUTF8); 760 written += WORD; 761 762 // compression method 763 writeOut(ZipShort.getBytes(zipMethod)); 764 written += SHORT; 765 766 // last mod. time and date 767 writeOut(ZipUtil.toDosTime(ze.getTime())); 768 written += WORD; 769 770 // CRC 771 // compressed length 772 // uncompressed length 773 writeOut(ZipLong.getBytes(ze.getCrc())); 774 writeOut(ZipLong.getBytes(ze.getCompressedSize())); 775 writeOut(ZipLong.getBytes(ze.getSize())); 776 // CheckStyle:MagicNumber OFF 777 written += 12; 778 // CheckStyle:MagicNumber ON 779 780 // file name length 781 final ZipEncoding entryEncoding; 782 783 if (!encodable && fallbackToUTF8) { 784 entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING; 785 } else { 786 entryEncoding = zipEncoding; 787 } 788 789 ByteBuffer name = entryEncoding.encode(ze.getName()); 790 791 writeOut(ZipShort.getBytes(name.limit())); 792 written += SHORT; 793 794 // extra field length 795 byte[] extra = ze.getCentralDirectoryExtra(); 796 writeOut(ZipShort.getBytes(extra.length)); 797 written += SHORT; 798 799 // file comment length 800 String comm = ze.getComment(); 801 if (comm == null) { 802 comm = ""; 803 } 804 805 ByteBuffer commentB = entryEncoding.encode(comm); 806 807 writeOut(ZipShort.getBytes(commentB.limit())); 808 written += SHORT; 809 810 // disk number start 811 writeOut(ZERO); 812 written += SHORT; 813 814 // internal file attributes 815 writeOut(ZipShort.getBytes(ze.getInternalAttributes())); 816 written += SHORT; 817 818 // external file attributes 819 writeOut(ZipLong.getBytes(ze.getExternalAttributes())); 820 written += WORD; 821 822 // relative offset of LFH 823 writeOut((byte[]) offsets.get(ze)); 824 written += WORD; 825 826 // file name 827 writeOut(name.array(), name.arrayOffset(), name.limit()); 828 written += name.limit(); 829 830 // extra field 831 writeOut(extra); 832 written += extra.length; 833 834 // file comment 835 writeOut(commentB.array(), commentB.arrayOffset(), commentB.limit()); 836 written += commentB.limit(); 837 } 838 839 /** 840 * Writes the "End of central dir record". 841 * @throws IOException on error 842 */ 843 protected void writeCentralDirectoryEnd() throws IOException { 844 writeOut(EOCD_SIG); 845 846 // disk numbers 847 writeOut(ZERO); 848 writeOut(ZERO); 849 850 // number of entries 851 byte[] num = ZipShort.getBytes(entries.size()); 852 writeOut(num); 853 writeOut(num); 854 855 // length and location of CD 856 writeOut(ZipLong.getBytes(cdLength)); 857 writeOut(ZipLong.getBytes(cdOffset)); 858 859 // ZIP file comment 860 ByteBuffer data = this.zipEncoding.encode(comment); 861 writeOut(ZipShort.getBytes(data.limit())); 862 writeOut(data.array(), data.arrayOffset(), data.limit()); 863 } 864 865 /** 866 * Write bytes to output or random access file. 867 * @param data the byte array to write 868 * @throws IOException on error 869 */ 870 protected final void writeOut(byte[] data) throws IOException { 871 writeOut(data, 0, data.length); 872 } 873 874 /** 875 * Write bytes to output or random access file. 876 * @param data the byte array to write 877 * @param offset the start position to write from 878 * @param length the number of bytes to write 879 * @throws IOException on error 880 */ 881 protected final void writeOut(byte[] data, int offset, int length) 882 throws IOException { 883 if (raf != null) { 884 raf.write(data, offset, length); 885 } else { 886 out.write(data, offset, length); 887 } 888 } 889 890 private void deflateUntilInputIsNeeded() throws IOException { 891 while (!def.needsInput()) { 892 deflate(); 893 } 894 } 895 896 private void writeVersionNeededToExtractAndGeneralPurposeBits(final int 897 zipMethod, 898 final boolean 899 utfFallback) 900 throws IOException { 901 902 // CheckStyle:MagicNumber OFF 903 int versionNeededToExtract = 10; 904 GeneralPurposeBit b = new GeneralPurposeBit(); 905 b.useUTF8ForNames(useUTF8Flag || utfFallback); 906 if (zipMethod == DEFLATED && raf == null) { 907 // requires version 2 as we are going to store length info 908 // in the data descriptor 909 versionNeededToExtract = 20; 910 b.useDataDescriptor(true); 911 } 912 // CheckStyle:MagicNumber ON 913 914 // version needed to extract 915 writeOut(ZipShort.getBytes(versionNeededToExtract)); 916 // general purpose bit flag 917 writeOut(b.encode()); 918 } 919 920 /** 921 * enum that represents the possible policies for creating Unicode 922 * extra fields. 923 */ 924 public static final class UnicodeExtraFieldPolicy { 925 /** 926 * Always create Unicode extra fields. 927 */ 928 public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always"); 929 /** 930 * Never create Unicode extra fields. 931 */ 932 public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never"); 933 /** 934 * Create Unicode extra fields for filenames that cannot be 935 * encoded using the specified encoding. 936 */ 937 public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = 938 new UnicodeExtraFieldPolicy("not encodeable"); 939 940 private final String name; 941 private UnicodeExtraFieldPolicy(String n) { 942 name = n; 943 } 944 public String toString() { 945 return name; 946 } 947 } 948 949 /** 950 * Creates a new zip entry taking some information from the given 951 * file and using the provided name. 952 * 953 * <p>The name will be adjusted to end with a forward slash "/" if 954 * the file is a directory. If the file is not a directory a 955 * potential trailing forward slash will be stripped from the 956 * entry name.</p> 957 * 958 * <p>Must not be used if the stream has already been closed.</p> 959 */ 960 public ArchiveEntry createArchiveEntry(File inputFile, String entryName) 961 throws IOException { 962 if (finished) { 963 throw new IOException("Stream has already been finished"); 964 } 965 return new ZipArchiveEntry(inputFile, entryName); 966 } 967 }