1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 public class ZipArchiveOutputStream extends ArchiveOutputStream<ZipArchiveEntry> {
63
64
65
66
67 private static final class CurrentEntry {
68
69
70
71
72 private final ZipArchiveEntry entry;
73
74
75
76
77 private long localDataStart;
78
79
80
81
82 private long dataStart;
83
84
85
86
87 private long bytesRead;
88
89
90
91
92 private boolean causedUseOfZip64;
93
94
95
96
97
98
99
100
101
102 private boolean hasWritten;
103
104 private CurrentEntry(final ZipArchiveEntry entry) {
105 this.entry = entry;
106 }
107 }
108
109 private static final class EntryMetaData {
110 private final long offset;
111 private final boolean usesDataDescriptor;
112
113 private EntryMetaData(final long offset, final boolean usesDataDescriptor) {
114 this.offset = offset;
115 this.usesDataDescriptor = usesDataDescriptor;
116 }
117 }
118
119
120
121
122 public static final class UnicodeExtraFieldPolicy {
123
124
125
126
127 public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always");
128
129
130
131
132 public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never");
133
134
135
136
137 public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = new UnicodeExtraFieldPolicy("not encodeable");
138
139 private final String name;
140
141 private UnicodeExtraFieldPolicy(final String n) {
142 name = n;
143 }
144
145 @Override
146 public String toString() {
147 return name;
148 }
149 }
150
151 static final int BUFFER_SIZE = 512;
152 private static final int LFH_SIG_OFFSET = 0;
153 private static final int LFH_VERSION_NEEDED_OFFSET = 4;
154 private static final int LFH_GPB_OFFSET = 6;
155 private static final int LFH_METHOD_OFFSET = 8;
156 private static final int LFH_TIME_OFFSET = 10;
157 private static final int LFH_CRC_OFFSET = 14;
158 private static final int LFH_COMPRESSED_SIZE_OFFSET = 18;
159 private static final int LFH_ORIGINAL_SIZE_OFFSET = 22;
160 private static final int LFH_FILENAME_LENGTH_OFFSET = 26;
161 private static final int LFH_EXTRA_LENGTH_OFFSET = 28;
162 private static final int LFH_FILENAME_OFFSET = 30;
163 private static final int CFH_SIG_OFFSET = 0;
164 private static final int CFH_VERSION_MADE_BY_OFFSET = 4;
165 private static final int CFH_VERSION_NEEDED_OFFSET = 6;
166 private static final int CFH_GPB_OFFSET = 8;
167 private static final int CFH_METHOD_OFFSET = 10;
168 private static final int CFH_TIME_OFFSET = 12;
169 private static final int CFH_CRC_OFFSET = 16;
170 private static final int CFH_COMPRESSED_SIZE_OFFSET = 20;
171 private static final int CFH_ORIGINAL_SIZE_OFFSET = 24;
172 private static final int CFH_FILENAME_LENGTH_OFFSET = 28;
173 private static final int CFH_EXTRA_LENGTH_OFFSET = 30;
174 private static final int CFH_COMMENT_LENGTH_OFFSET = 32;
175 private static final int CFH_DISK_NUMBER_OFFSET = 34;
176 private static final int CFH_INTERNAL_ATTRIBUTES_OFFSET = 36;
177
178 private static final int CFH_EXTERNAL_ATTRIBUTES_OFFSET = 38;
179
180 private static final int CFH_LFH_OFFSET = 42;
181
182 private static final int CFH_FILENAME_OFFSET = 46;
183
184
185
186
187 public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
188
189
190
191
192 public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
193
194
195
196
197 public static final int STORED = java.util.zip.ZipEntry.STORED;
198
199
200
201
202 static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
203
204
205
206
207
208
209 @Deprecated
210 public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG;
211
212
213
214
215 private static final byte[] ZERO = { 0, 0 };
216
217
218
219
220 private static final byte[] LZERO = { 0, 0, 0, 0 };
221
222 private static final byte[] ONE = ZipLong.getBytes(1L);
223
224
225
226
227
228
229
230 static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes();
231
232
233
234
235 static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes();
236
237
238
239
240 static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes();
241
242
243
244
245 static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
246
247
248
249
250 static final byte[] ZIP64_EOCD_SIG = ZipLong.getBytes(0X06064B50L);
251
252
253
254
255 static final byte[] ZIP64_EOCD_LOC_SIG = ZipLong.getBytes(0X07064B50L);
256
257
258 protected boolean finished;
259
260
261
262
263 private CurrentEntry entry;
264
265
266
267
268 private String comment = "";
269
270
271
272
273 private int level = DEFAULT_COMPRESSION;
274
275
276
277
278 private boolean hasCompressionLevelChanged;
279
280
281
282
283 private int method = java.util.zip.ZipEntry.DEFLATED;
284
285
286
287
288 private final List<ZipArchiveEntry> entries = new LinkedList<>();
289
290 private final StreamCompressor streamCompressor;
291
292
293
294
295 private long cdOffset;
296
297
298
299
300 private long cdLength;
301
302
303
304
305 private long cdDiskNumberStart;
306
307
308
309
310 private long eocdLength;
311
312
313
314
315 private final Map<ZipArchiveEntry, EntryMetaData> metaData = new HashMap<>();
316
317
318
319
320
321
322
323
324
325 private Charset charset = DEFAULT_CHARSET;
326
327
328
329
330
331
332 private ZipEncoding zipEncoding = ZipEncodingHelper.getZipEncoding(DEFAULT_CHARSET);
333
334
335
336
337 protected final Deflater def;
338
339 private final OutputStream outputStream;
340
341
342
343
344 private boolean useUTF8Flag = true;
345
346
347
348
349 private boolean fallbackToUTF8;
350
351
352
353
354 private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER;
355
356
357
358
359
360
361 private boolean hasUsedZip64;
362
363 private Zip64Mode zip64Mode = Zip64Mode.AsNeeded;
364
365 private final byte[] copyBuffer = new byte[32768];
366
367
368
369
370 private final boolean isSplitZip;
371
372
373
374
375 private final Map<Integer, Integer> numberOfCDInDiskData = new HashMap<>();
376
377
378
379
380
381
382
383 public ZipArchiveOutputStream(final File file) throws IOException {
384 this(file.toPath());
385 }
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407 public ZipArchiveOutputStream(final File file, final long zipSplitSize) throws IOException {
408 this(file.toPath(), zipSplitSize);
409 }
410
411
412
413
414
415
416 public ZipArchiveOutputStream(final OutputStream out) {
417 this.outputStream = out;
418 this.def = new Deflater(level, true);
419 this.streamCompressor = StreamCompressor.create(out, def);
420 this.isSplitZip = false;
421 }
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440 public ZipArchiveOutputStream(final Path path, final long zipSplitSize) throws IOException {
441 this.def = new Deflater(level, true);
442 this.outputStream = new ZipSplitOutputStream(path, zipSplitSize);
443 this.streamCompressor = StreamCompressor.create(this.outputStream, def);
444 this.isSplitZip = true;
445 }
446
447
448
449
450
451
452
453
454
455 public ZipArchiveOutputStream(final Path file, final OpenOption... options) throws IOException {
456 this.def = new Deflater(level, true);
457 this.outputStream = options.length == 0 ? new FileRandomAccessOutputStream(file) : new FileRandomAccessOutputStream(file, options);
458 this.streamCompressor = StreamCompressor.create(outputStream, def);
459 this.isSplitZip = false;
460 }
461
462
463
464
465
466
467
468
469
470
471
472 public ZipArchiveOutputStream(final SeekableByteChannel channel) {
473 this.outputStream = new SeekableChannelRandomAccessOutputStream(channel);
474 this.def = new Deflater(level, true);
475 this.streamCompressor = StreamCompressor.create(outputStream, def);
476 this.isSplitZip = false;
477 }
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493 public void addRawArchiveEntry(final ZipArchiveEntry entry, final InputStream rawStream) throws IOException {
494 final ZipArchiveEntry ae = new ZipArchiveEntry(entry);
495 if (hasZip64Extra(ae)) {
496
497
498
499 ae.removeExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
500 }
501 final boolean is2PhaseSource = ae.getCrc() != ZipArchiveEntry.CRC_UNKNOWN && ae.getSize() != ArchiveEntry.SIZE_UNKNOWN
502 && ae.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN;
503 putArchiveEntry(ae, is2PhaseSource);
504 copyFromZipInputStream(rawStream);
505 closeCopiedEntry(is2PhaseSource);
506 }
507
508
509
510
511 private void addUnicodeExtraFields(final ZipArchiveEntry ze, final boolean encodable, final ByteBuffer name) throws IOException {
512 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS || !encodable) {
513 ze.addExtraField(new UnicodePathExtraField(ze.getName(), name.array(), name.arrayOffset(), name.limit() - name.position()));
514 }
515
516 final String comm = ze.getComment();
517 if (comm != null && !comm.isEmpty()) {
518
519 final boolean commentEncodable = zipEncoding.canEncode(comm);
520
521 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS || !commentEncodable) {
522 final ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
523 ze.addExtraField(new UnicodeCommentExtraField(comm, commentB.array(), commentB.arrayOffset(), commentB.limit() - commentB.position()));
524 }
525 }
526 }
527
528
529
530
531
532
533
534
535
536 @Override
537 public boolean canWriteEntryData(final ArchiveEntry ae) {
538 if (ae instanceof ZipArchiveEntry) {
539 final ZipArchiveEntry zae = (ZipArchiveEntry) ae;
540 return zae.getMethod() != ZipMethod.IMPLODING.getCode() && zae.getMethod() != ZipMethod.UNSHRINKING.getCode() && ZipUtil.canHandleEntryData(zae);
541 }
542 return false;
543 }
544
545
546
547
548 private boolean checkIfNeedsZip64(final Zip64Mode effectiveMode) throws ZipException {
549 final boolean actuallyNeedsZip64 = isZip64Required(entry.entry, effectiveMode);
550 if (actuallyNeedsZip64 && effectiveMode == Zip64Mode.Never) {
551 throw new Zip64RequiredException(Zip64RequiredException.getEntryTooBigMessage(entry.entry));
552 }
553 return actuallyNeedsZip64;
554 }
555
556
557
558
559
560
561
562
563 @Override
564 public void close() throws IOException {
565 try {
566 if (!finished) {
567 finish();
568 }
569 } finally {
570 destroy();
571 }
572 }
573
574
575
576
577
578
579
580 @Override
581 public void closeArchiveEntry() throws IOException {
582 preClose();
583
584 flushDeflater();
585
586 final long bytesWritten = streamCompressor.getTotalBytesWritten() - entry.dataStart;
587 final long realCrc = streamCompressor.getCrc32();
588 entry.bytesRead = streamCompressor.getBytesRead();
589 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
590 final boolean actuallyNeedsZip64 = handleSizesAndCrc(bytesWritten, realCrc, effectiveMode);
591 closeEntry(actuallyNeedsZip64, false);
592 streamCompressor.reset();
593 }
594
595
596
597
598
599
600
601
602 private void closeCopiedEntry(final boolean phased) throws IOException {
603 preClose();
604 entry.bytesRead = entry.entry.getSize();
605 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
606 final boolean actuallyNeedsZip64 = checkIfNeedsZip64(effectiveMode);
607 closeEntry(actuallyNeedsZip64, phased);
608 }
609
610 private void closeEntry(final boolean actuallyNeedsZip64, final boolean phased) throws IOException {
611 if (!phased && outputStream instanceof RandomAccessOutputStream) {
612 rewriteSizesAndCrc(actuallyNeedsZip64);
613 }
614
615 if (!phased) {
616 writeDataDescriptor(entry.entry);
617 }
618 entry = null;
619 }
620
621 private void copyFromZipInputStream(final InputStream src) throws IOException {
622 if (entry == null) {
623 throw new IllegalStateException("No current entry");
624 }
625 ZipUtil.checkRequestedFeatures(entry.entry);
626 entry.hasWritten = true;
627 int length;
628 while ((length = src.read(copyBuffer)) >= 0) {
629 streamCompressor.writeCounted(copyBuffer, 0, length);
630 count(length);
631 }
632 }
633
634
635
636
637
638
639
640
641
642
643
644 @Override
645 public ZipArchiveEntry createArchiveEntry(final File inputFile, final String entryName) throws IOException {
646 if (finished) {
647 throw new IOException("Stream has already been finished");
648 }
649 return new ZipArchiveEntry(inputFile, entryName);
650 }
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669 @Override
670 public ZipArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
671 if (finished) {
672 throw new IOException("Stream has already been finished");
673 }
674 return new ZipArchiveEntry(inputPath, entryName);
675 }
676
677 private byte[] createCentralFileHeader(final ZipArchiveEntry ze) throws IOException {
678
679 final EntryMetaData entryMetaData = metaData.get(ze);
680 final boolean needsZip64Extra = hasZip64Extra(ze) || ze.getCompressedSize() >= ZipConstants.ZIP64_MAGIC || ze.getSize() >= ZipConstants.ZIP64_MAGIC
681 || entryMetaData.offset >= ZipConstants.ZIP64_MAGIC || ze.getDiskNumberStart() >= ZipConstants.ZIP64_MAGIC_SHORT
682 || zip64Mode == Zip64Mode.Always || zip64Mode == Zip64Mode.AlwaysWithCompatibility;
683
684 if (needsZip64Extra && zip64Mode == Zip64Mode.Never) {
685
686
687
688 throw new Zip64RequiredException(Zip64RequiredException.ARCHIVE_TOO_BIG_MESSAGE);
689 }
690
691 handleZip64Extra(ze, entryMetaData.offset, needsZip64Extra);
692
693 return createCentralFileHeader(ze, getName(ze), entryMetaData, needsZip64Extra);
694 }
695
696
697
698
699
700
701
702
703
704 private byte[] createCentralFileHeader(final ZipArchiveEntry ze, final ByteBuffer name, final EntryMetaData entryMetaData, final boolean needsZip64Extra)
705 throws IOException {
706 if (isSplitZip) {
707
708
709 final int currentSplitSegment = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
710 if (numberOfCDInDiskData.get(currentSplitSegment) == null) {
711 numberOfCDInDiskData.put(currentSplitSegment, 1);
712 } else {
713 final int originalNumberOfCD = numberOfCDInDiskData.get(currentSplitSegment);
714 numberOfCDInDiskData.put(currentSplitSegment, originalNumberOfCD + 1);
715 }
716 }
717
718 final byte[] extra = ze.getCentralDirectoryExtra();
719 final int extraLength = extra.length;
720
721
722 String comm = ze.getComment();
723 if (comm == null) {
724 comm = "";
725 }
726
727 final ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
728 final int nameLen = name.limit() - name.position();
729 final int commentLen = commentB.limit() - commentB.position();
730 final int len = CFH_FILENAME_OFFSET + nameLen + extraLength + commentLen;
731 final byte[] buf = new byte[len];
732
733 System.arraycopy(CFH_SIG, 0, buf, CFH_SIG_OFFSET, ZipConstants.WORD);
734
735
736
737 ZipShort.putShort(ze.getPlatform() << 8 | (!hasUsedZip64 ? ZipConstants.DATA_DESCRIPTOR_MIN_VERSION : ZipConstants.ZIP64_MIN_VERSION), buf,
738 CFH_VERSION_MADE_BY_OFFSET);
739
740 final int zipMethod = ze.getMethod();
741 final boolean encodable = zipEncoding.canEncode(ze.getName());
742 ZipShort.putShort(versionNeededToExtract(zipMethod, needsZip64Extra, entryMetaData.usesDataDescriptor), buf, CFH_VERSION_NEEDED_OFFSET);
743 getGeneralPurposeBits(!encodable && fallbackToUTF8, entryMetaData.usesDataDescriptor).encode(buf, CFH_GPB_OFFSET);
744
745
746 ZipShort.putShort(zipMethod, buf, CFH_METHOD_OFFSET);
747
748
749 ZipUtil.toDosTime(ze.getTime(), buf, CFH_TIME_OFFSET);
750
751
752
753
754 ZipLong.putLong(ze.getCrc(), buf, CFH_CRC_OFFSET);
755 if (ze.getCompressedSize() >= ZipConstants.ZIP64_MAGIC || ze.getSize() >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always
756 || zip64Mode == Zip64Mode.AlwaysWithCompatibility) {
757 ZipLong.ZIP64_MAGIC.putLong(buf, CFH_COMPRESSED_SIZE_OFFSET);
758 ZipLong.ZIP64_MAGIC.putLong(buf, CFH_ORIGINAL_SIZE_OFFSET);
759 } else {
760 ZipLong.putLong(ze.getCompressedSize(), buf, CFH_COMPRESSED_SIZE_OFFSET);
761 ZipLong.putLong(ze.getSize(), buf, CFH_ORIGINAL_SIZE_OFFSET);
762 }
763
764 ZipShort.putShort(nameLen, buf, CFH_FILENAME_LENGTH_OFFSET);
765
766
767 ZipShort.putShort(extraLength, buf, CFH_EXTRA_LENGTH_OFFSET);
768
769 ZipShort.putShort(commentLen, buf, CFH_COMMENT_LENGTH_OFFSET);
770
771
772 if (isSplitZip) {
773 if (ze.getDiskNumberStart() >= ZipConstants.ZIP64_MAGIC_SHORT || zip64Mode == Zip64Mode.Always) {
774 ZipShort.putShort(ZipConstants.ZIP64_MAGIC_SHORT, buf, CFH_DISK_NUMBER_OFFSET);
775 } else {
776 ZipShort.putShort((int) ze.getDiskNumberStart(), buf, CFH_DISK_NUMBER_OFFSET);
777 }
778 } else {
779 System.arraycopy(ZERO, 0, buf, CFH_DISK_NUMBER_OFFSET, ZipConstants.SHORT);
780 }
781
782
783 ZipShort.putShort(ze.getInternalAttributes(), buf, CFH_INTERNAL_ATTRIBUTES_OFFSET);
784
785
786 ZipLong.putLong(ze.getExternalAttributes(), buf, CFH_EXTERNAL_ATTRIBUTES_OFFSET);
787
788
789 if (entryMetaData.offset >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always) {
790 ZipLong.putLong(ZipConstants.ZIP64_MAGIC, buf, CFH_LFH_OFFSET);
791 } else {
792 ZipLong.putLong(Math.min(entryMetaData.offset, ZipConstants.ZIP64_MAGIC), buf, CFH_LFH_OFFSET);
793 }
794
795
796 System.arraycopy(name.array(), name.arrayOffset(), buf, CFH_FILENAME_OFFSET, nameLen);
797
798 final int extraStart = CFH_FILENAME_OFFSET + nameLen;
799 System.arraycopy(extra, 0, buf, extraStart, extraLength);
800
801 final int commentStart = extraStart + extraLength;
802
803
804 System.arraycopy(commentB.array(), commentB.arrayOffset(), buf, commentStart, commentLen);
805 return buf;
806 }
807
808 private byte[] createLocalFileHeader(final ZipArchiveEntry ze, final ByteBuffer name, final boolean encodable, final boolean phased,
809 final long archiveOffset) {
810 final ZipExtraField oldEx = ze.getExtraField(ResourceAlignmentExtraField.ID);
811 if (oldEx != null) {
812 ze.removeExtraField(ResourceAlignmentExtraField.ID);
813 }
814 final ResourceAlignmentExtraField oldAlignmentEx = oldEx instanceof ResourceAlignmentExtraField ? (ResourceAlignmentExtraField) oldEx : null;
815
816 int alignment = ze.getAlignment();
817 if (alignment <= 0 && oldAlignmentEx != null) {
818 alignment = oldAlignmentEx.getAlignment();
819 }
820
821 if (alignment > 1 || oldAlignmentEx != null && !oldAlignmentEx.allowMethodChange()) {
822 final int oldLength = LFH_FILENAME_OFFSET + name.limit() - name.position() + ze.getLocalFileDataExtra().length;
823
824 final int padding = (int) (-archiveOffset - oldLength - ZipExtraField.EXTRAFIELD_HEADER_SIZE - ResourceAlignmentExtraField.BASE_SIZE
825 & alignment - 1);
826 ze.addExtraField(new ResourceAlignmentExtraField(alignment, oldAlignmentEx != null && oldAlignmentEx.allowMethodChange(), padding));
827 }
828
829 final byte[] extra = ze.getLocalFileDataExtra();
830 final int nameLen = name.limit() - name.position();
831 final int len = LFH_FILENAME_OFFSET + nameLen + extra.length;
832 final byte[] buf = new byte[len];
833
834 System.arraycopy(LFH_SIG, 0, buf, LFH_SIG_OFFSET, ZipConstants.WORD);
835
836
837 final int zipMethod = ze.getMethod();
838 final boolean dataDescriptor = usesDataDescriptor(zipMethod, phased);
839
840 ZipShort.putShort(versionNeededToExtract(zipMethod, hasZip64Extra(ze), dataDescriptor), buf, LFH_VERSION_NEEDED_OFFSET);
841
842 final GeneralPurposeBit generalPurposeBit = getGeneralPurposeBits(!encodable && fallbackToUTF8, dataDescriptor);
843 generalPurposeBit.encode(buf, LFH_GPB_OFFSET);
844
845
846 ZipShort.putShort(zipMethod, buf, LFH_METHOD_OFFSET);
847
848 ZipUtil.toDosTime(ze.getTime(), buf, LFH_TIME_OFFSET);
849
850
851 if (phased || !(zipMethod == DEFLATED || outputStream instanceof RandomAccessOutputStream)) {
852 ZipLong.putLong(ze.getCrc(), buf, LFH_CRC_OFFSET);
853 } else {
854 System.arraycopy(LZERO, 0, buf, LFH_CRC_OFFSET, ZipConstants.WORD);
855 }
856
857
858
859 if (hasZip64Extra(entry.entry)) {
860
861
862
863 ZipLong.ZIP64_MAGIC.putLong(buf, LFH_COMPRESSED_SIZE_OFFSET);
864 ZipLong.ZIP64_MAGIC.putLong(buf, LFH_ORIGINAL_SIZE_OFFSET);
865 } else if (phased) {
866 ZipLong.putLong(ze.getCompressedSize(), buf, LFH_COMPRESSED_SIZE_OFFSET);
867 ZipLong.putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET);
868 } else if (zipMethod == DEFLATED || outputStream instanceof RandomAccessOutputStream) {
869 System.arraycopy(LZERO, 0, buf, LFH_COMPRESSED_SIZE_OFFSET, ZipConstants.WORD);
870 System.arraycopy(LZERO, 0, buf, LFH_ORIGINAL_SIZE_OFFSET, ZipConstants.WORD);
871 } else {
872 ZipLong.putLong(ze.getSize(), buf, LFH_COMPRESSED_SIZE_OFFSET);
873 ZipLong.putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET);
874 }
875
876 ZipShort.putShort(nameLen, buf, LFH_FILENAME_LENGTH_OFFSET);
877
878
879 ZipShort.putShort(extra.length, buf, LFH_EXTRA_LENGTH_OFFSET);
880
881
882 System.arraycopy(name.array(), name.arrayOffset(), buf, LFH_FILENAME_OFFSET, nameLen);
883
884
885 System.arraycopy(extra, 0, buf, LFH_FILENAME_OFFSET + nameLen, extra.length);
886
887 return buf;
888 }
889
890
891
892
893
894
895 protected final void deflate() throws IOException {
896 streamCompressor.deflate();
897 }
898
899
900
901
902
903
904
905 void destroy() throws IOException {
906 if (outputStream != null) {
907 outputStream.close();
908 }
909 }
910
911
912
913
914
915
916
917 @Override
918 public void finish() throws IOException {
919 if (finished) {
920 throw new IOException("This archive has already been finished");
921 }
922
923 if (entry != null) {
924 throw new IOException("This archive contains unclosed entries.");
925 }
926
927 final long cdOverallOffset = streamCompressor.getTotalBytesWritten();
928 cdOffset = cdOverallOffset;
929 if (isSplitZip) {
930
931
932 final ZipSplitOutputStream zipSplitOutputStream = (ZipSplitOutputStream) this.outputStream;
933 cdOffset = zipSplitOutputStream.getCurrentSplitSegmentBytesWritten();
934 cdDiskNumberStart = zipSplitOutputStream.getCurrentSplitSegmentIndex();
935 }
936 writeCentralDirectoryInChunks();
937
938 cdLength = streamCompressor.getTotalBytesWritten() - cdOverallOffset;
939
940
941 final ByteBuffer commentData = this.zipEncoding.encode(comment);
942 final long commentLength = (long) commentData.limit() - commentData.position();
943 eocdLength = ZipConstants.WORD
944 + ZipConstants.SHORT
945 + ZipConstants.SHORT
946 + ZipConstants.SHORT
947 + ZipConstants.SHORT
948 + ZipConstants.WORD
949 + ZipConstants.WORD
950 + ZipConstants.SHORT
951 + commentLength ;
952
953 writeZip64CentralDirectory();
954 writeCentralDirectoryEnd();
955 metaData.clear();
956 entries.clear();
957 streamCompressor.close();
958 if (isSplitZip) {
959
960 outputStream.close();
961 }
962 finished = true;
963 }
964
965
966
967
968
969
970 @Override
971 public void flush() throws IOException {
972 if (outputStream != null) {
973 outputStream.flush();
974 }
975 }
976
977
978
979
980 private void flushDeflater() throws IOException {
981 if (entry.entry.getMethod() == DEFLATED) {
982 streamCompressor.flushDeflater();
983 }
984 }
985
986
987
988
989
990
991
992 @Override
993 public long getBytesWritten() {
994 return streamCompressor.getTotalBytesWritten();
995 }
996
997
998
999
1000
1001
1002 private Zip64Mode getEffectiveZip64Mode(final ZipArchiveEntry ze) {
1003 if (zip64Mode != Zip64Mode.AsNeeded || outputStream instanceof RandomAccessOutputStream ||
1004 ze.getMethod() != DEFLATED || ze.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
1005 return zip64Mode;
1006 }
1007 return Zip64Mode.Never;
1008 }
1009
1010
1011
1012
1013
1014
1015 public String getEncoding() {
1016 return charset != null ? charset.name() : null;
1017 }
1018
1019 private ZipEncoding getEntryEncoding(final ZipArchiveEntry ze) {
1020 final boolean encodable = zipEncoding.canEncode(ze.getName());
1021 return !encodable && fallbackToUTF8 ? ZipEncodingHelper.ZIP_ENCODING_UTF_8 : zipEncoding;
1022 }
1023
1024 private GeneralPurposeBit getGeneralPurposeBits(final boolean utfFallback, final boolean usesDataDescriptor) {
1025 final GeneralPurposeBit b = new GeneralPurposeBit();
1026 b.useUTF8ForNames(useUTF8Flag || utfFallback);
1027 if (usesDataDescriptor) {
1028 b.useDataDescriptor(true);
1029 }
1030 return b;
1031 }
1032
1033 private ByteBuffer getName(final ZipArchiveEntry ze) throws IOException {
1034 return getEntryEncoding(ze).encode(ze.getName());
1035 }
1036
1037
1038
1039
1040
1041
1042 private Zip64ExtendedInformationExtraField getZip64Extra(final ZipArchiveEntry ze) {
1043 if (entry != null) {
1044 entry.causedUseOfZip64 = !hasUsedZip64;
1045 }
1046 hasUsedZip64 = true;
1047 final ZipExtraField extra = ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
1048 Zip64ExtendedInformationExtraField z64 = extra instanceof Zip64ExtendedInformationExtraField ? (Zip64ExtendedInformationExtraField) extra : null;
1049 if (z64 == null) {
1050
1051
1052
1053
1054 z64 = new Zip64ExtendedInformationExtraField();
1055 }
1056
1057
1058 ze.addAsFirstExtraField(z64);
1059
1060 return z64;
1061 }
1062
1063
1064
1065
1066
1067 private boolean handleSizesAndCrc(final long bytesWritten, final long crc, final Zip64Mode effectiveMode) throws ZipException {
1068 if (entry.entry.getMethod() == DEFLATED) {
1069
1070
1071
1072 entry.entry.setSize(entry.bytesRead);
1073 entry.entry.setCompressedSize(bytesWritten);
1074 entry.entry.setCrc(crc);
1075
1076 } else if (!(outputStream instanceof RandomAccessOutputStream)) {
1077 if (entry.entry.getCrc() != crc) {
1078 throw new ZipException("Bad CRC checksum for entry " + entry.entry.getName() + ": " + Long.toHexString(entry.entry.getCrc()) + " instead of "
1079 + Long.toHexString(crc));
1080 }
1081
1082 if (entry.entry.getSize() != bytesWritten) {
1083 throw new ZipException("Bad size for entry " + entry.entry.getName() + ": " + entry.entry.getSize() + " instead of " + bytesWritten);
1084 }
1085 } else {
1086 entry.entry.setSize(bytesWritten);
1087 entry.entry.setCompressedSize(bytesWritten);
1088 entry.entry.setCrc(crc);
1089 }
1090
1091 return checkIfNeedsZip64(effectiveMode);
1092 }
1093
1094
1095
1096
1097 private void handleZip64Extra(final ZipArchiveEntry ze, final long lfhOffset, final boolean needsZip64Extra) {
1098 if (needsZip64Extra) {
1099 final Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze);
1100 if (ze.getCompressedSize() >= ZipConstants.ZIP64_MAGIC || ze.getSize() >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always
1101 || zip64Mode == Zip64Mode.AlwaysWithCompatibility) {
1102 z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
1103 z64.setSize(new ZipEightByteInteger(ze.getSize()));
1104 } else {
1105
1106 z64.setCompressedSize(null);
1107 z64.setSize(null);
1108 }
1109
1110 final boolean needsToEncodeLfhOffset = lfhOffset >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always;
1111 final boolean needsToEncodeDiskNumberStart = ze.getDiskNumberStart() >= ZipConstants.ZIP64_MAGIC_SHORT || zip64Mode == Zip64Mode.Always;
1112
1113 if (needsToEncodeLfhOffset || needsToEncodeDiskNumberStart) {
1114 z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset));
1115 }
1116 if (needsToEncodeDiskNumberStart) {
1117 z64.setDiskStartNumber(new ZipLong(ze.getDiskNumberStart()));
1118 }
1119 ze.setExtra();
1120 }
1121 }
1122
1123
1124
1125
1126
1127
1128 private boolean hasZip64Extra(final ZipArchiveEntry ze) {
1129 return ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID) instanceof Zip64ExtendedInformationExtraField;
1130 }
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141 public boolean isSeekable() {
1142 return outputStream instanceof RandomAccessOutputStream;
1143 }
1144
1145 private boolean isTooLargeForZip32(final ZipArchiveEntry zipArchiveEntry) {
1146 return zipArchiveEntry.getSize() >= ZipConstants.ZIP64_MAGIC || zipArchiveEntry.getCompressedSize() >= ZipConstants.ZIP64_MAGIC;
1147 }
1148
1149 private boolean isZip64Required(final ZipArchiveEntry entry1, final Zip64Mode requestedMode) {
1150 return requestedMode == Zip64Mode.Always || requestedMode == Zip64Mode.AlwaysWithCompatibility || isTooLargeForZip32(entry1);
1151 }
1152
1153 private void preClose() throws IOException {
1154 if (finished) {
1155 throw new IOException("Stream has already been finished");
1156 }
1157
1158 if (entry == null) {
1159 throw new IOException("No current entry to close");
1160 }
1161
1162 if (!entry.hasWritten) {
1163 write(ByteUtils.EMPTY_BYTE_ARRAY, 0, 0);
1164 }
1165 }
1166
1167
1168
1169
1170
1171
1172
1173
1174 @Override
1175 public void putArchiveEntry(final ZipArchiveEntry archiveEntry) throws IOException {
1176 putArchiveEntry(archiveEntry, false);
1177 }
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189 private void putArchiveEntry(final ZipArchiveEntry archiveEntry, final boolean phased) throws IOException {
1190 if (finished) {
1191 throw new IOException("Stream has already been finished");
1192 }
1193
1194 if (entry != null) {
1195 closeArchiveEntry();
1196 }
1197
1198 entry = new CurrentEntry(archiveEntry);
1199 entries.add(entry.entry);
1200
1201 setDefaults(entry.entry);
1202
1203 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
1204 validateSizeInformation(effectiveMode);
1205
1206 if (shouldAddZip64Extra(entry.entry, effectiveMode)) {
1207
1208 final Zip64ExtendedInformationExtraField z64 = getZip64Extra(entry.entry);
1209
1210 final ZipEightByteInteger size;
1211 final ZipEightByteInteger compressedSize;
1212 if (phased) {
1213
1214 size = new ZipEightByteInteger(entry.entry.getSize());
1215 compressedSize = new ZipEightByteInteger(entry.entry.getCompressedSize());
1216 } else if (entry.entry.getMethod() == STORED && entry.entry.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
1217
1218 compressedSize = size = new ZipEightByteInteger(entry.entry.getSize());
1219 } else {
1220
1221
1222 compressedSize = size = ZipEightByteInteger.ZERO;
1223 }
1224 z64.setSize(size);
1225 z64.setCompressedSize(compressedSize);
1226 entry.entry.setExtra();
1227 }
1228
1229 if (entry.entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
1230 def.setLevel(level);
1231 hasCompressionLevelChanged = false;
1232 }
1233 writeLocalFileHeader(archiveEntry, phased);
1234 }
1235
1236
1237
1238
1239
1240 private void rewriteSizesAndCrc(final boolean actuallyNeedsZip64) throws IOException {
1241 final RandomAccessOutputStream randomStream = (RandomAccessOutputStream) outputStream;
1242 long dataStart = entry.localDataStart;
1243 if (randomStream instanceof ZipSplitOutputStream) {
1244 dataStart = ((ZipSplitOutputStream) randomStream).calculateDiskPosition(entry.entry.getDiskNumberStart(), dataStart);
1245 }
1246
1247 long position = dataStart;
1248 randomStream.writeFully(ZipLong.getBytes(entry.entry.getCrc()), position); position += ZipConstants.WORD;
1249 if (!hasZip64Extra(entry.entry) || !actuallyNeedsZip64) {
1250 randomStream.writeFully(ZipLong.getBytes(entry.entry.getCompressedSize()), position); position += ZipConstants.WORD;
1251 randomStream.writeFully(ZipLong.getBytes(entry.entry.getSize()), position); position += ZipConstants.WORD;
1252 } else {
1253 randomStream.writeFully(ZipLong.ZIP64_MAGIC.getBytes(), position); position += ZipConstants.WORD;
1254 randomStream.writeFully(ZipLong.ZIP64_MAGIC.getBytes(), position); position += ZipConstants.WORD;
1255 }
1256
1257 if (hasZip64Extra(entry.entry)) {
1258 final ByteBuffer name = getName(entry.entry);
1259 final int nameLen = name.limit() - name.position();
1260
1261 position = dataStart + 3 * ZipConstants.WORD + 2 * ZipConstants.SHORT + nameLen + 2 * ZipConstants.SHORT;
1262
1263
1264 randomStream.writeFully(ZipEightByteInteger.getBytes(entry.entry.getSize()), position); position += ZipConstants.DWORD;
1265 randomStream.writeFully(ZipEightByteInteger.getBytes(entry.entry.getCompressedSize()), position); position += ZipConstants.DWORD;
1266
1267 if (!actuallyNeedsZip64) {
1268
1269
1270 position = dataStart - 5 * ZipConstants.SHORT;
1271 randomStream.writeFully(ZipShort.getBytes(versionNeededToExtract(entry.entry.getMethod(), false, false)), position);
1272 position += ZipConstants.SHORT;
1273
1274
1275
1276 entry.entry.removeExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
1277 entry.entry.setExtra();
1278
1279
1280
1281 if (entry.causedUseOfZip64) {
1282 hasUsedZip64 = false;
1283 }
1284 }
1285 }
1286 }
1287
1288
1289
1290
1291
1292
1293 public void setComment(final String comment) {
1294 this.comment = comment;
1295 }
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305 public void setCreateUnicodeExtraFields(final UnicodeExtraFieldPolicy b) {
1306 createUnicodeExtraFields = b;
1307 }
1308
1309
1310
1311
1312 private void setDefaults(final ZipArchiveEntry entry) {
1313 if (entry.getMethod() == -1) {
1314 entry.setMethod(method);
1315 }
1316
1317 if (entry.getTime() == -1) {
1318 entry.setTime(System.currentTimeMillis());
1319 }
1320 }
1321
1322 private void setEncoding(final Charset encoding) {
1323 this.charset = encoding;
1324 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
1325 if (useUTF8Flag && !ZipEncodingHelper.isUTF8(encoding)) {
1326 useUTF8Flag = false;
1327 }
1328 }
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339 public void setEncoding(final String encoding) {
1340 setEncoding(Charset.forName(encoding));
1341 }
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351 public void setFallbackToUTF8(final boolean b) {
1352 fallbackToUTF8 = b;
1353 }
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364 public void setLevel(final int level) {
1365 if (level < Deflater.DEFAULT_COMPRESSION || level > Deflater.BEST_COMPRESSION) {
1366 throw new IllegalArgumentException("Invalid compression level: " + level);
1367 }
1368 if (this.level == level) {
1369 return;
1370 }
1371 hasCompressionLevelChanged = true;
1372 this.level = level;
1373 }
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383 public void setMethod(final int method) {
1384 this.method = method;
1385 }
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395 public void setUseLanguageEncodingFlag(final boolean b) {
1396 useUTF8Flag = b && ZipEncodingHelper.isUTF8(charset);
1397 }
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430 public void setUseZip64(final Zip64Mode mode) {
1431 zip64Mode = mode;
1432 }
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445 private boolean shouldAddZip64Extra(final ZipArchiveEntry entry, final Zip64Mode mode) {
1446 return mode == Zip64Mode.Always || mode == Zip64Mode.AlwaysWithCompatibility || entry.getSize() >= ZipConstants.ZIP64_MAGIC
1447 || entry.getCompressedSize() >= ZipConstants.ZIP64_MAGIC
1448 || entry.getSize() == ArchiveEntry.SIZE_UNKNOWN && outputStream instanceof RandomAccessOutputStream && mode != Zip64Mode.Never;
1449 }
1450
1451
1452
1453
1454
1455
1456
1457 private boolean shouldUseZip64EOCD() {
1458 int numberOfThisDisk = 0;
1459 if (isSplitZip) {
1460 numberOfThisDisk = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
1461 }
1462 final int numOfEntriesOnThisDisk = numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0);
1463 return numberOfThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT
1464 || cdDiskNumberStart >= ZipConstants.ZIP64_MAGIC_SHORT
1465 || numOfEntriesOnThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT
1466 || entries.size() >= ZipConstants.ZIP64_MAGIC_SHORT
1467 || cdLength >= ZipConstants.ZIP64_MAGIC
1468 || cdOffset >= ZipConstants.ZIP64_MAGIC;
1469
1470
1471 }
1472
1473 private boolean usesDataDescriptor(final int zipMethod, final boolean phased) {
1474 return !phased && zipMethod == DEFLATED && !(outputStream instanceof RandomAccessOutputStream);
1475 }
1476
1477
1478
1479
1480
1481
1482 private void validateIfZip64IsNeededInEOCD() throws Zip64RequiredException {
1483
1484 if (zip64Mode != Zip64Mode.Never) {
1485 return;
1486 }
1487
1488 int numberOfThisDisk = 0;
1489 if (isSplitZip) {
1490 numberOfThisDisk = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
1491 }
1492 if (numberOfThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT) {
1493 throw new Zip64RequiredException(Zip64RequiredException.DISK_NUMBER_TOO_BIG_MESSAGE);
1494 }
1495
1496 if (cdDiskNumberStart >= ZipConstants.ZIP64_MAGIC_SHORT) {
1497 throw new Zip64RequiredException(Zip64RequiredException.CENTRAL_DIRECTORY_DISK_NUMBER_TOO_BIG_MESSAGE);
1498 }
1499
1500 final int numOfEntriesOnThisDisk = numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0);
1501 if (numOfEntriesOnThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT) {
1502 throw new Zip64RequiredException(Zip64RequiredException.TOO_MANY_ENTRIES_ON_DISK_MESSAGE);
1503 }
1504
1505
1506 if (entries.size() >= ZipConstants.ZIP64_MAGIC_SHORT) {
1507 throw new Zip64RequiredException(Zip64RequiredException.TOO_MANY_ENTRIES_MESSAGE);
1508 }
1509
1510 if (cdLength >= ZipConstants.ZIP64_MAGIC) {
1511 throw new Zip64RequiredException(Zip64RequiredException.CENTRAL_DIRECTORY_SIZE_TOO_BIG_MESSAGE);
1512 }
1513
1514 if (cdOffset >= ZipConstants.ZIP64_MAGIC) {
1515 throw new Zip64RequiredException(Zip64RequiredException.ARCHIVE_TOO_BIG_MESSAGE);
1516 }
1517 }
1518
1519
1520
1521
1522
1523 private void validateSizeInformation(final Zip64Mode effectiveMode) throws ZipException {
1524
1525 if (entry.entry.getMethod() == STORED && !(outputStream instanceof RandomAccessOutputStream)) {
1526 if (entry.entry.getSize() == ArchiveEntry.SIZE_UNKNOWN) {
1527 throw new ZipException("Uncompressed size is required for" + " STORED method when not writing to a" + " file");
1528 }
1529 if (entry.entry.getCrc() == ZipArchiveEntry.CRC_UNKNOWN) {
1530 throw new ZipException("CRC checksum is required for STORED" + " method when not writing to a file");
1531 }
1532 entry.entry.setCompressedSize(entry.entry.getSize());
1533 }
1534
1535 if ((entry.entry.getSize() >= ZipConstants.ZIP64_MAGIC || entry.entry.getCompressedSize() >= ZipConstants.ZIP64_MAGIC)
1536 && effectiveMode == Zip64Mode.Never) {
1537 throw new Zip64RequiredException(Zip64RequiredException.getEntryTooBigMessage(entry.entry));
1538 }
1539 }
1540
1541 private int versionNeededToExtract(final int zipMethod, final boolean zip64, final boolean usedDataDescriptor) {
1542 if (zip64) {
1543 return ZipConstants.ZIP64_MIN_VERSION;
1544 }
1545 if (usedDataDescriptor) {
1546 return ZipConstants.DATA_DESCRIPTOR_MIN_VERSION;
1547 }
1548 return versionNeededToExtractMethod(zipMethod);
1549 }
1550
1551 private int versionNeededToExtractMethod(final int zipMethod) {
1552 return zipMethod == DEFLATED ? ZipConstants.DEFLATE_MIN_VERSION : ZipConstants.INITIAL_VERSION;
1553 }
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563 @Override
1564 public void write(final byte[] b, final int offset, final int length) throws IOException {
1565 if (entry == null) {
1566 throw new IllegalStateException("No current entry");
1567 }
1568 ZipUtil.checkRequestedFeatures(entry.entry);
1569 final long writtenThisTime = streamCompressor.write(b, offset, length, entry.entry.getMethod());
1570 count(writtenThisTime);
1571 }
1572
1573
1574
1575
1576
1577
1578
1579
1580 protected void writeCentralDirectoryEnd() throws IOException {
1581 if (!hasUsedZip64 && isSplitZip) {
1582 ((ZipSplitOutputStream) this.outputStream).prepareToWriteUnsplittableContent(eocdLength);
1583 }
1584
1585 validateIfZip64IsNeededInEOCD();
1586
1587 writeCounted(EOCD_SIG);
1588
1589
1590 int numberOfThisDisk = 0;
1591 if (isSplitZip) {
1592 numberOfThisDisk = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
1593 }
1594 writeCounted(ZipShort.getBytes(numberOfThisDisk));
1595
1596
1597 writeCounted(ZipShort.getBytes((int) cdDiskNumberStart));
1598
1599
1600 final int numberOfEntries = entries.size();
1601
1602
1603 final int numOfEntriesOnThisDisk = isSplitZip ? numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0) : numberOfEntries;
1604 final byte[] numOfEntriesOnThisDiskData = ZipShort.getBytes(Math.min(numOfEntriesOnThisDisk, ZipConstants.ZIP64_MAGIC_SHORT));
1605 writeCounted(numOfEntriesOnThisDiskData);
1606
1607
1608 final byte[] num = ZipShort.getBytes(Math.min(numberOfEntries, ZipConstants.ZIP64_MAGIC_SHORT));
1609 writeCounted(num);
1610
1611
1612 writeCounted(ZipLong.getBytes(Math.min(cdLength, ZipConstants.ZIP64_MAGIC)));
1613 writeCounted(ZipLong.getBytes(Math.min(cdOffset, ZipConstants.ZIP64_MAGIC)));
1614
1615
1616 final ByteBuffer data = this.zipEncoding.encode(comment);
1617 final int dataLen = data.limit() - data.position();
1618 writeCounted(ZipShort.getBytes(dataLen));
1619 streamCompressor.writeCounted(data.array(), data.arrayOffset(), dataLen);
1620 }
1621
1622 private void writeCentralDirectoryInChunks() throws IOException {
1623 final int NUM_PER_WRITE = 1000;
1624 final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(70 * NUM_PER_WRITE);
1625 int count = 0;
1626 for (final ZipArchiveEntry ze : entries) {
1627 byteArrayOutputStream.write(createCentralFileHeader(ze));
1628 if (++count > NUM_PER_WRITE) {
1629 writeCounted(byteArrayOutputStream.toByteArray());
1630 byteArrayOutputStream.reset();
1631 count = 0;
1632 }
1633 }
1634 writeCounted(byteArrayOutputStream.toByteArray());
1635 }
1636
1637
1638
1639
1640
1641
1642
1643
1644 protected void writeCentralFileHeader(final ZipArchiveEntry ze) throws IOException {
1645 final byte[] centralFileHeader = createCentralFileHeader(ze);
1646 writeCounted(centralFileHeader);
1647 }
1648
1649
1650
1651
1652
1653
1654
1655 private void writeCounted(final byte[] data) throws IOException {
1656 streamCompressor.writeCounted(data);
1657 }
1658
1659
1660
1661
1662
1663
1664
1665 protected void writeDataDescriptor(final ZipArchiveEntry ze) throws IOException {
1666 if (!usesDataDescriptor(ze.getMethod(), false)) {
1667 return;
1668 }
1669 writeCounted(DD_SIG);
1670 writeCounted(ZipLong.getBytes(ze.getCrc()));
1671 if (!hasZip64Extra(ze)) {
1672 writeCounted(ZipLong.getBytes(ze.getCompressedSize()));
1673 writeCounted(ZipLong.getBytes(ze.getSize()));
1674 } else {
1675 writeCounted(ZipEightByteInteger.getBytes(ze.getCompressedSize()));
1676 writeCounted(ZipEightByteInteger.getBytes(ze.getSize()));
1677 }
1678 }
1679
1680
1681
1682
1683
1684
1685
1686 protected void writeLocalFileHeader(final ZipArchiveEntry ze) throws IOException {
1687 writeLocalFileHeader(ze, false);
1688 }
1689
1690 private void writeLocalFileHeader(final ZipArchiveEntry ze, final boolean phased) throws IOException {
1691 final boolean encodable = zipEncoding.canEncode(ze.getName());
1692 final ByteBuffer name = getName(ze);
1693
1694 if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
1695 addUnicodeExtraFields(ze, encodable, name);
1696 }
1697
1698 long localHeaderStart = streamCompressor.getTotalBytesWritten();
1699 if (isSplitZip) {
1700
1701
1702 final ZipSplitOutputStream splitOutputStream = (ZipSplitOutputStream) this.outputStream;
1703 ze.setDiskNumberStart(splitOutputStream.getCurrentSplitSegmentIndex());
1704 localHeaderStart = splitOutputStream.getCurrentSplitSegmentBytesWritten();
1705 }
1706
1707 final byte[] localHeader = createLocalFileHeader(ze, name, encodable, phased, localHeaderStart);
1708 metaData.put(ze, new EntryMetaData(localHeaderStart, usesDataDescriptor(ze.getMethod(), phased)));
1709 entry.localDataStart = localHeaderStart + LFH_CRC_OFFSET;
1710 writeCounted(localHeader);
1711 entry.dataStart = streamCompressor.getTotalBytesWritten();
1712 }
1713
1714
1715
1716
1717
1718
1719
1720 protected final void writeOut(final byte[] data) throws IOException {
1721 streamCompressor.writeOut(data, 0, data.length);
1722 }
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732 protected final void writeOut(final byte[] data, final int offset, final int length) throws IOException {
1733 streamCompressor.writeOut(data, offset, length);
1734 }
1735
1736
1737
1738
1739
1740
1741
1742
1743 public void writePreamble(final byte[] preamble) throws IOException {
1744 writePreamble(preamble, 0, preamble.length);
1745 }
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756 public void writePreamble(final byte[] preamble, final int offset, final int length) throws IOException {
1757 if (entry != null) {
1758 throw new IllegalStateException("Preamble must be written before creating an entry");
1759 }
1760 this.streamCompressor.writeCounted(preamble, offset, length);
1761 }
1762
1763
1764
1765
1766
1767
1768
1769 protected void writeZip64CentralDirectory() throws IOException {
1770 if (zip64Mode == Zip64Mode.Never) {
1771 return;
1772 }
1773
1774 if (!hasUsedZip64 && shouldUseZip64EOCD()) {
1775
1776 hasUsedZip64 = true;
1777 }
1778
1779 if (!hasUsedZip64) {
1780 return;
1781 }
1782
1783 long offset = streamCompressor.getTotalBytesWritten();
1784 long diskNumberStart = 0L;
1785 if (isSplitZip) {
1786
1787
1788 final ZipSplitOutputStream zipSplitOutputStream = (ZipSplitOutputStream) this.outputStream;
1789 offset = zipSplitOutputStream.getCurrentSplitSegmentBytesWritten();
1790 diskNumberStart = zipSplitOutputStream.getCurrentSplitSegmentIndex();
1791 }
1792
1793 writeOut(ZIP64_EOCD_SIG);
1794
1795
1796 writeOut(ZipEightByteInteger.getBytes(ZipConstants.SHORT
1797 + ZipConstants.SHORT
1798 + ZipConstants.WORD
1799 + ZipConstants.WORD
1800 + ZipConstants.DWORD
1801 + ZipConstants.DWORD
1802 + ZipConstants.DWORD
1803 + (long) ZipConstants.DWORD
1804 ));
1805
1806
1807 writeOut(ZipShort.getBytes(ZipConstants.ZIP64_MIN_VERSION));
1808 writeOut(ZipShort.getBytes(ZipConstants.ZIP64_MIN_VERSION));
1809
1810
1811 int numberOfThisDisk = 0;
1812 if (isSplitZip) {
1813 numberOfThisDisk = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
1814 }
1815 writeOut(ZipLong.getBytes(numberOfThisDisk));
1816
1817
1818 writeOut(ZipLong.getBytes(cdDiskNumberStart));
1819
1820
1821 final int numOfEntriesOnThisDisk = isSplitZip ? numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0) : entries.size();
1822 final byte[] numOfEntriesOnThisDiskData = ZipEightByteInteger.getBytes(numOfEntriesOnThisDisk);
1823 writeOut(numOfEntriesOnThisDiskData);
1824
1825
1826 final byte[] num = ZipEightByteInteger.getBytes(entries.size());
1827 writeOut(num);
1828
1829
1830 writeOut(ZipEightByteInteger.getBytes(cdLength));
1831 writeOut(ZipEightByteInteger.getBytes(cdOffset));
1832
1833
1834
1835 if (isSplitZip) {
1836
1837
1838 final int zip64EOCDLOCLength = ZipConstants.WORD
1839 + ZipConstants.WORD
1840 + ZipConstants.DWORD
1841 + ZipConstants.WORD ;
1842
1843 final long unsplittableContentSize = zip64EOCDLOCLength + eocdLength;
1844 ((ZipSplitOutputStream) this.outputStream).prepareToWriteUnsplittableContent(unsplittableContentSize);
1845 }
1846
1847
1848 writeOut(ZIP64_EOCD_LOC_SIG);
1849
1850
1851 writeOut(ZipLong.getBytes(diskNumberStart));
1852
1853 writeOut(ZipEightByteInteger.getBytes(offset));
1854
1855 if (isSplitZip) {
1856
1857
1858 final int totalNumberOfDisks = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex() + 1;
1859 writeOut(ZipLong.getBytes(totalNumberOfDisks));
1860 } else {
1861 writeOut(ONE);
1862 }
1863 }
1864 }