1
17 package org.apache.commons.compress.archivers.zip;
18
19 import java.io.ByteArrayOutputStream;
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.nio.ByteBuffer;
25 import java.nio.channels.SeekableByteChannel;
26 import java.nio.charset.Charset;
27 import java.nio.charset.StandardCharsets;
28 import java.nio.file.LinkOption;
29 import java.nio.file.OpenOption;
30 import java.nio.file.Path;
31 import java.util.HashMap;
32 import java.util.LinkedList;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.zip.Deflater;
36 import java.util.zip.ZipException;
37
38 import org.apache.commons.compress.archivers.ArchiveEntry;
39 import org.apache.commons.compress.archivers.ArchiveOutputStream;
40 import org.apache.commons.compress.utils.ByteUtils;
41 import org.apache.commons.io.Charsets;
42
43
63 public class ZipArchiveOutputStream extends ArchiveOutputStream<ZipArchiveEntry> {
64
65
68 private static final class CurrentEntry {
69
70
73 private final ZipArchiveEntry entry;
74
75
78 private long localDataStart;
79
80
83 private long dataStart;
84
85
88 private long bytesRead;
89
90
93 private boolean causedUseOfZip64;
94
95
103 private boolean hasWritten;
104
105 private CurrentEntry(final ZipArchiveEntry entry) {
106 this.entry = entry;
107 }
108 }
109
110 private static final class EntryMetaData {
111 private final long offset;
112 private final boolean usesDataDescriptor;
113
114 private EntryMetaData(final long offset, final boolean usesDataDescriptor) {
115 this.offset = offset;
116 this.usesDataDescriptor = usesDataDescriptor;
117 }
118 }
119
120
123 public static final class UnicodeExtraFieldPolicy {
124
125
128 public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always");
129
130
133 public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never");
134
135
138 public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = new UnicodeExtraFieldPolicy("not encodeable");
139
140 private final String name;
141
142 private UnicodeExtraFieldPolicy(final String n) {
143 name = n;
144 }
145
146 @Override
147 public String toString() {
148 return name;
149 }
150 }
151
152 static final int BUFFER_SIZE = 512;
153 private static final int LFH_SIG_OFFSET = 0;
154 private static final int LFH_VERSION_NEEDED_OFFSET = 4;
155 private static final int LFH_GPB_OFFSET = 6;
156 private static final int LFH_METHOD_OFFSET = 8;
157 private static final int LFH_TIME_OFFSET = 10;
158 private static final int LFH_CRC_OFFSET = 14;
159 private static final int LFH_COMPRESSED_SIZE_OFFSET = 18;
160 private static final int LFH_ORIGINAL_SIZE_OFFSET = 22;
161 private static final int LFH_FILENAME_LENGTH_OFFSET = 26;
162 private static final int LFH_EXTRA_LENGTH_OFFSET = 28;
163 private static final int LFH_FILENAME_OFFSET = 30;
164 private static final int CFH_SIG_OFFSET = 0;
165 private static final int CFH_VERSION_MADE_BY_OFFSET = 4;
166 private static final int CFH_VERSION_NEEDED_OFFSET = 6;
167 private static final int CFH_GPB_OFFSET = 8;
168 private static final int CFH_METHOD_OFFSET = 10;
169 private static final int CFH_TIME_OFFSET = 12;
170 private static final int CFH_CRC_OFFSET = 16;
171 private static final int CFH_COMPRESSED_SIZE_OFFSET = 20;
172 private static final int CFH_ORIGINAL_SIZE_OFFSET = 24;
173 private static final int CFH_FILENAME_LENGTH_OFFSET = 28;
174 private static final int CFH_EXTRA_LENGTH_OFFSET = 30;
175 private static final int CFH_COMMENT_LENGTH_OFFSET = 32;
176 private static final int CFH_DISK_NUMBER_OFFSET = 34;
177 private static final int CFH_INTERNAL_ATTRIBUTES_OFFSET = 36;
178
179 private static final int CFH_EXTERNAL_ATTRIBUTES_OFFSET = 38;
180
181 private static final int CFH_LFH_OFFSET = 42;
182
183 private static final int CFH_FILENAME_OFFSET = 46;
184
185
188 public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
189
190
193 public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
194
195
198 public static final int STORED = java.util.zip.ZipEntry.STORED;
199
200
203 static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
204
205
210 @Deprecated
211 public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG;
212
213
216 private static final byte[] ZERO = { 0, 0 };
217
218
221 private static final byte[] LZERO = { 0, 0, 0, 0 };
222
223 private static final byte[] ONE = ZipLong.getBytes(1L);
224
225
228
231 static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes();
232
233
236 static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes();
237
238
241 static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes();
242
243
246 static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
247
248
251 static final byte[] ZIP64_EOCD_SIG = ZipLong.getBytes(0X06064B50L);
252
253
256 static final byte[] ZIP64_EOCD_LOC_SIG = ZipLong.getBytes(0X07064B50L);
257
258
259 protected boolean finished;
260
261
264 private CurrentEntry entry;
265
266
269 private String comment = "";
270
271
274 private int level = DEFAULT_COMPRESSION;
275
276
279 private boolean hasCompressionLevelChanged;
280
281
284 private int method = java.util.zip.ZipEntry.DEFLATED;
285
286
289 private final List<ZipArchiveEntry> entries = new LinkedList<>();
290
291 private final StreamCompressor streamCompressor;
292
293
296 private long cdOffset;
297
298
301 private long cdLength;
302
303
306 private long cdDiskNumberStart;
307
308
311 private long eocdLength;
312
313
316 private final Map<ZipArchiveEntry, EntryMetaData> metaData = new HashMap<>();
317
318
326 private Charset charset = DEFAULT_CHARSET;
327
328
333 private ZipEncoding zipEncoding = ZipEncodingHelper.getZipEncoding(DEFAULT_CHARSET);
334
335
338 protected final Deflater def;
339
340 private final OutputStream outputStream;
341
342
345 private boolean useUTF8Flag = true;
346
347
350 private boolean fallbackToUTF8;
351
352
355 private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER;
356
357
362 private boolean hasUsedZip64;
363
364 private Zip64Mode zip64Mode = Zip64Mode.AsNeeded;
365
366 private final byte[] copyBuffer = new byte[32768];
367
368
371 private final boolean isSplitZip;
372
373
376 private final Map<Integer, Integer> numberOfCDInDiskData = new HashMap<>();
377
378
384 public ZipArchiveOutputStream(final File file) throws IOException {
385 this(file.toPath());
386 }
387
388
408 public ZipArchiveOutputStream(final File file, final long zipSplitSize) throws IOException {
409 this(file.toPath(), zipSplitSize);
410 }
411
412
417 public ZipArchiveOutputStream(final OutputStream out) {
418 this.outputStream = out;
419 this.def = new Deflater(level, true);
420 this.streamCompressor = StreamCompressor.create(out, def);
421 this.isSplitZip = false;
422 }
423
424
441 public ZipArchiveOutputStream(final Path path, final long zipSplitSize) throws IOException {
442 this.def = new Deflater(level, true);
443 this.outputStream = new ZipSplitOutputStream(path, zipSplitSize);
444 this.streamCompressor = StreamCompressor.create(this.outputStream, def);
445 this.isSplitZip = true;
446 }
447
448
456 public ZipArchiveOutputStream(final Path file, final OpenOption... options) throws IOException {
457 this.def = new Deflater(level, true);
458 this.outputStream = options.length == 0 ? new FileRandomAccessOutputStream(file) : new FileRandomAccessOutputStream(file, options);
459 this.streamCompressor = StreamCompressor.create(outputStream, def);
460 this.isSplitZip = false;
461 }
462
463
473 public ZipArchiveOutputStream(final SeekableByteChannel channel) {
474 this.outputStream = new SeekableChannelRandomAccessOutputStream(channel);
475 this.def = new Deflater(level, true);
476 this.streamCompressor = StreamCompressor.create(outputStream, def);
477 this.isSplitZip = false;
478 }
479
480
494 public void addRawArchiveEntry(final ZipArchiveEntry entry, final InputStream rawStream) throws IOException {
495 final ZipArchiveEntry ae = new ZipArchiveEntry(entry);
496 if (hasZip64Extra(ae)) {
497
498
499
500 ae.removeExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
501 }
502 final boolean is2PhaseSource = ae.getCrc() != ZipArchiveEntry.CRC_UNKNOWN && ae.getSize() != ArchiveEntry.SIZE_UNKNOWN
503 && ae.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN;
504 putArchiveEntry(ae, is2PhaseSource);
505 copyFromZipInputStream(rawStream);
506 closeCopiedEntry(is2PhaseSource);
507 }
508
509
512 private void addUnicodeExtraFields(final ZipArchiveEntry ze, final boolean encodable, final ByteBuffer name) throws IOException {
513 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS || !encodable) {
514 ze.addExtraField(new UnicodePathExtraField(ze.getName(), name.array(), name.arrayOffset(), name.limit() - name.position()));
515 }
516
517 final String comm = ze.getComment();
518 if (comm != null && !comm.isEmpty()) {
519
520 final boolean commentEncodable = zipEncoding.canEncode(comm);
521
522 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS || !commentEncodable) {
523 final ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
524 ze.addExtraField(new UnicodeCommentExtraField(comm, commentB.array(), commentB.arrayOffset(), commentB.limit() - commentB.position()));
525 }
526 }
527 }
528
529
537 @Override
538 public boolean canWriteEntryData(final ArchiveEntry ae) {
539 if (ae instanceof ZipArchiveEntry) {
540 final ZipArchiveEntry zae = (ZipArchiveEntry) ae;
541 return zae.getMethod() != ZipMethod.IMPLODING.getCode() && zae.getMethod() != ZipMethod.UNSHRINKING.getCode() && ZipUtil.canHandleEntryData(zae);
542 }
543 return false;
544 }
545
546
549 private boolean checkIfNeedsZip64(final Zip64Mode effectiveMode) throws ZipException {
550 final boolean actuallyNeedsZip64 = isZip64Required(entry.entry, effectiveMode);
551 if (actuallyNeedsZip64 && effectiveMode == Zip64Mode.Never) {
552 throw new Zip64RequiredException(Zip64RequiredException.getEntryTooBigMessage(entry.entry));
553 }
554 return actuallyNeedsZip64;
555 }
556
557
564 @Override
565 public void close() throws IOException {
566 try {
567 if (!finished) {
568 finish();
569 }
570 } finally {
571 destroy();
572 }
573 }
574
575
581 @Override
582 public void closeArchiveEntry() throws IOException {
583 preClose();
584
585 flushDeflater();
586
587 final long bytesWritten = streamCompressor.getTotalBytesWritten() - entry.dataStart;
588 final long realCrc = streamCompressor.getCrc32();
589 entry.bytesRead = streamCompressor.getBytesRead();
590 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
591 final boolean actuallyNeedsZip64 = handleSizesAndCrc(bytesWritten, realCrc, effectiveMode);
592 closeEntry(actuallyNeedsZip64, false);
593 streamCompressor.reset();
594 }
595
596
603 private void closeCopiedEntry(final boolean phased) throws IOException {
604 preClose();
605 entry.bytesRead = entry.entry.getSize();
606 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
607 final boolean actuallyNeedsZip64 = checkIfNeedsZip64(effectiveMode);
608 closeEntry(actuallyNeedsZip64, phased);
609 }
610
611 private void closeEntry(final boolean actuallyNeedsZip64, final boolean phased) throws IOException {
612 if (!phased && outputStream instanceof RandomAccessOutputStream) {
613 rewriteSizesAndCrc(actuallyNeedsZip64);
614 }
615
616 if (!phased) {
617 writeDataDescriptor(entry.entry);
618 }
619 entry = null;
620 }
621
622 private void copyFromZipInputStream(final InputStream src) throws IOException {
623 if (entry == null) {
624 throw new IllegalStateException("No current entry");
625 }
626 ZipUtil.checkRequestedFeatures(entry.entry);
627 entry.hasWritten = true;
628 int length;
629 while ((length = src.read(copyBuffer)) >= 0) {
630 streamCompressor.writeCounted(copyBuffer, 0, length);
631 count(length);
632 }
633 }
634
635
645 @Override
646 public ZipArchiveEntry createArchiveEntry(final File inputFile, final String entryName) throws IOException {
647 if (finished) {
648 throw new IOException("Stream has already been finished");
649 }
650 return new ZipArchiveEntry(inputFile, entryName);
651 }
652
653
670 @Override
671 public ZipArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
672 if (finished) {
673 throw new IOException("Stream has already been finished");
674 }
675 return new ZipArchiveEntry(inputPath, entryName);
676 }
677
678 private byte[] createCentralFileHeader(final ZipArchiveEntry ze) throws IOException {
679
680 final EntryMetaData entryMetaData = metaData.get(ze);
681 final boolean needsZip64Extra = hasZip64Extra(ze) || ze.getCompressedSize() >= ZipConstants.ZIP64_MAGIC || ze.getSize() >= ZipConstants.ZIP64_MAGIC
682 || entryMetaData.offset >= ZipConstants.ZIP64_MAGIC || ze.getDiskNumberStart() >= ZipConstants.ZIP64_MAGIC_SHORT
683 || zip64Mode == Zip64Mode.Always || zip64Mode == Zip64Mode.AlwaysWithCompatibility;
684
685 if (needsZip64Extra && zip64Mode == Zip64Mode.Never) {
686
687
688
689 throw new Zip64RequiredException(Zip64RequiredException.ARCHIVE_TOO_BIG_MESSAGE);
690 }
691
692 handleZip64Extra(ze, entryMetaData.offset, needsZip64Extra);
693
694 return createCentralFileHeader(ze, getName(ze), entryMetaData, needsZip64Extra);
695 }
696
697
705 private byte[] createCentralFileHeader(final ZipArchiveEntry ze, final ByteBuffer name, final EntryMetaData entryMetaData, final boolean needsZip64Extra)
706 throws IOException {
707 if (isSplitZip) {
708
709
710 final int currentSplitSegment = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
711 if (numberOfCDInDiskData.get(currentSplitSegment) == null) {
712 numberOfCDInDiskData.put(currentSplitSegment, 1);
713 } else {
714 final int originalNumberOfCD = numberOfCDInDiskData.get(currentSplitSegment);
715 numberOfCDInDiskData.put(currentSplitSegment, originalNumberOfCD + 1);
716 }
717 }
718
719 final byte[] extra = ze.getCentralDirectoryExtra();
720 final int extraLength = extra.length;
721
722
723 String comm = ze.getComment();
724 if (comm == null) {
725 comm = "";
726 }
727
728 final ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
729 final int nameLen = name.limit() - name.position();
730 final int commentLen = commentB.limit() - commentB.position();
731 final int len = CFH_FILENAME_OFFSET + nameLen + extraLength + commentLen;
732 final byte[] buf = new byte[len];
733
734 System.arraycopy(CFH_SIG, 0, buf, CFH_SIG_OFFSET, ZipConstants.WORD);
735
736
737
738 ZipShort.putShort(ze.getPlatform() << 8 | (!hasUsedZip64 ? ZipConstants.DATA_DESCRIPTOR_MIN_VERSION : ZipConstants.ZIP64_MIN_VERSION), buf,
739 CFH_VERSION_MADE_BY_OFFSET);
740
741 final int zipMethod = ze.getMethod();
742 final boolean encodable = zipEncoding.canEncode(ze.getName());
743 ZipShort.putShort(versionNeededToExtract(zipMethod, needsZip64Extra, entryMetaData.usesDataDescriptor), buf, CFH_VERSION_NEEDED_OFFSET);
744 getGeneralPurposeBits(!encodable && fallbackToUTF8, entryMetaData.usesDataDescriptor).encode(buf, CFH_GPB_OFFSET);
745
746
747 ZipShort.putShort(zipMethod, buf, CFH_METHOD_OFFSET);
748
749
750 ZipUtil.toDosTime(ze.getTime(), buf, CFH_TIME_OFFSET);
751
752
753
754
755 ZipLong.putLong(ze.getCrc(), buf, CFH_CRC_OFFSET);
756 if (ze.getCompressedSize() >= ZipConstants.ZIP64_MAGIC || ze.getSize() >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always
757 || zip64Mode == Zip64Mode.AlwaysWithCompatibility) {
758 ZipLong.ZIP64_MAGIC.putLong(buf, CFH_COMPRESSED_SIZE_OFFSET);
759 ZipLong.ZIP64_MAGIC.putLong(buf, CFH_ORIGINAL_SIZE_OFFSET);
760 } else {
761 ZipLong.putLong(ze.getCompressedSize(), buf, CFH_COMPRESSED_SIZE_OFFSET);
762 ZipLong.putLong(ze.getSize(), buf, CFH_ORIGINAL_SIZE_OFFSET);
763 }
764
765 ZipShort.putShort(nameLen, buf, CFH_FILENAME_LENGTH_OFFSET);
766
767
768 ZipShort.putShort(extraLength, buf, CFH_EXTRA_LENGTH_OFFSET);
769
770 ZipShort.putShort(commentLen, buf, CFH_COMMENT_LENGTH_OFFSET);
771
772
773 if (isSplitZip) {
774 if (ze.getDiskNumberStart() >= ZipConstants.ZIP64_MAGIC_SHORT || zip64Mode == Zip64Mode.Always) {
775 ZipShort.putShort(ZipConstants.ZIP64_MAGIC_SHORT, buf, CFH_DISK_NUMBER_OFFSET);
776 } else {
777 ZipShort.putShort((int) ze.getDiskNumberStart(), buf, CFH_DISK_NUMBER_OFFSET);
778 }
779 } else {
780 System.arraycopy(ZERO, 0, buf, CFH_DISK_NUMBER_OFFSET, ZipConstants.SHORT);
781 }
782
783
784 ZipShort.putShort(ze.getInternalAttributes(), buf, CFH_INTERNAL_ATTRIBUTES_OFFSET);
785
786
787 ZipLong.putLong(ze.getExternalAttributes(), buf, CFH_EXTERNAL_ATTRIBUTES_OFFSET);
788
789
790 if (entryMetaData.offset >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always) {
791 ZipLong.putLong(ZipConstants.ZIP64_MAGIC, buf, CFH_LFH_OFFSET);
792 } else {
793 ZipLong.putLong(Math.min(entryMetaData.offset, ZipConstants.ZIP64_MAGIC), buf, CFH_LFH_OFFSET);
794 }
795
796
797 System.arraycopy(name.array(), name.arrayOffset(), buf, CFH_FILENAME_OFFSET, nameLen);
798
799 final int extraStart = CFH_FILENAME_OFFSET + nameLen;
800 System.arraycopy(extra, 0, buf, extraStart, extraLength);
801
802 final int commentStart = extraStart + extraLength;
803
804
805 System.arraycopy(commentB.array(), commentB.arrayOffset(), buf, commentStart, commentLen);
806 return buf;
807 }
808
809 private byte[] createLocalFileHeader(final ZipArchiveEntry ze, final ByteBuffer name, final boolean encodable, final boolean phased,
810 final long archiveOffset) {
811 final ZipExtraField oldEx = ze.getExtraField(ResourceAlignmentExtraField.ID);
812 if (oldEx != null) {
813 ze.removeExtraField(ResourceAlignmentExtraField.ID);
814 }
815 final ResourceAlignmentExtraField oldAlignmentEx = oldEx instanceof ResourceAlignmentExtraField ? (ResourceAlignmentExtraField) oldEx : null;
816
817 int alignment = ze.getAlignment();
818 if (alignment <= 0 && oldAlignmentEx != null) {
819 alignment = oldAlignmentEx.getAlignment();
820 }
821
822 if (alignment > 1 || oldAlignmentEx != null && !oldAlignmentEx.allowMethodChange()) {
823 final int oldLength = LFH_FILENAME_OFFSET + name.limit() - name.position() + ze.getLocalFileDataExtra().length;
824
825 final int padding = (int) (-archiveOffset - oldLength - ZipExtraField.EXTRAFIELD_HEADER_SIZE - ResourceAlignmentExtraField.BASE_SIZE
826 & alignment - 1);
827 ze.addExtraField(new ResourceAlignmentExtraField(alignment, oldAlignmentEx != null && oldAlignmentEx.allowMethodChange(), padding));
828 }
829
830 final byte[] extra = ze.getLocalFileDataExtra();
831 final int nameLen = name.limit() - name.position();
832 final int len = LFH_FILENAME_OFFSET + nameLen + extra.length;
833 final byte[] buf = new byte[len];
834
835 System.arraycopy(LFH_SIG, 0, buf, LFH_SIG_OFFSET, ZipConstants.WORD);
836
837
838 final int zipMethod = ze.getMethod();
839 final boolean dataDescriptor = usesDataDescriptor(zipMethod, phased);
840
841 ZipShort.putShort(versionNeededToExtract(zipMethod, hasZip64Extra(ze), dataDescriptor), buf, LFH_VERSION_NEEDED_OFFSET);
842
843 final GeneralPurposeBit generalPurposeBit = getGeneralPurposeBits(!encodable && fallbackToUTF8, dataDescriptor);
844 generalPurposeBit.encode(buf, LFH_GPB_OFFSET);
845
846
847 ZipShort.putShort(zipMethod, buf, LFH_METHOD_OFFSET);
848
849 ZipUtil.toDosTime(ze.getTime(), buf, LFH_TIME_OFFSET);
850
851
852 if (phased || !(zipMethod == DEFLATED || outputStream instanceof RandomAccessOutputStream)) {
853 ZipLong.putLong(ze.getCrc(), buf, LFH_CRC_OFFSET);
854 } else {
855 System.arraycopy(LZERO, 0, buf, LFH_CRC_OFFSET, ZipConstants.WORD);
856 }
857
858
859
860 if (hasZip64Extra(entry.entry)) {
861
862
863
864 ZipLong.ZIP64_MAGIC.putLong(buf, LFH_COMPRESSED_SIZE_OFFSET);
865 ZipLong.ZIP64_MAGIC.putLong(buf, LFH_ORIGINAL_SIZE_OFFSET);
866 } else if (phased) {
867 ZipLong.putLong(ze.getCompressedSize(), buf, LFH_COMPRESSED_SIZE_OFFSET);
868 ZipLong.putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET);
869 } else if (zipMethod == DEFLATED || outputStream instanceof RandomAccessOutputStream) {
870 System.arraycopy(LZERO, 0, buf, LFH_COMPRESSED_SIZE_OFFSET, ZipConstants.WORD);
871 System.arraycopy(LZERO, 0, buf, LFH_ORIGINAL_SIZE_OFFSET, ZipConstants.WORD);
872 } else {
873 ZipLong.putLong(ze.getSize(), buf, LFH_COMPRESSED_SIZE_OFFSET);
874 ZipLong.putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET);
875 }
876
877 ZipShort.putShort(nameLen, buf, LFH_FILENAME_LENGTH_OFFSET);
878
879
880 ZipShort.putShort(extra.length, buf, LFH_EXTRA_LENGTH_OFFSET);
881
882
883 System.arraycopy(name.array(), name.arrayOffset(), buf, LFH_FILENAME_OFFSET, nameLen);
884
885
886 System.arraycopy(extra, 0, buf, LFH_FILENAME_OFFSET + nameLen, extra.length);
887
888 return buf;
889 }
890
891
896 protected final void deflate() throws IOException {
897 streamCompressor.deflate();
898 }
899
900
906 void destroy() throws IOException {
907 if (outputStream != null) {
908 outputStream.close();
909 }
910 }
911
912
918 @Override
919 public void finish() throws IOException {
920 if (finished) {
921 throw new IOException("This archive has already been finished");
922 }
923
924 if (entry != null) {
925 throw new IOException("This archive contains unclosed entries.");
926 }
927
928 final long cdOverallOffset = streamCompressor.getTotalBytesWritten();
929 cdOffset = cdOverallOffset;
930 if (isSplitZip) {
931
932
933 final ZipSplitOutputStream zipSplitOutputStream = (ZipSplitOutputStream) this.outputStream;
934 cdOffset = zipSplitOutputStream.getCurrentSplitSegmentBytesWritten();
935 cdDiskNumberStart = zipSplitOutputStream.getCurrentSplitSegmentIndex();
936 }
937 writeCentralDirectoryInChunks();
938
939 cdLength = streamCompressor.getTotalBytesWritten() - cdOverallOffset;
940
941
942 final ByteBuffer commentData = this.zipEncoding.encode(comment);
943 final long commentLength = (long) commentData.limit() - commentData.position();
944 eocdLength = ZipConstants.WORD
945 + ZipConstants.SHORT
946 + ZipConstants.SHORT
947 + ZipConstants.SHORT
948 + ZipConstants.SHORT
949 + ZipConstants.WORD
950 + ZipConstants.WORD
951 + ZipConstants.SHORT
952 + commentLength ;
953
954 writeZip64CentralDirectory();
955 writeCentralDirectoryEnd();
956 metaData.clear();
957 entries.clear();
958 streamCompressor.close();
959 if (isSplitZip) {
960
961 outputStream.close();
962 }
963 finished = true;
964 }
965
966
971 @Override
972 public void flush() throws IOException {
973 if (outputStream != null) {
974 outputStream.flush();
975 }
976 }
977
978
981 private void flushDeflater() throws IOException {
982 if (entry.entry.getMethod() == DEFLATED) {
983 streamCompressor.flushDeflater();
984 }
985 }
986
987
993 @Override
994 public long getBytesWritten() {
995 return streamCompressor.getTotalBytesWritten();
996 }
997
998
1003 private Zip64Mode getEffectiveZip64Mode(final ZipArchiveEntry ze) {
1004 if (zip64Mode != Zip64Mode.AsNeeded || outputStream instanceof RandomAccessOutputStream ||
1005 ze.getMethod() != DEFLATED || ze.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
1006 return zip64Mode;
1007 }
1008 return Zip64Mode.Never;
1009 }
1010
1011
1016 public String getEncoding() {
1017 return charset != null ? charset.name() : null;
1018 }
1019
1020 private ZipEncoding getEntryEncoding(final ZipArchiveEntry ze) {
1021 final boolean encodable = zipEncoding.canEncode(ze.getName());
1022 return !encodable && fallbackToUTF8 ? ZipEncodingHelper.ZIP_ENCODING_UTF_8 : zipEncoding;
1023 }
1024
1025 private GeneralPurposeBit getGeneralPurposeBits(final boolean utfFallback, final boolean usesDataDescriptor) {
1026 final GeneralPurposeBit b = new GeneralPurposeBit();
1027 b.useUTF8ForNames(useUTF8Flag || utfFallback);
1028 if (usesDataDescriptor) {
1029 b.useDataDescriptor(true);
1030 }
1031 return b;
1032 }
1033
1034 private ByteBuffer getName(final ZipArchiveEntry ze) throws IOException {
1035 return getEntryEncoding(ze).encode(ze.getName());
1036 }
1037
1038
1043 private Zip64ExtendedInformationExtraField getZip64Extra(final ZipArchiveEntry ze) {
1044 if (entry != null) {
1045 entry.causedUseOfZip64 = !hasUsedZip64;
1046 }
1047 hasUsedZip64 = true;
1048 final ZipExtraField extra = ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
1049 Zip64ExtendedInformationExtraField z64 = extra instanceof Zip64ExtendedInformationExtraField ? (Zip64ExtendedInformationExtraField) extra : null;
1050 if (z64 == null) {
1051
1055 z64 = new Zip64ExtendedInformationExtraField();
1056 }
1057
1058
1059 ze.addAsFirstExtraField(z64);
1060
1061 return z64;
1062 }
1063
1064
1068 private boolean handleSizesAndCrc(final long bytesWritten, final long crc, final Zip64Mode effectiveMode) throws ZipException {
1069 if (entry.entry.getMethod() == DEFLATED) {
1070
1073 entry.entry.setSize(entry.bytesRead);
1074 entry.entry.setCompressedSize(bytesWritten);
1075 entry.entry.setCrc(crc);
1076
1077 } else if (!(outputStream instanceof RandomAccessOutputStream)) {
1078 if (entry.entry.getCrc() != crc) {
1079 throw new ZipException("Bad CRC checksum for entry " + entry.entry.getName() + ": " + Long.toHexString(entry.entry.getCrc()) + " instead of "
1080 + Long.toHexString(crc));
1081 }
1082
1083 if (entry.entry.getSize() != bytesWritten) {
1084 throw new ZipException("Bad size for entry " + entry.entry.getName() + ": " + entry.entry.getSize() + " instead of " + bytesWritten);
1085 }
1086 } else {
1087 entry.entry.setSize(bytesWritten);
1088 entry.entry.setCompressedSize(bytesWritten);
1089 entry.entry.setCrc(crc);
1090 }
1091
1092 return checkIfNeedsZip64(effectiveMode);
1093 }
1094
1095
1098 private void handleZip64Extra(final ZipArchiveEntry ze, final long lfhOffset, final boolean needsZip64Extra) {
1099 if (needsZip64Extra) {
1100 final Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze);
1101 if (ze.getCompressedSize() >= ZipConstants.ZIP64_MAGIC || ze.getSize() >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always
1102 || zip64Mode == Zip64Mode.AlwaysWithCompatibility) {
1103 z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
1104 z64.setSize(new ZipEightByteInteger(ze.getSize()));
1105 } else {
1106
1107 z64.setCompressedSize(null);
1108 z64.setSize(null);
1109 }
1110
1111 final boolean needsToEncodeLfhOffset = lfhOffset >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always;
1112 final boolean needsToEncodeDiskNumberStart = ze.getDiskNumberStart() >= ZipConstants.ZIP64_MAGIC_SHORT || zip64Mode == Zip64Mode.Always;
1113
1114 if (needsToEncodeLfhOffset || needsToEncodeDiskNumberStart) {
1115 z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset));
1116 }
1117 if (needsToEncodeDiskNumberStart) {
1118 z64.setDiskStartNumber(new ZipLong(ze.getDiskNumberStart()));
1119 }
1120 ze.setExtra();
1121 }
1122 }
1123
1124
1129 private boolean hasZip64Extra(final ZipArchiveEntry ze) {
1130 return ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID) instanceof Zip64ExtendedInformationExtraField;
1131 }
1132
1133
1142 public boolean isSeekable() {
1143 return outputStream instanceof RandomAccessOutputStream;
1144 }
1145
1146 private boolean isTooLargeForZip32(final ZipArchiveEntry zipArchiveEntry) {
1147 return zipArchiveEntry.getSize() >= ZipConstants.ZIP64_MAGIC || zipArchiveEntry.getCompressedSize() >= ZipConstants.ZIP64_MAGIC;
1148 }
1149
1150 private boolean isZip64Required(final ZipArchiveEntry entry1, final Zip64Mode requestedMode) {
1151 return requestedMode == Zip64Mode.Always || requestedMode == Zip64Mode.AlwaysWithCompatibility || isTooLargeForZip32(entry1);
1152 }
1153
1154 private void preClose() throws IOException {
1155 if (finished) {
1156 throw new IOException("Stream has already been finished");
1157 }
1158
1159 if (entry == null) {
1160 throw new IOException("No current entry to close");
1161 }
1162
1163 if (!entry.hasWritten) {
1164 write(ByteUtils.EMPTY_BYTE_ARRAY, 0, 0);
1165 }
1166 }
1167
1168
1175 @Override
1176 public void putArchiveEntry(final ZipArchiveEntry archiveEntry) throws IOException {
1177 putArchiveEntry(archiveEntry, false);
1178 }
1179
1180
1190 private void putArchiveEntry(final ZipArchiveEntry archiveEntry, final boolean phased) throws IOException {
1191 if (finished) {
1192 throw new IOException("Stream has already been finished");
1193 }
1194
1195 if (entry != null) {
1196 closeArchiveEntry();
1197 }
1198
1199 entry = new CurrentEntry(archiveEntry);
1200 entries.add(entry.entry);
1201
1202 setDefaults(entry.entry);
1203
1204 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
1205 validateSizeInformation(effectiveMode);
1206
1207 if (shouldAddZip64Extra(entry.entry, effectiveMode)) {
1208
1209 final Zip64ExtendedInformationExtraField z64 = getZip64Extra(entry.entry);
1210
1211 final ZipEightByteInteger size;
1212 final ZipEightByteInteger compressedSize;
1213 if (phased) {
1214
1215 size = new ZipEightByteInteger(entry.entry.getSize());
1216 compressedSize = new ZipEightByteInteger(entry.entry.getCompressedSize());
1217 } else if (entry.entry.getMethod() == STORED && entry.entry.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
1218
1219 compressedSize = size = new ZipEightByteInteger(entry.entry.getSize());
1220 } else {
1221
1222
1223 compressedSize = size = ZipEightByteInteger.ZERO;
1224 }
1225 z64.setSize(size);
1226 z64.setCompressedSize(compressedSize);
1227 entry.entry.setExtra();
1228 }
1229
1230 if (entry.entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
1231 def.setLevel(level);
1232 hasCompressionLevelChanged = false;
1233 }
1234 writeLocalFileHeader(archiveEntry, phased);
1235 }
1236
1237
1241 private void rewriteSizesAndCrc(final boolean actuallyNeedsZip64) throws IOException {
1242 final RandomAccessOutputStream randomStream = (RandomAccessOutputStream) outputStream;
1243 long dataStart = entry.localDataStart;
1244 if (randomStream instanceof ZipSplitOutputStream) {
1245 dataStart = ((ZipSplitOutputStream) randomStream).calculateDiskPosition(entry.entry.getDiskNumberStart(), dataStart);
1246 }
1247
1248 long position = dataStart;
1249 randomStream.writeFully(ZipLong.getBytes(entry.entry.getCrc()), position); position += ZipConstants.WORD;
1250 if (!hasZip64Extra(entry.entry) || !actuallyNeedsZip64) {
1251 randomStream.writeFully(ZipLong.getBytes(entry.entry.getCompressedSize()), position); position += ZipConstants.WORD;
1252 randomStream.writeFully(ZipLong.getBytes(entry.entry.getSize()), position); position += ZipConstants.WORD;
1253 } else {
1254 randomStream.writeFully(ZipLong.ZIP64_MAGIC.getBytes(), position); position += ZipConstants.WORD;
1255 randomStream.writeFully(ZipLong.ZIP64_MAGIC.getBytes(), position); position += ZipConstants.WORD;
1256 }
1257
1258 if (hasZip64Extra(entry.entry)) {
1259 final ByteBuffer name = getName(entry.entry);
1260 final int nameLen = name.limit() - name.position();
1261
1262 position = dataStart + 3 * ZipConstants.WORD + 2 * ZipConstants.SHORT + nameLen + 2 * ZipConstants.SHORT;
1263
1264
1265 randomStream.writeFully(ZipEightByteInteger.getBytes(entry.entry.getSize()), position); position += ZipConstants.DWORD;
1266 randomStream.writeFully(ZipEightByteInteger.getBytes(entry.entry.getCompressedSize()), position); position += ZipConstants.DWORD;
1267
1268 if (!actuallyNeedsZip64) {
1269
1270
1271 position = dataStart - 5 * ZipConstants.SHORT;
1272 randomStream.writeFully(ZipShort.getBytes(versionNeededToExtract(entry.entry.getMethod(), false, false)), position);
1273 position += ZipConstants.SHORT;
1274
1275
1276
1277 entry.entry.removeExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
1278 entry.entry.setExtra();
1279
1280
1281
1282 if (entry.causedUseOfZip64) {
1283 hasUsedZip64 = false;
1284 }
1285 }
1286 }
1287 }
1288
1289
1294 public void setComment(final String comment) {
1295 this.comment = comment;
1296 }
1297
1298
1306 public void setCreateUnicodeExtraFields(final UnicodeExtraFieldPolicy b) {
1307 createUnicodeExtraFields = b;
1308 }
1309
1310
1313 private void setDefaults(final ZipArchiveEntry entry) {
1314 if (entry.getMethod() == -1) {
1315 entry.setMethod(method);
1316 }
1317
1318 if (entry.getTime() == -1) {
1319 entry.setTime(System.currentTimeMillis());
1320 }
1321 }
1322
1323 private void setEncoding(final Charset encoding) {
1324 this.charset = encoding;
1325 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
1326 if (useUTF8Flag && !ZipEncodingHelper.isUTF8(encoding)) {
1327 useUTF8Flag = false;
1328 }
1329 }
1330
1331
1340 public void setEncoding(final String encoding) {
1341 setEncoding(Charsets.toCharset(encoding));
1342 }
1343
1344
1352 public void setFallbackToUTF8(final boolean b) {
1353 fallbackToUTF8 = b;
1354 }
1355
1356
1365 public void setLevel(final int level) {
1366 if (level < Deflater.DEFAULT_COMPRESSION || level > Deflater.BEST_COMPRESSION) {
1367 throw new IllegalArgumentException("Invalid compression level: " + level);
1368 }
1369 if (this.level == level) {
1370 return;
1371 }
1372 hasCompressionLevelChanged = true;
1373 this.level = level;
1374 }
1375
1376
1384 public void setMethod(final int method) {
1385 this.method = method;
1386 }
1387
1388
1396 public void setUseLanguageEncodingFlag(final boolean b) {
1397 useUTF8Flag = b && ZipEncodingHelper.isUTF8(charset);
1398 }
1399
1400
1431 public void setUseZip64(final Zip64Mode mode) {
1432 zip64Mode = mode;
1433 }
1434
1435
1446 private boolean shouldAddZip64Extra(final ZipArchiveEntry entry, final Zip64Mode mode) {
1447 return mode == Zip64Mode.Always || mode == Zip64Mode.AlwaysWithCompatibility || entry.getSize() >= ZipConstants.ZIP64_MAGIC
1448 || entry.getCompressedSize() >= ZipConstants.ZIP64_MAGIC
1449 || entry.getSize() == ArchiveEntry.SIZE_UNKNOWN && outputStream instanceof RandomAccessOutputStream && mode != Zip64Mode.Never;
1450 }
1451
1452
1458 private boolean shouldUseZip64EOCD() {
1459 int numberOfThisDisk = 0;
1460 if (isSplitZip) {
1461 numberOfThisDisk = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
1462 }
1463 final int numOfEntriesOnThisDisk = numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0);
1464 return numberOfThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT
1465 || cdDiskNumberStart >= ZipConstants.ZIP64_MAGIC_SHORT
1466 || numOfEntriesOnThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT
1467 || entries.size() >= ZipConstants.ZIP64_MAGIC_SHORT
1468 || cdLength >= ZipConstants.ZIP64_MAGIC
1469 || cdOffset >= ZipConstants.ZIP64_MAGIC;
1472 }
1473
1474 private boolean usesDataDescriptor(final int zipMethod, final boolean phased) {
1475 return !phased && zipMethod == DEFLATED && !(outputStream instanceof RandomAccessOutputStream);
1476 }
1477
1478
1483 private void validateIfZip64IsNeededInEOCD() throws Zip64RequiredException {
1484
1485 if (zip64Mode != Zip64Mode.Never) {
1486 return;
1487 }
1488
1489 int numberOfThisDisk = 0;
1490 if (isSplitZip) {
1491 numberOfThisDisk = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
1492 }
1493 if (numberOfThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT) {
1494 throw new Zip64RequiredException(Zip64RequiredException.DISK_NUMBER_TOO_BIG_MESSAGE);
1495 }
1496
1497 if (cdDiskNumberStart >= ZipConstants.ZIP64_MAGIC_SHORT) {
1498 throw new Zip64RequiredException(Zip64RequiredException.CENTRAL_DIRECTORY_DISK_NUMBER_TOO_BIG_MESSAGE);
1499 }
1500
1501 final int numOfEntriesOnThisDisk = numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0);
1502 if (numOfEntriesOnThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT) {
1503 throw new Zip64RequiredException(Zip64RequiredException.TOO_MANY_ENTRIES_ON_DISK_MESSAGE);
1504 }
1505
1506
1507 if (entries.size() >= ZipConstants.ZIP64_MAGIC_SHORT) {
1508 throw new Zip64RequiredException(Zip64RequiredException.TOO_MANY_ENTRIES_MESSAGE);
1509 }
1510
1511 if (cdLength >= ZipConstants.ZIP64_MAGIC) {
1512 throw new Zip64RequiredException(Zip64RequiredException.CENTRAL_DIRECTORY_SIZE_TOO_BIG_MESSAGE);
1513 }
1514
1515 if (cdOffset >= ZipConstants.ZIP64_MAGIC) {
1516 throw new Zip64RequiredException(Zip64RequiredException.ARCHIVE_TOO_BIG_MESSAGE);
1517 }
1518 }
1519
1520
1524 private void validateSizeInformation(final Zip64Mode effectiveMode) throws ZipException {
1525
1526 if (entry.entry.getMethod() == STORED && !(outputStream instanceof RandomAccessOutputStream)) {
1527 if (entry.entry.getSize() == ArchiveEntry.SIZE_UNKNOWN) {
1528 throw new ZipException("Uncompressed size is required for" + " STORED method when not writing to a" + " file");
1529 }
1530 if (entry.entry.getCrc() == ZipArchiveEntry.CRC_UNKNOWN) {
1531 throw new ZipException("CRC checksum is required for STORED" + " method when not writing to a file");
1532 }
1533 entry.entry.setCompressedSize(entry.entry.getSize());
1534 }
1535
1536 if ((entry.entry.getSize() >= ZipConstants.ZIP64_MAGIC || entry.entry.getCompressedSize() >= ZipConstants.ZIP64_MAGIC)
1537 && effectiveMode == Zip64Mode.Never) {
1538 throw new Zip64RequiredException(Zip64RequiredException.getEntryTooBigMessage(entry.entry));
1539 }
1540 }
1541
1542 private int versionNeededToExtract(final int zipMethod, final boolean zip64, final boolean usedDataDescriptor) {
1543 if (zip64) {
1544 return ZipConstants.ZIP64_MIN_VERSION;
1545 }
1546 if (usedDataDescriptor) {
1547 return ZipConstants.DATA_DESCRIPTOR_MIN_VERSION;
1548 }
1549 return versionNeededToExtractMethod(zipMethod);
1550 }
1551
1552 private int versionNeededToExtractMethod(final int zipMethod) {
1553 return zipMethod == DEFLATED ? ZipConstants.DEFLATE_MIN_VERSION : ZipConstants.INITIAL_VERSION;
1554 }
1555
1556
1564 @Override
1565 public void write(final byte[] b, final int offset, final int length) throws IOException {
1566 if (entry == null) {
1567 throw new IllegalStateException("No current entry");
1568 }
1569 ZipUtil.checkRequestedFeatures(entry.entry);
1570 final long writtenThisTime = streamCompressor.write(b, offset, length, entry.entry.getMethod());
1571 count(writtenThisTime);
1572 }
1573
1574
1581 protected void writeCentralDirectoryEnd() throws IOException {
1582 if (!hasUsedZip64 && isSplitZip) {
1583 ((ZipSplitOutputStream) this.outputStream).prepareToWriteUnsplittableContent(eocdLength);
1584 }
1585
1586 validateIfZip64IsNeededInEOCD();
1587
1588 writeCounted(EOCD_SIG);
1589
1590
1591 int numberOfThisDisk = 0;
1592 if (isSplitZip) {
1593 numberOfThisDisk = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
1594 }
1595 writeCounted(ZipShort.getBytes(numberOfThisDisk));
1596
1597
1598 writeCounted(ZipShort.getBytes((int) cdDiskNumberStart));
1599
1600
1601 final int numberOfEntries = entries.size();
1602
1603
1604 final int numOfEntriesOnThisDisk = isSplitZip ? numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0) : numberOfEntries;
1605 final byte[] numOfEntriesOnThisDiskData = ZipShort.getBytes(Math.min(numOfEntriesOnThisDisk, ZipConstants.ZIP64_MAGIC_SHORT));
1606 writeCounted(numOfEntriesOnThisDiskData);
1607
1608
1609 final byte[] num = ZipShort.getBytes(Math.min(numberOfEntries, ZipConstants.ZIP64_MAGIC_SHORT));
1610 writeCounted(num);
1611
1612
1613 writeCounted(ZipLong.getBytes(Math.min(cdLength, ZipConstants.ZIP64_MAGIC)));
1614 writeCounted(ZipLong.getBytes(Math.min(cdOffset, ZipConstants.ZIP64_MAGIC)));
1615
1616
1617 final ByteBuffer data = this.zipEncoding.encode(comment);
1618 final int dataLen = data.limit() - data.position();
1619 writeCounted(ZipShort.getBytes(dataLen));
1620 streamCompressor.writeCounted(data.array(), data.arrayOffset(), dataLen);
1621 }
1622
1623 private void writeCentralDirectoryInChunks() throws IOException {
1624 final int NUM_PER_WRITE = 1000;
1625 final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(70 * NUM_PER_WRITE);
1626 int count = 0;
1627 for (final ZipArchiveEntry ze : entries) {
1628 byteArrayOutputStream.write(createCentralFileHeader(ze));
1629 if (++count > NUM_PER_WRITE) {
1630 writeCounted(byteArrayOutputStream.toByteArray());
1631 byteArrayOutputStream.reset();
1632 count = 0;
1633 }
1634 }
1635 writeCounted(byteArrayOutputStream.toByteArray());
1636 }
1637
1638
1645 protected void writeCentralFileHeader(final ZipArchiveEntry ze) throws IOException {
1646 final byte[] centralFileHeader = createCentralFileHeader(ze);
1647 writeCounted(centralFileHeader);
1648 }
1649
1650
1656 private void writeCounted(final byte[] data) throws IOException {
1657 streamCompressor.writeCounted(data);
1658 }
1659
1660
1666 protected void writeDataDescriptor(final ZipArchiveEntry ze) throws IOException {
1667 if (!usesDataDescriptor(ze.getMethod(), false)) {
1668 return;
1669 }
1670 writeCounted(DD_SIG);
1671 writeCounted(ZipLong.getBytes(ze.getCrc()));
1672 if (!hasZip64Extra(ze)) {
1673 writeCounted(ZipLong.getBytes(ze.getCompressedSize()));
1674 writeCounted(ZipLong.getBytes(ze.getSize()));
1675 } else {
1676 writeCounted(ZipEightByteInteger.getBytes(ze.getCompressedSize()));
1677 writeCounted(ZipEightByteInteger.getBytes(ze.getSize()));
1678 }
1679 }
1680
1681
1687 protected void writeLocalFileHeader(final ZipArchiveEntry ze) throws IOException {
1688 writeLocalFileHeader(ze, false);
1689 }
1690
1691 private void writeLocalFileHeader(final ZipArchiveEntry ze, final boolean phased) throws IOException {
1692 final boolean encodable = zipEncoding.canEncode(ze.getName());
1693 final ByteBuffer name = getName(ze);
1694
1695 if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
1696 addUnicodeExtraFields(ze, encodable, name);
1697 }
1698
1699 long localHeaderStart = streamCompressor.getTotalBytesWritten();
1700 if (isSplitZip) {
1701
1702
1703 final ZipSplitOutputStream splitOutputStream = (ZipSplitOutputStream) this.outputStream;
1704 ze.setDiskNumberStart(splitOutputStream.getCurrentSplitSegmentIndex());
1705 localHeaderStart = splitOutputStream.getCurrentSplitSegmentBytesWritten();
1706 }
1707
1708 final byte[] localHeader = createLocalFileHeader(ze, name, encodable, phased, localHeaderStart);
1709 metaData.put(ze, new EntryMetaData(localHeaderStart, usesDataDescriptor(ze.getMethod(), phased)));
1710 entry.localDataStart = localHeaderStart + LFH_CRC_OFFSET;
1711 writeCounted(localHeader);
1712 entry.dataStart = streamCompressor.getTotalBytesWritten();
1713 }
1714
1715
1721 protected final void writeOut(final byte[] data) throws IOException {
1722 streamCompressor.writeOut(data, 0, data.length);
1723 }
1724
1725
1733 protected final void writeOut(final byte[] data, final int offset, final int length) throws IOException {
1734 streamCompressor.writeOut(data, offset, length);
1735 }
1736
1737
1744 public void writePreamble(final byte[] preamble) throws IOException {
1745 writePreamble(preamble, 0, preamble.length);
1746 }
1747
1748
1757 public void writePreamble(final byte[] preamble, final int offset, final int length) throws IOException {
1758 if (entry != null) {
1759 throw new IllegalStateException("Preamble must be written before creating an entry");
1760 }
1761 this.streamCompressor.writeCounted(preamble, offset, length);
1762 }
1763
1764
1770 protected void writeZip64CentralDirectory() throws IOException {
1771 if (zip64Mode == Zip64Mode.Never) {
1772 return;
1773 }
1774
1775 if (!hasUsedZip64 && shouldUseZip64EOCD()) {
1776
1777 hasUsedZip64 = true;
1778 }
1779
1780 if (!hasUsedZip64) {
1781 return;
1782 }
1783
1784 long offset = streamCompressor.getTotalBytesWritten();
1785 long diskNumberStart = 0L;
1786 if (isSplitZip) {
1787
1788
1789 final ZipSplitOutputStream zipSplitOutputStream = (ZipSplitOutputStream) this.outputStream;
1790 offset = zipSplitOutputStream.getCurrentSplitSegmentBytesWritten();
1791 diskNumberStart = zipSplitOutputStream.getCurrentSplitSegmentIndex();
1792 }
1793
1794 writeOut(ZIP64_EOCD_SIG);
1795
1796
1797 writeOut(ZipEightByteInteger.getBytes(ZipConstants.SHORT
1798 + ZipConstants.SHORT
1799 + ZipConstants.WORD
1800 + ZipConstants.WORD
1801 + ZipConstants.DWORD
1802 + ZipConstants.DWORD
1803 + ZipConstants.DWORD
1804 + (long) ZipConstants.DWORD
1805 ));
1806
1807
1808 writeOut(ZipShort.getBytes(ZipConstants.ZIP64_MIN_VERSION));
1809 writeOut(ZipShort.getBytes(ZipConstants.ZIP64_MIN_VERSION));
1810
1811
1812 int numberOfThisDisk = 0;
1813 if (isSplitZip) {
1814 numberOfThisDisk = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
1815 }
1816 writeOut(ZipLong.getBytes(numberOfThisDisk));
1817
1818
1819 writeOut(ZipLong.getBytes(cdDiskNumberStart));
1820
1821
1822 final int numOfEntriesOnThisDisk = isSplitZip ? numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0) : entries.size();
1823 final byte[] numOfEntriesOnThisDiskData = ZipEightByteInteger.getBytes(numOfEntriesOnThisDisk);
1824 writeOut(numOfEntriesOnThisDiskData);
1825
1826
1827 final byte[] num = ZipEightByteInteger.getBytes(entries.size());
1828 writeOut(num);
1829
1830
1831 writeOut(ZipEightByteInteger.getBytes(cdLength));
1832 writeOut(ZipEightByteInteger.getBytes(cdOffset));
1833
1834
1835
1836 if (isSplitZip) {
1837
1838
1839 final int zip64EOCDLOCLength = ZipConstants.WORD
1840 + ZipConstants.WORD
1841 + ZipConstants.DWORD
1842 + ZipConstants.WORD ;
1843
1844 final long unsplittableContentSize = zip64EOCDLOCLength + eocdLength;
1845 ((ZipSplitOutputStream) this.outputStream).prepareToWriteUnsplittableContent(unsplittableContentSize);
1846 }
1847
1848
1849 writeOut(ZIP64_EOCD_LOC_SIG);
1850
1851
1852 writeOut(ZipLong.getBytes(diskNumberStart));
1853
1854 writeOut(ZipEightByteInteger.getBytes(offset));
1855
1856 if (isSplitZip) {
1857
1858
1859 final int totalNumberOfDisks = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex() + 1;
1860 writeOut(ZipLong.getBytes(totalNumberOfDisks));
1861 } else {
1862 writeOut(ONE);
1863 }
1864 }
1865 }
1866