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