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 */ 101 public static final int EFS_FLAG = 1 << 11; 102 103 /** 104 * Current entry. 105 */ 106 private ZipArchiveEntry entry; 107 108 /** 109 * The file comment. 110 */ 111 private String comment = ""; 112 113 /** 114 * Compression level for next entry. 115 */ 116 private int level = DEFAULT_COMPRESSION; 117 118 /** 119 * Has the compression level changed when compared to the last 120 * entry? 121 */ 122 private boolean hasCompressionLevelChanged = false; 123 124 /** 125 * Default compression method for next entry. 126 */ 127 private int method = java.util.zip.ZipEntry.DEFLATED; 128 129 /** 130 * List of ZipArchiveEntries written so far. 131 */ 132 private final List entries = new LinkedList(); 133 134 /** 135 * CRC instance to avoid parsing DEFLATED data twice. 136 */ 137 private final CRC32 crc = new CRC32(); 138 139 /** 140 * Count the bytes written to out. 141 */ 142 private long written = 0; 143 144 /** 145 * Data for local header data 146 */ 147 private long dataStart = 0; 148 149 /** 150 * Offset for CRC entry in the local file header data for the 151 * current entry starts here. 152 */ 153 private long localDataStart = 0; 154 155 /** 156 * Start of central directory. 157 */ 158 private long cdOffset = 0; 159 160 /** 161 * Length of central directory. 162 */ 163 private long cdLength = 0; 164 165 /** 166 * Helper, a 0 as ZipShort. 167 */ 168 private static final byte[] ZERO = {0, 0}; 169 170 /** 171 * Helper, a 0 as ZipLong. 172 */ 173 private static final byte[] LZERO = {0, 0, 0, 0}; 174 175 /** 176 * Holds the offsets of the LFH starts for each entry. 177 */ 178 private final Map offsets = new HashMap(); 179 180 /** 181 * The encoding to use for filenames and the file comment. 182 * 183 * <p>For a list of possible values see <a 184 * 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>. 185 * Defaults to UTF-8.</p> 186 */ 187 private String encoding = DEFAULT_ENCODING; 188 189 /** 190 * The zip encoding to use for filenames and the file comment. 191 * 192 * This field is of internal use and will be set in {@link 193 * #setEncoding(String)}. 194 */ 195 private ZipEncoding zipEncoding = 196 ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING); 197 198 /** 199 * This Deflater object is used for output. 200 * 201 */ 202 protected final Deflater def = new Deflater(level, true); 203 204 /** 205 * This buffer servers as a Deflater. 206 * 207 */ 208 private final byte[] buf = new byte[BUFFER_SIZE]; 209 210 /** 211 * Optional random access output. 212 */ 213 private final RandomAccessFile raf; 214 215 private final OutputStream out; 216 217 /** 218 * whether to use the EFS flag when writing UTF-8 filenames or not. 219 */ 220 private boolean useEFS = true; 221 222 /** 223 * Whether to encode non-encodable file names as UTF-8. 224 */ 225 private boolean fallbackToUTF8 = false; 226 227 /** 228 * whether to create UnicodePathExtraField-s for each entry. 229 */ 230 private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER; 231 232 /** 233 * Creates a new ZIP OutputStream filtering the underlying stream. 234 * @param out the outputstream to zip 235 */ 236 public ZipArchiveOutputStream(OutputStream out) { 237 this.out = out; 238 this.raf = null; 239 } 240 241 /** 242 * Creates a new ZIP OutputStream writing to a File. Will use 243 * random access if possible. 244 * @param file the file to zip to 245 * @throws IOException on error 246 */ 247 public ZipArchiveOutputStream(File file) throws IOException { 248 OutputStream o = null; 249 RandomAccessFile _raf = null; 250 try { 251 _raf = new RandomAccessFile(file, "rw"); 252 _raf.setLength(0); 253 } catch (IOException e) { 254 if (_raf != null) { 255 try { 256 _raf.close(); 257 } catch (IOException inner) { 258 // ignore 259 } 260 _raf = null; 261 } 262 o = new FileOutputStream(file); 263 } 264 out = o; 265 raf = _raf; 266 } 267 268 /** 269 * This method indicates whether this archive is writing to a 270 * seekable stream (i.e., to a random access file). 271 * 272 * <p>For seekable streams, you don't need to calculate the CRC or 273 * uncompressed size for {@link #STORED} entries before 274 * invoking {@link #putArchiveEntry(ArchiveEntry)}. 275 * @return true if seekable 276 */ 277 public boolean isSeekable() { 278 return raf != null; 279 } 280 281 /** 282 * The encoding to use for filenames and the file comment. 283 * 284 * <p>For a list of possible values see <a 285 * 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>. 286 * Defaults to UTF-8.</p> 287 * @param encoding the encoding to use for file names, use null 288 * for the platform's default encoding 289 */ 290 public void setEncoding(final String encoding) { 291 this.encoding = encoding; 292 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); 293 useEFS &= ZipEncodingHelper.isUTF8(encoding); 294 } 295 296 /** 297 * The encoding to use for filenames and the file comment. 298 * 299 * @return null if using the platform's default character encoding. 300 */ 301 public String getEncoding() { 302 return encoding; 303 } 304 305 /** 306 * Whether to set the language encoding flag if the file name 307 * encoding is UTF-8. 308 * 309 * <p>Defaults to true.</p> 310 */ 311 public void setUseLanguageEncodingFlag(boolean b) { 312 useEFS = b && ZipEncodingHelper.isUTF8(encoding); 313 } 314 315 /** 316 * Whether to create Unicode Extra Fields. 317 * 318 * <p>Defaults to NEVER.</p> 319 */ 320 public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) { 321 createUnicodeExtraFields = b; 322 } 323 324 /** 325 * Whether to fall back to UTF and the language encoding flag if 326 * the file name cannot be encoded using the specified encoding. 327 * 328 * <p>Defaults to false.</p> 329 */ 330 public void setFallbackToUTF8(boolean b) { 331 fallbackToUTF8 = b; 332 } 333 334 /* (non-Javadoc) 335 * @see org.apache.commons.compress.archivers.ArchiveOutputStream#finish() 336 */ 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 /** {@inheritDoc} */ 427 // @throws ClassCastException if entry is not an instance of ZipArchiveEntry 428 public void putArchiveEntry(ArchiveEntry archiveEntry) throws IOException { 429 if(finished) { 430 throw new IOException("Stream has already been finished"); 431 } 432 433 if (entry != null) { 434 closeArchiveEntry(); 435 } 436 437 entry = ((ZipArchiveEntry) archiveEntry); 438 entries.add(entry); 439 440 if (entry.getMethod() == -1) { // not specified 441 entry.setMethod(method); 442 } 443 444 if (entry.getTime() == -1) { // not specified 445 entry.setTime(System.currentTimeMillis()); 446 } 447 448 // Size/CRC not required if RandomAccessFile is used 449 if (entry.getMethod() == STORED && raf == null) { 450 if (entry.getSize() == -1) { 451 throw new ZipException("uncompressed size is required for" 452 + " STORED method when not writing to a" 453 + " file"); 454 } 455 if (entry.getCrc() == -1) { 456 throw new ZipException("crc checksum is required for STORED" 457 + " method when not writing to a file"); 458 } 459 entry.setCompressedSize(entry.getSize()); 460 } 461 462 if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) { 463 def.setLevel(level); 464 hasCompressionLevelChanged = false; 465 } 466 writeLocalFileHeader(entry); 467 } 468 469 /** 470 * Set the file comment. 471 * @param comment the comment 472 */ 473 public void setComment(String comment) { 474 this.comment = comment; 475 } 476 477 /** 478 * Sets the compression level for subsequent entries. 479 * 480 * <p>Default is Deflater.DEFAULT_COMPRESSION.</p> 481 * @param level the compression level. 482 * @throws IllegalArgumentException if an invalid compression 483 * level is specified. 484 */ 485 public void setLevel(int level) { 486 if (level < Deflater.DEFAULT_COMPRESSION 487 || level > Deflater.BEST_COMPRESSION) { 488 throw new IllegalArgumentException("Invalid compression level: " 489 + level); 490 } 491 hasCompressionLevelChanged = (this.level != level); 492 this.level = level; 493 } 494 495 /** 496 * Sets the default compression method for subsequent entries. 497 * 498 * <p>Default is DEFLATED.</p> 499 * @param method an <code>int</code> from java.util.zip.ZipEntry 500 */ 501 public void setMethod(int method) { 502 this.method = method; 503 } 504 505 /** 506 * Writes bytes to ZIP entry. 507 * @param b the byte array to write 508 * @param offset the start position to write from 509 * @param length the number of bytes to write 510 * @throws IOException on error 511 */ 512 public void write(byte[] b, int offset, int length) throws IOException { 513 if (entry.getMethod() == DEFLATED) { 514 if (length > 0) { 515 if (!def.finished()) { 516 if (length <= DEFLATER_BLOCK_SIZE) { 517 def.setInput(b, offset, length); 518 deflateUntilInputIsNeeded(); 519 } else { 520 final int fullblocks = length / DEFLATER_BLOCK_SIZE; 521 for (int i = 0; i < fullblocks; i++) { 522 def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE, 523 DEFLATER_BLOCK_SIZE); 524 deflateUntilInputIsNeeded(); 525 } 526 final int done = fullblocks * DEFLATER_BLOCK_SIZE; 527 if (done < length) { 528 def.setInput(b, offset + done, length - done); 529 deflateUntilInputIsNeeded(); 530 } 531 } 532 } 533 } 534 } else { 535 writeOut(b, offset, length); 536 written += length; 537 } 538 crc.update(b, offset, length); 539 count(length); 540 } 541 542 /** 543 * Closes this output stream and releases any system resources 544 * associated with the stream. 545 * 546 * @exception IOException if an I/O error occurs. 547 */ 548 public void close() throws IOException { 549 if(!finished) { 550 finish(); 551 } 552 553 if (raf != null) { 554 raf.close(); 555 } 556 if (out != null) { 557 out.close(); 558 } 559 } 560 561 /** 562 * Flushes this output stream and forces any buffered output bytes 563 * to be written out to the stream. 564 * 565 * @exception IOException if an I/O error occurs. 566 */ 567 public void flush() throws IOException { 568 if (out != null) { 569 out.flush(); 570 } 571 } 572 573 /* 574 * Various ZIP constants 575 */ 576 /** 577 * local file header signature 578 */ 579 static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes(); 580 /** 581 * data descriptor signature 582 */ 583 static final byte[] DD_SIG = ZipLong.getBytes(0X08074B50L); 584 /** 585 * central file header signature 586 */ 587 static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes(); 588 /** 589 * end of central dir signature 590 */ 591 static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L); 592 593 /** 594 * Writes next block of compressed data to the output stream. 595 * @throws IOException on error 596 */ 597 protected final void deflate() throws IOException { 598 int len = def.deflate(buf, 0, buf.length); 599 if (len > 0) { 600 writeOut(buf, 0, len); 601 } 602 } 603 604 /** 605 * Writes the local file header entry 606 * @param ze the entry to write 607 * @throws IOException on error 608 */ 609 protected void writeLocalFileHeader(ZipArchiveEntry ze) throws IOException { 610 611 boolean encodable = zipEncoding.canEncode(ze.getName()); 612 613 final ZipEncoding entryEncoding; 614 615 if (!encodable && fallbackToUTF8) { 616 entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING; 617 } else { 618 entryEncoding = zipEncoding; 619 } 620 621 ByteBuffer name = entryEncoding.encode(ze.getName()); 622 623 if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) { 624 625 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS 626 || !encodable) { 627 ze.addExtraField(new UnicodePathExtraField(ze.getName(), 628 name.array(), 629 name.arrayOffset(), 630 name.limit())); 631 } 632 633 String comm = ze.getComment(); 634 if (comm != null && !"".equals(comm)) { 635 636 boolean commentEncodable = this.zipEncoding.canEncode(comm); 637 638 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS 639 || !commentEncodable) { 640 ByteBuffer commentB = entryEncoding.encode(comm); 641 ze.addExtraField(new UnicodeCommentExtraField(comm, 642 commentB.array(), 643 commentB.arrayOffset(), 644 commentB.limit()) 645 ); 646 } 647 } 648 } 649 650 offsets.put(ze, ZipLong.getBytes(written)); 651 652 writeOut(LFH_SIG); 653 written += WORD; 654 655 //store method in local variable to prevent multiple method calls 656 final int zipMethod = ze.getMethod(); 657 658 writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod, 659 !encodable 660 && fallbackToUTF8); 661 written += WORD; 662 663 // compression method 664 writeOut(ZipShort.getBytes(zipMethod)); 665 written += SHORT; 666 667 // last mod. time and date 668 writeOut(ZipUtil.toDosTime(ze.getTime())); 669 written += WORD; 670 671 // CRC 672 // compressed length 673 // uncompressed length 674 localDataStart = written; 675 if (zipMethod == DEFLATED || raf != null) { 676 writeOut(LZERO); 677 writeOut(LZERO); 678 writeOut(LZERO); 679 } else { 680 writeOut(ZipLong.getBytes(ze.getCrc())); 681 writeOut(ZipLong.getBytes(ze.getSize())); 682 writeOut(ZipLong.getBytes(ze.getSize())); 683 } 684 // CheckStyle:MagicNumber OFF 685 written += 12; 686 // CheckStyle:MagicNumber ON 687 688 // file name length 689 writeOut(ZipShort.getBytes(name.limit())); 690 written += SHORT; 691 692 // extra field length 693 byte[] extra = ze.getLocalFileDataExtra(); 694 writeOut(ZipShort.getBytes(extra.length)); 695 written += SHORT; 696 697 // file name 698 writeOut(name.array(), name.arrayOffset(), name.limit()); 699 written += name.limit(); 700 701 // extra field 702 writeOut(extra); 703 written += extra.length; 704 705 dataStart = written; 706 } 707 708 /** 709 * Writes the data descriptor entry. 710 * @param ze the entry to write 711 * @throws IOException on error 712 */ 713 protected void writeDataDescriptor(ZipArchiveEntry ze) throws IOException { 714 if (ze.getMethod() != DEFLATED || raf != null) { 715 return; 716 } 717 writeOut(DD_SIG); 718 writeOut(ZipLong.getBytes(entry.getCrc())); 719 writeOut(ZipLong.getBytes(entry.getCompressedSize())); 720 writeOut(ZipLong.getBytes(entry.getSize())); 721 // CheckStyle:MagicNumber OFF 722 written += 16; 723 // CheckStyle:MagicNumber ON 724 } 725 726 /** 727 * Writes the central file header entry. 728 * @param ze the entry to write 729 * @throws IOException on error 730 */ 731 protected void writeCentralFileHeader(ZipArchiveEntry ze) throws IOException { 732 writeOut(CFH_SIG); 733 written += WORD; 734 735 // version made by 736 // CheckStyle:MagicNumber OFF 737 writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20)); 738 written += SHORT; 739 740 final int zipMethod = ze.getMethod(); 741 final boolean encodable = zipEncoding.canEncode(ze.getName()); 742 writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod, 743 !encodable 744 && fallbackToUTF8); 745 written += WORD; 746 747 // compression method 748 writeOut(ZipShort.getBytes(zipMethod)); 749 written += SHORT; 750 751 // last mod. time and date 752 writeOut(ZipUtil.toDosTime(ze.getTime())); 753 written += WORD; 754 755 // CRC 756 // compressed length 757 // uncompressed length 758 writeOut(ZipLong.getBytes(ze.getCrc())); 759 writeOut(ZipLong.getBytes(ze.getCompressedSize())); 760 writeOut(ZipLong.getBytes(ze.getSize())); 761 // CheckStyle:MagicNumber OFF 762 written += 12; 763 // CheckStyle:MagicNumber ON 764 765 // file name length 766 final ZipEncoding entryEncoding; 767 768 if (!encodable && fallbackToUTF8) { 769 entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING; 770 } else { 771 entryEncoding = zipEncoding; 772 } 773 774 ByteBuffer name = entryEncoding.encode(ze.getName()); 775 776 writeOut(ZipShort.getBytes(name.limit())); 777 written += SHORT; 778 779 // extra field length 780 byte[] extra = ze.getCentralDirectoryExtra(); 781 writeOut(ZipShort.getBytes(extra.length)); 782 written += SHORT; 783 784 // file comment length 785 String comm = ze.getComment(); 786 if (comm == null) { 787 comm = ""; 788 } 789 790 ByteBuffer commentB = entryEncoding.encode(comm); 791 792 writeOut(ZipShort.getBytes(commentB.limit())); 793 written += SHORT; 794 795 // disk number start 796 writeOut(ZERO); 797 written += SHORT; 798 799 // internal file attributes 800 writeOut(ZipShort.getBytes(ze.getInternalAttributes())); 801 written += SHORT; 802 803 // external file attributes 804 writeOut(ZipLong.getBytes(ze.getExternalAttributes())); 805 written += WORD; 806 807 // relative offset of LFH 808 writeOut((byte[]) offsets.get(ze)); 809 written += WORD; 810 811 // file name 812 writeOut(name.array(), name.arrayOffset(), name.limit()); 813 written += name.limit(); 814 815 // extra field 816 writeOut(extra); 817 written += extra.length; 818 819 // file comment 820 writeOut(commentB.array(), commentB.arrayOffset(), commentB.limit()); 821 written += commentB.limit(); 822 } 823 824 /** 825 * Writes the "End of central dir record". 826 * @throws IOException on error 827 */ 828 protected void writeCentralDirectoryEnd() throws IOException { 829 writeOut(EOCD_SIG); 830 831 // disk numbers 832 writeOut(ZERO); 833 writeOut(ZERO); 834 835 // number of entries 836 byte[] num = ZipShort.getBytes(entries.size()); 837 writeOut(num); 838 writeOut(num); 839 840 // length and location of CD 841 writeOut(ZipLong.getBytes(cdLength)); 842 writeOut(ZipLong.getBytes(cdOffset)); 843 844 // ZIP file comment 845 ByteBuffer data = this.zipEncoding.encode(comment); 846 writeOut(ZipShort.getBytes(data.limit())); 847 writeOut(data.array(), data.arrayOffset(), data.limit()); 848 } 849 850 /** 851 * Write bytes to output or random access file. 852 * @param data the byte array to write 853 * @throws IOException on error 854 */ 855 protected final void writeOut(byte[] data) throws IOException { 856 writeOut(data, 0, data.length); 857 } 858 859 /** 860 * Write bytes to output or random access file. 861 * @param data the byte array to write 862 * @param offset the start position to write from 863 * @param length the number of bytes to write 864 * @throws IOException on error 865 */ 866 protected final void writeOut(byte[] data, int offset, int length) 867 throws IOException { 868 if (raf != null) { 869 raf.write(data, offset, length); 870 } else { 871 out.write(data, offset, length); 872 } 873 } 874 875 private void deflateUntilInputIsNeeded() throws IOException { 876 while (!def.needsInput()) { 877 deflate(); 878 } 879 } 880 881 private void writeVersionNeededToExtractAndGeneralPurposeBits(final int 882 zipMethod, 883 final boolean 884 utfFallback) 885 throws IOException { 886 887 // CheckStyle:MagicNumber OFF 888 int versionNeededToExtract = 10; 889 int generalPurposeFlag = (useEFS || utfFallback) ? EFS_FLAG : 0; 890 if (zipMethod == DEFLATED && raf == null) { 891 // requires version 2 as we are going to store length info 892 // in the data descriptor 893 versionNeededToExtract = 20; 894 // bit3 set to signal, we use a data descriptor 895 generalPurposeFlag |= 8; 896 } 897 // CheckStyle:MagicNumber ON 898 899 // version needed to extract 900 writeOut(ZipShort.getBytes(versionNeededToExtract)); 901 // general purpose bit flag 902 writeOut(ZipShort.getBytes(generalPurposeFlag)); 903 } 904 905 /** 906 * enum that represents the possible policies for creating Unicode 907 * extra fields. 908 */ 909 public static final class UnicodeExtraFieldPolicy { 910 /** 911 * Always create Unicode extra fields. 912 */ 913 public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always"); 914 /** 915 * Never create Unicode extra fields. 916 */ 917 public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never"); 918 /** 919 * Create Unicode extra fields for filenames that cannot be 920 * encoded using the specified encoding. 921 */ 922 public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = 923 new UnicodeExtraFieldPolicy("not encodeable"); 924 925 private final String name; 926 private UnicodeExtraFieldPolicy(String n) { 927 name = n; 928 } 929 public String toString() { 930 return name; 931 } 932 } 933 934 public ArchiveEntry createArchiveEntry(File inputFile, String entryName) 935 throws IOException { 936 if(finished) { 937 throw new IOException("Stream has already been finished"); 938 } 939 return new ZipArchiveEntry(inputFile, entryName); 940 } 941 }