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