1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.compress.archivers.sevenz;
18
19 import static java.nio.charset.StandardCharsets.UTF_16LE;
20
21 import java.io.BufferedInputStream;
22 import java.io.ByteArrayOutputStream;
23 import java.io.Closeable;
24 import java.io.DataOutput;
25 import java.io.DataOutputStream;
26 import java.io.File;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.nio.ByteBuffer;
31 import java.nio.ByteOrder;
32 import java.nio.channels.SeekableByteChannel;
33 import java.nio.file.Files;
34 import java.nio.file.LinkOption;
35 import java.nio.file.OpenOption;
36 import java.nio.file.Path;
37 import java.nio.file.StandardOpenOption;
38 import java.nio.file.attribute.BasicFileAttributes;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.BitSet;
42 import java.util.Collections;
43 import java.util.Date;
44 import java.util.EnumSet;
45 import java.util.HashMap;
46 import java.util.LinkedList;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.stream.Collectors;
50 import java.util.stream.Stream;
51 import java.util.stream.StreamSupport;
52 import java.util.zip.CRC32;
53
54 import org.apache.commons.compress.archivers.ArchiveEntry;
55 import org.apache.commons.io.file.attribute.FileTimes;
56 import org.apache.commons.io.output.CountingOutputStream;
57
58
59
60
61
62
63 public class SevenZOutputFile implements Closeable {
64
65 private final class OutputStreamWrapper extends OutputStream {
66
67 private static final int BUF_SIZE = 8192;
68 private final ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE);
69
70 @Override
71 public void close() throws IOException {
72
73 }
74
75 @Override
76 public void flush() throws IOException {
77
78 }
79
80 @Override
81 public void write(final byte[] b) throws IOException {
82 write(b, 0, b.length);
83 }
84
85 @Override
86 public void write(final byte[] b, final int off, final int len) throws IOException {
87 if (len > BUF_SIZE) {
88 channel.write(ByteBuffer.wrap(b, off, len));
89 } else {
90 buffer.clear();
91 buffer.put(b, off, len).flip();
92 channel.write(buffer);
93 }
94 compressedCrc32.update(b, off, len);
95 fileBytesWritten += len;
96 }
97
98 @Override
99 public void write(final int b) throws IOException {
100 buffer.clear();
101 buffer.put((byte) b).flip();
102 channel.write(buffer);
103 compressedCrc32.update(b);
104 fileBytesWritten++;
105 }
106 }
107
108 private static <T> Iterable<T> reverse(final Iterable<T> i) {
109 final LinkedList<T> l = new LinkedList<>();
110 for (final T t : i) {
111 l.addFirst(t);
112 }
113 return l;
114 }
115
116 private final SeekableByteChannel channel;
117 private final List<SevenZArchiveEntry> files = new ArrayList<>();
118 private int numNonEmptyStreams;
119 private final CRC32 crc32 = new CRC32();
120 private final CRC32 compressedCrc32 = new CRC32();
121 private long fileBytesWritten;
122 private boolean finished;
123 private CountingOutputStream currentOutputStream;
124 private CountingOutputStream[] additionalCountingStreams;
125 private Iterable<? extends SevenZMethodConfiguration> contentMethods = Collections.singletonList(new SevenZMethodConfiguration(SevenZMethod.LZMA2));
126 private final Map<SevenZArchiveEntry, long[]> additionalSizes = new HashMap<>();
127 private AES256Options aes256Options;
128
129
130
131
132
133
134
135 public SevenZOutputFile(final File fileName) throws IOException {
136 this(fileName, null);
137 }
138
139
140
141
142
143
144
145
146
147 public SevenZOutputFile(final File fileName, final char[] password) throws IOException {
148 this(Files.newByteChannel(fileName.toPath(), EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)),
149 password);
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163 public SevenZOutputFile(final SeekableByteChannel channel) throws IOException {
164 this(channel, null);
165 }
166
167
168
169
170
171
172
173
174
175
176
177
178
179 public SevenZOutputFile(final SeekableByteChannel channel, final char[] password) throws IOException {
180 this.channel = channel;
181 channel.position(SevenZFile.SIGNATURE_HEADER_SIZE);
182 if (password != null) {
183 this.aes256Options = new AES256Options(password);
184 }
185 }
186
187
188
189
190
191
192 @Override
193 public void close() throws IOException {
194 try {
195 if (!finished) {
196 finish();
197 }
198 } finally {
199 channel.close();
200 }
201 }
202
203
204
205
206
207
208 public void closeArchiveEntry() throws IOException {
209 if (currentOutputStream != null) {
210 currentOutputStream.flush();
211 currentOutputStream.close();
212 }
213
214 final SevenZArchiveEntry entry = files.get(files.size() - 1);
215 if (fileBytesWritten > 0) {
216 entry.setHasStream(true);
217 ++numNonEmptyStreams;
218 entry.setSize(currentOutputStream.getByteCount());
219 entry.setCompressedSize(fileBytesWritten);
220 entry.setCrcValue(crc32.getValue());
221 entry.setCompressedCrcValue(compressedCrc32.getValue());
222 entry.setHasCrc(true);
223 if (additionalCountingStreams != null) {
224 final long[] sizes = new long[additionalCountingStreams.length];
225 Arrays.setAll(sizes, i -> additionalCountingStreams[i].getByteCount());
226 additionalSizes.put(entry, sizes);
227 }
228 } else {
229 entry.setHasStream(false);
230 entry.setSize(0);
231 entry.setCompressedSize(0);
232 entry.setHasCrc(false);
233 }
234 currentOutputStream = null;
235 additionalCountingStreams = null;
236 crc32.reset();
237 compressedCrc32.reset();
238 fileBytesWritten = 0;
239 }
240
241
242
243
244
245
246
247
248 public SevenZArchiveEntry createArchiveEntry(final File inputFile, final String entryName) {
249 final SevenZArchiveEntry entry = new SevenZArchiveEntry();
250 entry.setDirectory(inputFile.isDirectory());
251 entry.setName(entryName);
252 try {
253 fillDates(inputFile.toPath(), entry);
254 } catch (final IOException e) {
255 entry.setLastModifiedDate(new Date(inputFile.lastModified()));
256 }
257 return entry;
258 }
259
260
261
262
263
264
265
266
267
268
269
270
271 public SevenZArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
272 final SevenZArchiveEntry entry = new SevenZArchiveEntry();
273 entry.setDirectory(Files.isDirectory(inputPath, options));
274 entry.setName(entryName);
275 fillDates(inputPath, entry, options);
276 return entry;
277 }
278
279 private void fillDates(final Path inputPath, final SevenZArchiveEntry entry, final LinkOption... options) throws IOException {
280 final BasicFileAttributes attributes = Files.readAttributes(inputPath, BasicFileAttributes.class, options);
281 entry.setLastModifiedTime(attributes.lastModifiedTime());
282 entry.setCreationTime(attributes.creationTime());
283 entry.setAccessTime(attributes.lastAccessTime());
284 }
285
286
287
288
289
290
291 public void finish() throws IOException {
292 if (finished) {
293 throw new IOException("This archive has already been finished");
294 }
295 finished = true;
296
297 final long headerPosition = channel.position();
298
299 final ByteArrayOutputStream headerBaos = new ByteArrayOutputStream();
300 final DataOutputStream header = new DataOutputStream(headerBaos);
301
302 writeHeader(header);
303 header.flush();
304 final byte[] headerBytes = headerBaos.toByteArray();
305 channel.write(ByteBuffer.wrap(headerBytes));
306
307 final CRC32 crc32 = new CRC32();
308 crc32.update(headerBytes);
309
310 final ByteBuffer bb = ByteBuffer.allocate(SevenZFile.sevenZSignature.length + 2
311 + 4
312 + 8
313 + 8
314 + 4 ).order(ByteOrder.LITTLE_ENDIAN);
315
316 channel.position(0);
317 bb.put(SevenZFile.sevenZSignature);
318
319 bb.put((byte) 0).put((byte) 2);
320
321
322 bb.putInt(0);
323
324
325 bb.putLong(headerPosition - SevenZFile.SIGNATURE_HEADER_SIZE).putLong(0xffffFFFFL & headerBytes.length).putInt((int) crc32.getValue());
326 crc32.reset();
327 crc32.update(bb.array(), SevenZFile.sevenZSignature.length + 6, 20);
328 bb.putInt(SevenZFile.sevenZSignature.length + 2, (int) crc32.getValue());
329 bb.flip();
330 channel.write(bb);
331 }
332
333 private Iterable<? extends SevenZMethodConfiguration> getContentMethods(final SevenZArchiveEntry entry) {
334 final Iterable<? extends SevenZMethodConfiguration> ms = entry.getContentMethods();
335 Iterable<? extends SevenZMethodConfiguration> iter = ms == null ? contentMethods : ms;
336
337 if (aes256Options != null) {
338
339 iter = Stream
340 .concat(Stream.of(new SevenZMethodConfiguration(SevenZMethod.AES256SHA256, aes256Options)), StreamSupport.stream(iter.spliterator(), false))
341 .collect(Collectors.toList());
342 }
343 return iter;
344 }
345
346
347
348
349
350 private OutputStream getCurrentOutputStream() throws IOException {
351 if (currentOutputStream == null) {
352 currentOutputStream = setupFileOutputStream();
353 }
354 return currentOutputStream;
355 }
356
357
358
359
360
361
362
363
364
365 @Deprecated
366 public void putArchiveEntry(final ArchiveEntry archiveEntry) {
367 putArchiveEntry((SevenZArchiveEntry) archiveEntry);
368 }
369
370
371
372
373
374
375
376
377
378 public void putArchiveEntry(final SevenZArchiveEntry archiveEntry) {
379 files.add(archiveEntry);
380 }
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395 public void setContentCompression(final SevenZMethod method) {
396 setContentMethods(Collections.singletonList(new SevenZMethodConfiguration(method)));
397 }
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413 public void setContentMethods(final Iterable<? extends SevenZMethodConfiguration> methods) {
414 this.contentMethods = reverse(methods);
415 }
416
417 private CountingOutputStream setupFileOutputStream() throws IOException {
418 if (files.isEmpty()) {
419 throw new IllegalStateException("No current 7z entry");
420 }
421
422
423 OutputStream out = new OutputStreamWrapper();
424 final ArrayList<CountingOutputStream> moreStreams = new ArrayList<>();
425 boolean first = true;
426 for (final SevenZMethodConfiguration m : getContentMethods(files.get(files.size() - 1))) {
427 if (!first) {
428 final CountingOutputStream cos = new CountingOutputStream(out);
429 moreStreams.add(cos);
430 out = cos;
431 }
432 out = Coders.addEncoder(out, m.getMethod(), m.getOptions());
433 first = false;
434 }
435 if (!moreStreams.isEmpty()) {
436 additionalCountingStreams = moreStreams.toArray(new CountingOutputStream[0]);
437 }
438 return new CountingOutputStream(out) {
439 @Override
440 public void write(final byte[] b) throws IOException {
441 super.write(b);
442 crc32.update(b);
443 }
444
445 @Override
446 public void write(final byte[] b, final int off, final int len) throws IOException {
447 super.write(b, off, len);
448 crc32.update(b, off, len);
449 }
450
451 @Override
452 public void write(final int b) throws IOException {
453 super.write(b);
454 crc32.update(b);
455 }
456 };
457 }
458
459
460
461
462
463
464
465 public void write(final byte[] b) throws IOException {
466 write(b, 0, b.length);
467 }
468
469
470
471
472
473
474
475
476
477 public void write(final byte[] b, final int off, final int len) throws IOException {
478 if (len > 0) {
479 getCurrentOutputStream().write(b, off, len);
480 }
481 }
482
483
484
485
486
487
488
489
490 public void write(final InputStream inputStream) throws IOException {
491 final byte[] buffer = new byte[8024];
492 int n = 0;
493 while (-1 != (n = inputStream.read(buffer))) {
494 write(buffer, 0, n);
495 }
496 }
497
498
499
500
501
502
503
504 public void write(final int b) throws IOException {
505 getCurrentOutputStream().write(b);
506 }
507
508
509
510
511
512
513
514
515
516 public void write(final Path path, final OpenOption... options) throws IOException {
517 try (InputStream in = new BufferedInputStream(Files.newInputStream(path, options))) {
518 write(in);
519 }
520 }
521
522 private void writeBits(final DataOutput header, final BitSet bits, final int length) throws IOException {
523 int cache = 0;
524 int shift = 7;
525 for (int i = 0; i < length; i++) {
526 cache |= (bits.get(i) ? 1 : 0) << shift;
527 if (--shift < 0) {
528 header.write(cache);
529 shift = 7;
530 cache = 0;
531 }
532 }
533 if (shift != 7) {
534 header.write(cache);
535 }
536 }
537
538 private void writeFileAntiItems(final DataOutput header) throws IOException {
539 boolean hasAntiItems = false;
540 final BitSet antiItems = new BitSet(0);
541 int antiItemCounter = 0;
542 for (final SevenZArchiveEntry file1 : files) {
543 if (!file1.hasStream()) {
544 final boolean isAnti = file1.isAntiItem();
545 antiItems.set(antiItemCounter++, isAnti);
546 hasAntiItems |= isAnti;
547 }
548 }
549 if (hasAntiItems) {
550 header.write(NID.kAnti);
551 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
552 final DataOutputStream out = new DataOutputStream(baos);
553 writeBits(out, antiItems, antiItemCounter);
554 out.flush();
555 final byte[] contents = baos.toByteArray();
556 writeUint64(header, contents.length);
557 header.write(contents);
558 }
559 }
560
561 private void writeFileATimes(final DataOutput header) throws IOException {
562 int numAccessDates = 0;
563 for (final SevenZArchiveEntry entry : files) {
564 if (entry.getHasAccessDate()) {
565 ++numAccessDates;
566 }
567 }
568 if (numAccessDates > 0) {
569 header.write(NID.kATime);
570
571 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
572 final DataOutputStream out = new DataOutputStream(baos);
573 if (numAccessDates != files.size()) {
574 out.write(0);
575 final BitSet aTimes = new BitSet(files.size());
576 for (int i = 0; i < files.size(); i++) {
577 aTimes.set(i, files.get(i).getHasAccessDate());
578 }
579 writeBits(out, aTimes, files.size());
580 } else {
581 out.write(1);
582 }
583 out.write(0);
584 for (final SevenZArchiveEntry entry : files) {
585 if (entry.getHasAccessDate()) {
586 final long ntfsTime = FileTimes.toNtfsTime(entry.getAccessTime());
587 out.writeLong(Long.reverseBytes(ntfsTime));
588 }
589 }
590 out.flush();
591 final byte[] contents = baos.toByteArray();
592 writeUint64(header, contents.length);
593 header.write(contents);
594 }
595 }
596
597 private void writeFileCTimes(final DataOutput header) throws IOException {
598 int numCreationDates = 0;
599 for (final SevenZArchiveEntry entry : files) {
600 if (entry.getHasCreationDate()) {
601 ++numCreationDates;
602 }
603 }
604 if (numCreationDates > 0) {
605 header.write(NID.kCTime);
606
607 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
608 final DataOutputStream out = new DataOutputStream(baos);
609 if (numCreationDates != files.size()) {
610 out.write(0);
611 final BitSet cTimes = new BitSet(files.size());
612 for (int i = 0; i < files.size(); i++) {
613 cTimes.set(i, files.get(i).getHasCreationDate());
614 }
615 writeBits(out, cTimes, files.size());
616 } else {
617 out.write(1);
618 }
619 out.write(0);
620 for (final SevenZArchiveEntry entry : files) {
621 if (entry.getHasCreationDate()) {
622 final long ntfsTime = FileTimes.toNtfsTime(entry.getCreationTime());
623 out.writeLong(Long.reverseBytes(ntfsTime));
624 }
625 }
626 out.flush();
627 final byte[] contents = baos.toByteArray();
628 writeUint64(header, contents.length);
629 header.write(contents);
630 }
631 }
632
633 private void writeFileEmptyFiles(final DataOutput header) throws IOException {
634 boolean hasEmptyFiles = false;
635 int emptyStreamCounter = 0;
636 final BitSet emptyFiles = new BitSet(0);
637 for (final SevenZArchiveEntry file1 : files) {
638 if (!file1.hasStream()) {
639 final boolean isDir = file1.isDirectory();
640 emptyFiles.set(emptyStreamCounter++, !isDir);
641 hasEmptyFiles |= !isDir;
642 }
643 }
644 if (hasEmptyFiles) {
645 header.write(NID.kEmptyFile);
646 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
647 final DataOutputStream out = new DataOutputStream(baos);
648 writeBits(out, emptyFiles, emptyStreamCounter);
649 out.flush();
650 final byte[] contents = baos.toByteArray();
651 writeUint64(header, contents.length);
652 header.write(contents);
653 }
654 }
655
656 private void writeFileEmptyStreams(final DataOutput header) throws IOException {
657 final boolean hasEmptyStreams = files.stream().anyMatch(entry -> !entry.hasStream());
658 if (hasEmptyStreams) {
659 header.write(NID.kEmptyStream);
660 final BitSet emptyStreams = new BitSet(files.size());
661 for (int i = 0; i < files.size(); i++) {
662 emptyStreams.set(i, !files.get(i).hasStream());
663 }
664 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
665 final DataOutputStream out = new DataOutputStream(baos);
666 writeBits(out, emptyStreams, files.size());
667 out.flush();
668 final byte[] contents = baos.toByteArray();
669 writeUint64(header, contents.length);
670 header.write(contents);
671 }
672 }
673
674 private void writeFileMTimes(final DataOutput header) throws IOException {
675 int numLastModifiedDates = 0;
676 for (final SevenZArchiveEntry entry : files) {
677 if (entry.getHasLastModifiedDate()) {
678 ++numLastModifiedDates;
679 }
680 }
681 if (numLastModifiedDates > 0) {
682 header.write(NID.kMTime);
683
684 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
685 final DataOutputStream out = new DataOutputStream(baos);
686 if (numLastModifiedDates != files.size()) {
687 out.write(0);
688 final BitSet mTimes = new BitSet(files.size());
689 for (int i = 0; i < files.size(); i++) {
690 mTimes.set(i, files.get(i).getHasLastModifiedDate());
691 }
692 writeBits(out, mTimes, files.size());
693 } else {
694 out.write(1);
695 }
696 out.write(0);
697 for (final SevenZArchiveEntry entry : files) {
698 if (entry.getHasLastModifiedDate()) {
699 final long ntfsTime = FileTimes.toNtfsTime(entry.getLastModifiedTime());
700 out.writeLong(Long.reverseBytes(ntfsTime));
701 }
702 }
703 out.flush();
704 final byte[] contents = baos.toByteArray();
705 writeUint64(header, contents.length);
706 header.write(contents);
707 }
708 }
709
710 private void writeFileNames(final DataOutput header) throws IOException {
711 header.write(NID.kName);
712
713 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
714 final DataOutputStream out = new DataOutputStream(baos);
715 out.write(0);
716 for (final SevenZArchiveEntry entry : files) {
717 out.write(entry.getName().getBytes(UTF_16LE));
718 out.writeShort(0);
719 }
720 out.flush();
721 final byte[] contents = baos.toByteArray();
722 writeUint64(header, contents.length);
723 header.write(contents);
724 }
725
726 private void writeFilesInfo(final DataOutput header) throws IOException {
727 header.write(NID.kFilesInfo);
728
729 writeUint64(header, files.size());
730
731 writeFileEmptyStreams(header);
732 writeFileEmptyFiles(header);
733 writeFileAntiItems(header);
734 writeFileNames(header);
735 writeFileCTimes(header);
736 writeFileATimes(header);
737 writeFileMTimes(header);
738 writeFileWindowsAttributes(header);
739 header.write(NID.kEnd);
740 }
741
742 private void writeFileWindowsAttributes(final DataOutput header) throws IOException {
743 int numWindowsAttributes = 0;
744 for (final SevenZArchiveEntry entry : files) {
745 if (entry.getHasWindowsAttributes()) {
746 ++numWindowsAttributes;
747 }
748 }
749 if (numWindowsAttributes > 0) {
750 header.write(NID.kWinAttributes);
751
752 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
753 final DataOutputStream out = new DataOutputStream(baos);
754 if (numWindowsAttributes != files.size()) {
755 out.write(0);
756 final BitSet attributes = new BitSet(files.size());
757 for (int i = 0; i < files.size(); i++) {
758 attributes.set(i, files.get(i).getHasWindowsAttributes());
759 }
760 writeBits(out, attributes, files.size());
761 } else {
762 out.write(1);
763 }
764 out.write(0);
765 for (final SevenZArchiveEntry entry : files) {
766 if (entry.getHasWindowsAttributes()) {
767 out.writeInt(Integer.reverseBytes(entry.getWindowsAttributes()));
768 }
769 }
770 out.flush();
771 final byte[] contents = baos.toByteArray();
772 writeUint64(header, contents.length);
773 header.write(contents);
774 }
775 }
776
777 private void writeFolder(final DataOutput header, final SevenZArchiveEntry entry) throws IOException {
778 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
779 int numCoders = 0;
780 for (final SevenZMethodConfiguration m : getContentMethods(entry)) {
781 numCoders++;
782 writeSingleCodec(m, bos);
783 }
784
785 writeUint64(header, numCoders);
786 header.write(bos.toByteArray());
787 for (long i = 0; i < numCoders - 1; i++) {
788 writeUint64(header, i + 1);
789 writeUint64(header, i);
790 }
791 }
792
793 private void writeHeader(final DataOutput header) throws IOException {
794 header.write(NID.kHeader);
795
796 header.write(NID.kMainStreamsInfo);
797 writeStreamsInfo(header);
798 writeFilesInfo(header);
799 header.write(NID.kEnd);
800 }
801
802 private void writePackInfo(final DataOutput header) throws IOException {
803 header.write(NID.kPackInfo);
804
805 writeUint64(header, 0);
806 writeUint64(header, 0xffffFFFFL & numNonEmptyStreams);
807
808 header.write(NID.kSize);
809 for (final SevenZArchiveEntry entry : files) {
810 if (entry.hasStream()) {
811 writeUint64(header, entry.getCompressedSize());
812 }
813 }
814
815 header.write(NID.kCRC);
816 header.write(1);
817 for (final SevenZArchiveEntry entry : files) {
818 if (entry.hasStream()) {
819 header.writeInt(Integer.reverseBytes((int) entry.getCompressedCrcValue()));
820 }
821 }
822
823 header.write(NID.kEnd);
824 }
825
826 private void writeSingleCodec(final SevenZMethodConfiguration m, final OutputStream bos) throws IOException {
827 final byte[] id = m.getMethod().getId();
828 final byte[] properties = Coders.findByMethod(m.getMethod()).getOptionsAsProperties(m.getOptions());
829
830 int codecFlags = id.length;
831 if (properties.length > 0) {
832 codecFlags |= 0x20;
833 }
834 bos.write(codecFlags);
835 bos.write(id);
836
837 if (properties.length > 0) {
838 bos.write(properties.length);
839 bos.write(properties);
840 }
841 }
842
843 private void writeStreamsInfo(final DataOutput header) throws IOException {
844 if (numNonEmptyStreams > 0) {
845 writePackInfo(header);
846 writeUnpackInfo(header);
847 }
848
849 writeSubStreamsInfo(header);
850
851 header.write(NID.kEnd);
852 }
853
854 private void writeSubStreamsInfo(final DataOutput header) throws IOException {
855 header.write(NID.kSubStreamsInfo);
856
857
858
859
860
861
862
863
864
865 header.write(NID.kEnd);
866 }
867
868 private void writeUint64(final DataOutput header, long value) throws IOException {
869 int firstByte = 0;
870 int mask = 0x80;
871 int i;
872 for (i = 0; i < 8; i++) {
873 if (value < 1L << 7 * (i + 1)) {
874 firstByte |= value >>> 8 * i;
875 break;
876 }
877 firstByte |= mask;
878 mask >>>= 1;
879 }
880 header.write(firstByte);
881 for (; i > 0; i--) {
882 header.write((int) (0xff & value));
883 value >>>= 8;
884 }
885 }
886
887 private void writeUnpackInfo(final DataOutput header) throws IOException {
888 header.write(NID.kUnpackInfo);
889
890 header.write(NID.kFolder);
891 writeUint64(header, numNonEmptyStreams);
892 header.write(0);
893 for (final SevenZArchiveEntry entry : files) {
894 if (entry.hasStream()) {
895 writeFolder(header, entry);
896 }
897 }
898
899 header.write(NID.kCodersUnpackSize);
900 for (final SevenZArchiveEntry entry : files) {
901 if (entry.hasStream()) {
902 final long[] moreSizes = additionalSizes.get(entry);
903 if (moreSizes != null) {
904 for (final long s : moreSizes) {
905 writeUint64(header, s);
906 }
907 }
908 writeUint64(header, entry.getSize());
909 }
910 }
911
912 header.write(NID.kCRC);
913 header.write(1);
914 for (final SevenZArchiveEntry entry : files) {
915 if (entry.hasStream()) {
916 header.writeInt(Integer.reverseBytes((int) entry.getCrcValue()));
917 }
918 }
919
920 header.write(NID.kEnd);
921 }
922
923 }