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) { 532 if (!def.finished()) { 533 if (length <= DEFLATER_BLOCK_SIZE) { 534 def.setInput(b, offset, length); 535 deflateUntilInputIsNeeded(); 536 } else { 537 final int fullblocks = length / DEFLATER_BLOCK_SIZE; 538 for (int i = 0; i < fullblocks; i++) { 539 def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE, 540 DEFLATER_BLOCK_SIZE); 541 deflateUntilInputIsNeeded(); 542 } 543 final int done = fullblocks * DEFLATER_BLOCK_SIZE; 544 if (done < length) { 545 def.setInput(b, offset + done, length - done); 546 deflateUntilInputIsNeeded(); 547 } 548 } 549 } 550 } 551 } else { 552 writeOut(b, offset, length); 553 written += length; 554 } 555 crc.update(b, offset, length); 556 count(length); 557 } 558 559 /** 560 * Closes this output stream and releases any system resources 561 * associated with the stream. 562 * 563 * @exception IOException if an I/O error occurs. 564 */ 565 public void close() throws IOException { 566 if (!finished) { 567 finish(); 568 } 569 570 if (raf != null) { 571 raf.close(); 572 } 573 if (out != null) { 574 out.close(); 575 } 576 } 577 578 /** 579 * Flushes this output stream and forces any buffered output bytes 580 * to be written out to the stream. 581 * 582 * @exception IOException if an I/O error occurs. 583 */ 584 public void flush() throws IOException { 585 if (out != null) { 586 out.flush(); 587 } 588 } 589 590 /* 591 * Various ZIP constants 592 */ 593 /** 594 * local file header signature 595 */ 596 static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes(); 597 /** 598 * data descriptor signature 599 */ 600 static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes(); 601 /** 602 * central file header signature 603 */ 604 static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes(); 605 /** 606 * end of central dir signature 607 */ 608 static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L); 609 610 /** 611 * Writes next block of compressed data to the output stream. 612 * @throws IOException on error 613 */ 614 protected final void deflate() throws IOException { 615 int len = def.deflate(buf, 0, buf.length); 616 if (len > 0) { 617 writeOut(buf, 0, len); 618 } 619 } 620 621 /** 622 * Writes the local file header entry 623 * @param ze the entry to write 624 * @throws IOException on error 625 */ 626 protected void writeLocalFileHeader(ZipArchiveEntry ze) throws IOException { 627 628 boolean encodable = zipEncoding.canEncode(ze.getName()); 629 630 final ZipEncoding entryEncoding; 631 632 if (!encodable && fallbackToUTF8) { 633 entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING; 634 } else { 635 entryEncoding = zipEncoding; 636 } 637 638 ByteBuffer name = entryEncoding.encode(ze.getName()); 639 640 if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) { 641 642 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS 643 || !encodable) { 644 ze.addExtraField(new UnicodePathExtraField(ze.getName(), 645 name.array(), 646 name.arrayOffset(), 647 name.limit())); 648 } 649 650 String comm = ze.getComment(); 651 if (comm != null && !"".equals(comm)) { 652 653 boolean commentEncodable = this.zipEncoding.canEncode(comm); 654 655 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS 656 || !commentEncodable) { 657 ByteBuffer commentB = entryEncoding.encode(comm); 658 ze.addExtraField(new UnicodeCommentExtraField(comm, 659 commentB.array(), 660 commentB.arrayOffset(), 661 commentB.limit()) 662 ); 663 } 664 } 665 } 666 667 offsets.put(ze, ZipLong.getBytes(written)); 668 669 writeOut(LFH_SIG); 670 written += WORD; 671 672 //store method in local variable to prevent multiple method calls 673 final int zipMethod = ze.getMethod(); 674 675 writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod, 676 !encodable 677 && fallbackToUTF8); 678 written += WORD; 679 680 // compression method 681 writeOut(ZipShort.getBytes(zipMethod)); 682 written += SHORT; 683 684 // last mod. time and date 685 writeOut(ZipUtil.toDosTime(ze.getTime())); 686 written += WORD; 687 688 // CRC 689 // compressed length 690 // uncompressed length 691 localDataStart = written; 692 if (zipMethod == DEFLATED || raf != null) { 693 writeOut(LZERO); 694 writeOut(LZERO); 695 writeOut(LZERO); 696 } else { 697 writeOut(ZipLong.getBytes(ze.getCrc())); 698 writeOut(ZipLong.getBytes(ze.getSize())); 699 writeOut(ZipLong.getBytes(ze.getSize())); 700 } 701 // CheckStyle:MagicNumber OFF 702 written += 12; 703 // CheckStyle:MagicNumber ON 704 705 // file name length 706 writeOut(ZipShort.getBytes(name.limit())); 707 written += SHORT; 708 709 // extra field length 710 byte[] extra = ze.getLocalFileDataExtra(); 711 writeOut(ZipShort.getBytes(extra.length)); 712 written += SHORT; 713 714 // file name 715 writeOut(name.array(), name.arrayOffset(), name.limit()); 716 written += name.limit(); 717 718 // extra field 719 writeOut(extra); 720 written += extra.length; 721 722 dataStart = written; 723 } 724 725 /** 726 * Writes the data descriptor entry. 727 * @param ze the entry to write 728 * @throws IOException on error 729 */ 730 protected void writeDataDescriptor(ZipArchiveEntry ze) throws IOException { 731 if (ze.getMethod() != DEFLATED || raf != null) { 732 return; 733 } 734 writeOut(DD_SIG); 735 writeOut(ZipLong.getBytes(entry.getCrc())); 736 writeOut(ZipLong.getBytes(entry.getCompressedSize())); 737 writeOut(ZipLong.getBytes(entry.getSize())); 738 // CheckStyle:MagicNumber OFF 739 written += 16; 740 // CheckStyle:MagicNumber ON 741 } 742 743 /** 744 * Writes the central file header entry. 745 * @param ze the entry to write 746 * @throws IOException on error 747 */ 748 protected void writeCentralFileHeader(ZipArchiveEntry ze) throws IOException { 749 writeOut(CFH_SIG); 750 written += WORD; 751 752 // version made by 753 // CheckStyle:MagicNumber OFF 754 writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20)); 755 written += SHORT; 756 757 final int zipMethod = ze.getMethod(); 758 final boolean encodable = zipEncoding.canEncode(ze.getName()); 759 writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod, 760 !encodable 761 && fallbackToUTF8); 762 written += WORD; 763 764 // compression method 765 writeOut(ZipShort.getBytes(zipMethod)); 766 written += SHORT; 767 768 // last mod. time and date 769 writeOut(ZipUtil.toDosTime(ze.getTime())); 770 written += WORD; 771 772 // CRC 773 // compressed length 774 // uncompressed length 775 writeOut(ZipLong.getBytes(ze.getCrc())); 776 writeOut(ZipLong.getBytes(ze.getCompressedSize())); 777 writeOut(ZipLong.getBytes(ze.getSize())); 778 // CheckStyle:MagicNumber OFF 779 written += 12; 780 // CheckStyle:MagicNumber ON 781 782 // file name length 783 final ZipEncoding entryEncoding; 784 785 if (!encodable && fallbackToUTF8) { 786 entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING; 787 } else { 788 entryEncoding = zipEncoding; 789 } 790 791 ByteBuffer name = entryEncoding.encode(ze.getName()); 792 793 writeOut(ZipShort.getBytes(name.limit())); 794 written += SHORT; 795 796 // extra field length 797 byte[] extra = ze.getCentralDirectoryExtra(); 798 writeOut(ZipShort.getBytes(extra.length)); 799 written += SHORT; 800 801 // file comment length 802 String comm = ze.getComment(); 803 if (comm == null) { 804 comm = ""; 805 } 806 807 ByteBuffer commentB = entryEncoding.encode(comm); 808 809 writeOut(ZipShort.getBytes(commentB.limit())); 810 written += SHORT; 811 812 // disk number start 813 writeOut(ZERO); 814 written += SHORT; 815 816 // internal file attributes 817 writeOut(ZipShort.getBytes(ze.getInternalAttributes())); 818 written += SHORT; 819 820 // external file attributes 821 writeOut(ZipLong.getBytes(ze.getExternalAttributes())); 822 written += WORD; 823 824 // relative offset of LFH 825 writeOut((byte[]) offsets.get(ze)); 826 written += WORD; 827 828 // file name 829 writeOut(name.array(), name.arrayOffset(), name.limit()); 830 written += name.limit(); 831 832 // extra field 833 writeOut(extra); 834 written += extra.length; 835 836 // file comment 837 writeOut(commentB.array(), commentB.arrayOffset(), commentB.limit()); 838 written += commentB.limit(); 839 } 840 841 /** 842 * Writes the "End of central dir record". 843 * @throws IOException on error 844 */ 845 protected void writeCentralDirectoryEnd() throws IOException { 846 writeOut(EOCD_SIG); 847 848 // disk numbers 849 writeOut(ZERO); 850 writeOut(ZERO); 851 852 // number of entries 853 byte[] num = ZipShort.getBytes(entries.size()); 854 writeOut(num); 855 writeOut(num); 856 857 // length and location of CD 858 writeOut(ZipLong.getBytes(cdLength)); 859 writeOut(ZipLong.getBytes(cdOffset)); 860 861 // ZIP file comment 862 ByteBuffer data = this.zipEncoding.encode(comment); 863 writeOut(ZipShort.getBytes(data.limit())); 864 writeOut(data.array(), data.arrayOffset(), data.limit()); 865 } 866 867 /** 868 * Write bytes to output or random access file. 869 * @param data the byte array to write 870 * @throws IOException on error 871 */ 872 protected final void writeOut(byte[] data) throws IOException { 873 writeOut(data, 0, data.length); 874 } 875 876 /** 877 * Write bytes to output or random access file. 878 * @param data the byte array to write 879 * @param offset the start position to write from 880 * @param length the number of bytes to write 881 * @throws IOException on error 882 */ 883 protected final void writeOut(byte[] data, int offset, int length) 884 throws IOException { 885 if (raf != null) { 886 raf.write(data, offset, length); 887 } else { 888 out.write(data, offset, length); 889 } 890 } 891 892 private void deflateUntilInputIsNeeded() throws IOException { 893 while (!def.needsInput()) { 894 deflate(); 895 } 896 } 897 898 private void writeVersionNeededToExtractAndGeneralPurposeBits(final int 899 zipMethod, 900 final boolean 901 utfFallback) 902 throws IOException { 903 904 // CheckStyle:MagicNumber OFF 905 int versionNeededToExtract = 10; 906 GeneralPurposeBit b = new GeneralPurposeBit(); 907 b.useUTF8ForNames(useUTF8Flag || utfFallback); 908 if (zipMethod == DEFLATED && raf == null) { 909 // requires version 2 as we are going to store length info 910 // in the data descriptor 911 versionNeededToExtract = 20; 912 b.useDataDescriptor(true); 913 } 914 // CheckStyle:MagicNumber ON 915 916 // version needed to extract 917 writeOut(ZipShort.getBytes(versionNeededToExtract)); 918 // general purpose bit flag 919 writeOut(b.encode()); 920 } 921 922 /** 923 * enum that represents the possible policies for creating Unicode 924 * extra fields. 925 */ 926 public static final class UnicodeExtraFieldPolicy { 927 /** 928 * Always create Unicode extra fields. 929 */ 930 public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always"); 931 /** 932 * Never create Unicode extra fields. 933 */ 934 public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never"); 935 /** 936 * Create Unicode extra fields for filenames that cannot be 937 * encoded using the specified encoding. 938 */ 939 public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = 940 new UnicodeExtraFieldPolicy("not encodeable"); 941 942 private final String name; 943 private UnicodeExtraFieldPolicy(String n) { 944 name = n; 945 } 946 public String toString() { 947 return name; 948 } 949 } 950 951 /** 952 * Creates a new zip entry taking some information from the given 953 * file and using the provided name. 954 * 955 * <p>The name will be adjusted to end with a forward slash "/" if 956 * the file is a directory. If the file is not a directory a 957 * potential trailing forward slash will be stripped from the 958 * entry name.</p> 959 * 960 * <p>Must not be used if the stream has already been closed.</p> 961 */ 962 public ArchiveEntry createArchiveEntry(File inputFile, String entryName) 963 throws IOException { 964 if (finished) { 965 throw new IOException("Stream has already been finished"); 966 } 967 return new ZipArchiveEntry(inputFile, entryName); 968 } 969 }