001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.compress.archivers.sevenz; 018 019import static java.nio.charset.StandardCharsets.UTF_16LE; 020 021import java.io.BufferedInputStream; 022import java.io.ByteArrayOutputStream; 023import java.io.Closeable; 024import java.io.DataOutput; 025import java.io.DataOutputStream; 026import java.io.File; 027import java.io.IOException; 028import java.io.InputStream; 029import java.io.OutputStream; 030import java.nio.ByteBuffer; 031import java.nio.ByteOrder; 032import java.nio.channels.SeekableByteChannel; 033import java.nio.file.Files; 034import java.nio.file.LinkOption; 035import java.nio.file.OpenOption; 036import java.nio.file.Path; 037import java.nio.file.StandardOpenOption; 038import java.nio.file.attribute.BasicFileAttributes; 039import java.util.ArrayList; 040import java.util.Arrays; 041import java.util.BitSet; 042import java.util.Collections; 043import java.util.Date; 044import java.util.EnumSet; 045import java.util.HashMap; 046import java.util.LinkedList; 047import java.util.List; 048import java.util.Map; 049import java.util.stream.Collectors; 050import java.util.stream.Stream; 051import java.util.stream.StreamSupport; 052import java.util.zip.CRC32; 053 054import org.apache.commons.compress.archivers.ArchiveEntry; 055import org.apache.commons.compress.utils.CountingOutputStream; 056import org.apache.commons.compress.utils.TimeUtils; 057 058/** 059 * Writes a 7z file. 060 * @since 1.6 061 */ 062public class SevenZOutputFile implements Closeable { 063 private class OutputStreamWrapper extends OutputStream { 064 private static final int BUF_SIZE = 8192; 065 private final ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); 066 @Override 067 public void close() throws IOException { 068 // the file will be closed by the containing class's close method 069 } 070 071 @Override 072 public void flush() throws IOException { 073 // no reason to flush the channel 074 } 075 076 @Override 077 public void write(final byte[] b) throws IOException { 078 OutputStreamWrapper.this.write(b, 0, b.length); 079 } 080 081 @Override 082 public void write(final byte[] b, final int off, final int len) 083 throws IOException { 084 if (len > BUF_SIZE) { 085 channel.write(ByteBuffer.wrap(b, off, len)); 086 } else { 087 buffer.clear(); 088 buffer.put(b, off, len).flip(); 089 channel.write(buffer); 090 } 091 compressedCrc32.update(b, off, len); 092 fileBytesWritten += len; 093 } 094 095 @Override 096 public void write(final int b) throws IOException { 097 buffer.clear(); 098 buffer.put((byte) b).flip(); 099 channel.write(buffer); 100 compressedCrc32.update(b); 101 fileBytesWritten++; 102 } 103 } 104 private static <T> Iterable<T> reverse(final Iterable<T> i) { 105 final LinkedList<T> l = new LinkedList<>(); 106 for (final T t : i) { 107 l.addFirst(t); 108 } 109 return l; 110 } 111 private final SeekableByteChannel channel; 112 private final List<SevenZArchiveEntry> files = new ArrayList<>(); 113 private int numNonEmptyStreams; 114 private final CRC32 crc32 = new CRC32(); 115 private final CRC32 compressedCrc32 = new CRC32(); 116 private long fileBytesWritten; 117 private boolean finished; 118 private CountingOutputStream currentOutputStream; 119 private CountingOutputStream[] additionalCountingStreams; 120 private Iterable<? extends SevenZMethodConfiguration> contentMethods = 121 Collections.singletonList(new SevenZMethodConfiguration(SevenZMethod.LZMA2)); 122 123 private final Map<SevenZArchiveEntry, long[]> additionalSizes = new HashMap<>(); 124 125 private AES256Options aes256Options; 126 127 /** 128 * Opens file to write a 7z archive to. 129 * 130 * @param fileName the file to write to 131 * @throws IOException if opening the file fails 132 */ 133 public SevenZOutputFile(final File fileName) throws IOException { 134 this(fileName, null); 135 } 136 137 /** 138 * Opens file to write a 7z archive to. 139 * 140 * @param fileName the file to write to 141 * @param password optional password if the archive has to be encrypted 142 * @throws IOException if opening the file fails 143 * @since 1.23 144 */ 145 public SevenZOutputFile(final File fileName, final char[] password) throws IOException { 146 this( 147 Files.newByteChannel( 148 fileName.toPath(), 149 EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING) 150 ), 151 password 152 ); 153 } 154 155 /** 156 * Prepares channel to write a 7z archive to. 157 * 158 * <p>{@link 159 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 160 * allows you to write to an in-memory archive.</p> 161 * 162 * @param channel the channel to write to 163 * @throws IOException if the channel cannot be positioned properly 164 * @since 1.13 165 */ 166 public SevenZOutputFile(final SeekableByteChannel channel) throws IOException { 167 this(channel, null); 168 } 169 170 /** 171 * Prepares channel to write a 7z archive to. 172 * 173 * <p>{@link 174 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 175 * allows you to write to an in-memory archive.</p> 176 * 177 * @param channel the channel to write to 178 * @param password optional password if the archive has to be encrypted 179 * @throws IOException if the channel cannot be positioned properly 180 * @since 1.23 181 */ 182 public SevenZOutputFile(final SeekableByteChannel channel, final char[] password) throws IOException { 183 this.channel = channel; 184 channel.position(SevenZFile.SIGNATURE_HEADER_SIZE); 185 if (password != null) { 186 this.aes256Options = new AES256Options(password); 187 } 188 } 189 190 /** 191 * Closes the archive, calling {@link #finish} if necessary. 192 * 193 * @throws IOException on error 194 */ 195 @Override 196 public void close() throws IOException { 197 try { 198 if (!finished) { 199 finish(); 200 } 201 } finally { 202 channel.close(); 203 } 204 } 205 206 /** 207 * Closes the archive entry. 208 * @throws IOException on error 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) { // this implies currentOutputStream != null 218 entry.setHasStream(true); 219 ++numNonEmptyStreams; 220 entry.setSize(currentOutputStream.getBytesWritten()); //NOSONAR 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].getBytesWritten()); 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 * Create an archive entry using the inputFile and entryName provided. 245 * 246 * @param inputFile file to create an entry from 247 * @param entryName the name to use 248 * @return the ArchiveEntry set up with details from the file 249 */ 250 public SevenZArchiveEntry createArchiveEntry(final File inputFile, 251 final String entryName) { 252 final SevenZArchiveEntry entry = new SevenZArchiveEntry(); 253 entry.setDirectory(inputFile.isDirectory()); 254 entry.setName(entryName); 255 try { 256 fillDates(inputFile.toPath(), entry); 257 } catch (final IOException e) { // NOSONAR 258 entry.setLastModifiedDate(new Date(inputFile.lastModified())); 259 } 260 return entry; 261 } 262 263 /** 264 * Create an archive entry using the inputPath and entryName provided. 265 * 266 * @param inputPath path to create an entry from 267 * @param entryName the name to use 268 * @param options options indicating how symbolic links are handled. 269 * @return the ArchiveEntry set up with details from the file 270 * 271 * @throws IOException on error 272 * @since 1.21 273 */ 274 public SevenZArchiveEntry createArchiveEntry(final Path inputPath, 275 final String entryName, final LinkOption... options) throws IOException { 276 final SevenZArchiveEntry entry = new SevenZArchiveEntry(); 277 entry.setDirectory(Files.isDirectory(inputPath, options)); 278 entry.setName(entryName); 279 fillDates(inputPath, entry, options); 280 return entry; 281 } 282 283 private void fillDates(final Path inputPath, final SevenZArchiveEntry entry, 284 final LinkOption... options) throws IOException { 285 final BasicFileAttributes attributes = Files.readAttributes(inputPath, BasicFileAttributes.class, options); 286 entry.setLastModifiedTime(attributes.lastModifiedTime()); 287 entry.setCreationTime(attributes.creationTime()); 288 entry.setAccessTime(attributes.lastAccessTime()); 289 } 290 291 /** 292 * Finishes the addition of entries to this archive, without closing it. 293 * 294 * @throws IOException if archive is already closed. 295 */ 296 public void finish() throws IOException { 297 if (finished) { 298 throw new IOException("This archive has already been finished"); 299 } 300 finished = true; 301 302 final long headerPosition = channel.position(); 303 304 final ByteArrayOutputStream headerBaos = new ByteArrayOutputStream(); 305 final DataOutputStream header = new DataOutputStream(headerBaos); 306 307 writeHeader(header); 308 header.flush(); 309 final byte[] headerBytes = headerBaos.toByteArray(); 310 channel.write(ByteBuffer.wrap(headerBytes)); 311 312 final CRC32 crc32 = new CRC32(); 313 crc32.update(headerBytes); 314 315 final ByteBuffer bb = ByteBuffer.allocate(SevenZFile.sevenZSignature.length 316 + 2 /* version */ 317 + 4 /* start header CRC */ 318 + 8 /* next header position */ 319 + 8 /* next header length */ 320 + 4 /* next header CRC */) 321 .order(ByteOrder.LITTLE_ENDIAN); 322 // signature header 323 channel.position(0); 324 bb.put(SevenZFile.sevenZSignature); 325 // version 326 bb.put((byte) 0).put((byte) 2); 327 328 // placeholder for start header CRC 329 bb.putInt(0); 330 331 // start header 332 bb.putLong(headerPosition - SevenZFile.SIGNATURE_HEADER_SIZE) 333 .putLong(0xffffFFFFL & headerBytes.length) 334 .putInt((int) crc32.getValue()); 335 crc32.reset(); 336 crc32.update(bb.array(), SevenZFile.sevenZSignature.length + 6, 20); 337 bb.putInt(SevenZFile.sevenZSignature.length + 2, (int) crc32.getValue()); 338 bb.flip(); 339 channel.write(bb); 340 } 341 342 private Iterable<? extends SevenZMethodConfiguration> getContentMethods(final SevenZArchiveEntry entry) { 343 final Iterable<? extends SevenZMethodConfiguration> ms = entry.getContentMethods(); 344 Iterable<? extends SevenZMethodConfiguration> iter = ms == null ? contentMethods : ms; 345 346 if (aes256Options != null) { 347 // prepend encryption 348 iter = 349 Stream 350 .concat( 351 Stream.of(new SevenZMethodConfiguration(SevenZMethod.AES256SHA256, aes256Options)), 352 StreamSupport.stream(iter.spliterator(), false) 353 ) 354 .collect(Collectors.toList()); 355 } 356 return iter; 357 } 358 359 /* 360 * Creation of output stream is deferred until data is actually 361 * written as some codecs might write header information even for 362 * empty streams and directories otherwise. 363 */ 364 private OutputStream getCurrentOutputStream() throws IOException { 365 if (currentOutputStream == null) { 366 currentOutputStream = setupFileOutputStream(); 367 } 368 return currentOutputStream; 369 } 370 371 /** 372 * Records an archive entry to add. 373 * 374 * The caller must then write the content to the archive and call 375 * {@link #closeArchiveEntry()} to complete the process. 376 * 377 * @param archiveEntry describes the entry 378 */ 379 public void putArchiveEntry(final ArchiveEntry archiveEntry) { 380 final SevenZArchiveEntry entry = (SevenZArchiveEntry) archiveEntry; 381 files.add(entry); 382 } 383 384 /** 385 * Sets the default compression method to use for entry contents - the 386 * default is LZMA2. 387 * 388 * <p>Currently only {@link SevenZMethod#COPY}, {@link 389 * SevenZMethod#LZMA2}, {@link SevenZMethod#BZIP2} and {@link 390 * SevenZMethod#DEFLATE} are supported.</p> 391 * 392 * <p>This is a short form for passing a single-element iterable 393 * to {@link #setContentMethods}.</p> 394 * @param method the default compression method 395 */ 396 public void setContentCompression(final SevenZMethod method) { 397 setContentMethods(Collections.singletonList(new SevenZMethodConfiguration(method))); 398 } 399 400 /** 401 * Sets the default (compression) methods to use for entry contents - the 402 * default is LZMA2. 403 * 404 * <p>Currently only {@link SevenZMethod#COPY}, {@link 405 * SevenZMethod#LZMA2}, {@link SevenZMethod#BZIP2} and {@link 406 * SevenZMethod#DEFLATE} are supported.</p> 407 * 408 * <p>The methods will be consulted in iteration order to create 409 * the final output.</p> 410 * 411 * @since 1.8 412 * @param methods the default (compression) methods 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 // doesn't need to be closed, just wraps the instance field channel 424 OutputStream out = new OutputStreamWrapper(); // NOSONAR 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) 448 throws IOException { 449 super.write(b, off, len); 450 crc32.update(b, off, len); 451 } 452 453 @Override 454 public void write(final int b) throws IOException { 455 super.write(b); 456 crc32.update(b); 457 } 458 }; 459 } 460 461 /** 462 * Writes a byte array to the current archive entry. 463 * @param b The byte array to be written. 464 * @throws IOException on error 465 */ 466 public void write(final byte[] b) throws IOException { 467 write(b, 0, b.length); 468 } 469 470 /** 471 * Writes part of a byte array to the current archive entry. 472 * @param b The byte array to be written. 473 * @param off offset into the array to start writing from 474 * @param len number of bytes to write 475 * @throws IOException on error 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 * Writes all of the given input stream to the current archive entry. 485 * @param inputStream the data source. 486 * @throws IOException if an I/O error occurs. 487 * @since 1.21 488 */ 489 public void write(final InputStream inputStream) throws IOException { 490 final byte[] buffer = new byte[8024]; 491 int n = 0; 492 while (-1 != (n = inputStream.read(buffer))) { 493 write(buffer, 0, n); 494 } 495 } 496 497 /** 498 * Writes a byte to the current archive entry. 499 * @param b The byte to be written. 500 * @throws IOException on error 501 */ 502 public void write(final int b) throws IOException { 503 getCurrentOutputStream().write(b); 504 } 505 506 /** 507 * Writes all of the given input stream to the current archive entry. 508 * @param path the data source. 509 * @param options options specifying how the file is opened. 510 * @throws IOException if an I/O error occurs. 511 * @since 1.21 512 */ 513 public void write(final Path path, final OpenOption... options) throws IOException { 514 try (InputStream in = new BufferedInputStream(Files.newInputStream(path, options))) { 515 write(in); 516 } 517 } 518 519 private void writeBits(final DataOutput header, final BitSet bits, final int length) throws IOException { 520 int cache = 0; 521 int shift = 7; 522 for (int i = 0; i < length; i++) { 523 cache |= ((bits.get(i) ? 1 : 0) << shift); 524 if (--shift < 0) { 525 header.write(cache); 526 shift = 7; 527 cache = 0; 528 } 529 } 530 if (shift != 7) { 531 header.write(cache); 532 } 533 } 534 535 private void writeFileAntiItems(final DataOutput header) throws IOException { 536 boolean hasAntiItems = false; 537 final BitSet antiItems = new BitSet(0); 538 int antiItemCounter = 0; 539 for (final SevenZArchiveEntry file1 : files) { 540 if (!file1.hasStream()) { 541 final boolean isAnti = file1.isAntiItem(); 542 antiItems.set(antiItemCounter++, isAnti); 543 hasAntiItems |= isAnti; 544 } 545 } 546 if (hasAntiItems) { 547 header.write(NID.kAnti); 548 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 549 final DataOutputStream out = new DataOutputStream(baos); 550 writeBits(out, antiItems, antiItemCounter); 551 out.flush(); 552 final byte[] contents = baos.toByteArray(); 553 writeUint64(header, contents.length); 554 header.write(contents); 555 } 556 } 557 558 private void writeFileATimes(final DataOutput header) throws IOException { 559 int numAccessDates = 0; 560 for (final SevenZArchiveEntry entry : files) { 561 if (entry.getHasAccessDate()) { 562 ++numAccessDates; 563 } 564 } 565 if (numAccessDates > 0) { 566 header.write(NID.kATime); 567 568 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 569 final DataOutputStream out = new DataOutputStream(baos); 570 if (numAccessDates != files.size()) { 571 out.write(0); 572 final BitSet aTimes = new BitSet(files.size()); 573 for (int i = 0; i < files.size(); i++) { 574 aTimes.set(i, files.get(i).getHasAccessDate()); 575 } 576 writeBits(out, aTimes, files.size()); 577 } else { 578 out.write(1); // "allAreDefined" == true 579 } 580 out.write(0); 581 for (final SevenZArchiveEntry entry : files) { 582 if (entry.getHasAccessDate()) { 583 final long ntfsTime = TimeUtils.toNtfsTime(entry.getAccessTime()); 584 out.writeLong(Long.reverseBytes(ntfsTime)); 585 } 586 } 587 out.flush(); 588 final byte[] contents = baos.toByteArray(); 589 writeUint64(header, contents.length); 590 header.write(contents); 591 } 592 } 593 594 private void writeFileCTimes(final DataOutput header) throws IOException { 595 int numCreationDates = 0; 596 for (final SevenZArchiveEntry entry : files) { 597 if (entry.getHasCreationDate()) { 598 ++numCreationDates; 599 } 600 } 601 if (numCreationDates > 0) { 602 header.write(NID.kCTime); 603 604 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 605 final DataOutputStream out = new DataOutputStream(baos); 606 if (numCreationDates != files.size()) { 607 out.write(0); 608 final BitSet cTimes = new BitSet(files.size()); 609 for (int i = 0; i < files.size(); i++) { 610 cTimes.set(i, files.get(i).getHasCreationDate()); 611 } 612 writeBits(out, cTimes, files.size()); 613 } else { 614 out.write(1); // "allAreDefined" == true 615 } 616 out.write(0); 617 for (final SevenZArchiveEntry entry : files) { 618 if (entry.getHasCreationDate()) { 619 final long ntfsTime = TimeUtils.toNtfsTime(entry.getCreationTime()); 620 out.writeLong(Long.reverseBytes(ntfsTime)); 621 } 622 } 623 out.flush(); 624 final byte[] contents = baos.toByteArray(); 625 writeUint64(header, contents.length); 626 header.write(contents); 627 } 628 } 629 630 private void writeFileEmptyFiles(final DataOutput header) throws IOException { 631 boolean hasEmptyFiles = false; 632 int emptyStreamCounter = 0; 633 final BitSet emptyFiles = new BitSet(0); 634 for (final SevenZArchiveEntry file1 : files) { 635 if (!file1.hasStream()) { 636 final boolean isDir = file1.isDirectory(); 637 emptyFiles.set(emptyStreamCounter++, !isDir); 638 hasEmptyFiles |= !isDir; 639 } 640 } 641 if (hasEmptyFiles) { 642 header.write(NID.kEmptyFile); 643 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 644 final DataOutputStream out = new DataOutputStream(baos); 645 writeBits(out, emptyFiles, emptyStreamCounter); 646 out.flush(); 647 final byte[] contents = baos.toByteArray(); 648 writeUint64(header, contents.length); 649 header.write(contents); 650 } 651 } 652 653 private void writeFileEmptyStreams(final DataOutput header) throws IOException { 654 final boolean hasEmptyStreams = files.stream().anyMatch(entry -> !entry.hasStream()); 655 if (hasEmptyStreams) { 656 header.write(NID.kEmptyStream); 657 final BitSet emptyStreams = new BitSet(files.size()); 658 for (int i = 0; i < files.size(); i++) { 659 emptyStreams.set(i, !files.get(i).hasStream()); 660 } 661 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 662 final DataOutputStream out = new DataOutputStream(baos); 663 writeBits(out, emptyStreams, files.size()); 664 out.flush(); 665 final byte[] contents = baos.toByteArray(); 666 writeUint64(header, contents.length); 667 header.write(contents); 668 } 669 } 670 671 private void writeFileMTimes(final DataOutput header) throws IOException { 672 int numLastModifiedDates = 0; 673 for (final SevenZArchiveEntry entry : files) { 674 if (entry.getHasLastModifiedDate()) { 675 ++numLastModifiedDates; 676 } 677 } 678 if (numLastModifiedDates > 0) { 679 header.write(NID.kMTime); 680 681 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 682 final DataOutputStream out = new DataOutputStream(baos); 683 if (numLastModifiedDates != files.size()) { 684 out.write(0); 685 final BitSet mTimes = new BitSet(files.size()); 686 for (int i = 0; i < files.size(); i++) { 687 mTimes.set(i, files.get(i).getHasLastModifiedDate()); 688 } 689 writeBits(out, mTimes, files.size()); 690 } else { 691 out.write(1); // "allAreDefined" == true 692 } 693 out.write(0); 694 for (final SevenZArchiveEntry entry : files) { 695 if (entry.getHasLastModifiedDate()) { 696 final long ntfsTime = TimeUtils.toNtfsTime(entry.getLastModifiedTime()); 697 out.writeLong(Long.reverseBytes(ntfsTime)); 698 } 699 } 700 out.flush(); 701 final byte[] contents = baos.toByteArray(); 702 writeUint64(header, contents.length); 703 header.write(contents); 704 } 705 } 706 707 private void writeFileNames(final DataOutput header) throws IOException { 708 header.write(NID.kName); 709 710 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 711 final DataOutputStream out = new DataOutputStream(baos); 712 out.write(0); 713 for (final SevenZArchiveEntry entry : files) { 714 out.write(entry.getName().getBytes(UTF_16LE)); 715 out.writeShort(0); 716 } 717 out.flush(); 718 final byte[] contents = baos.toByteArray(); 719 writeUint64(header, contents.length); 720 header.write(contents); 721 } 722 723 private void writeFilesInfo(final DataOutput header) throws IOException { 724 header.write(NID.kFilesInfo); 725 726 writeUint64(header, files.size()); 727 728 writeFileEmptyStreams(header); 729 writeFileEmptyFiles(header); 730 writeFileAntiItems(header); 731 writeFileNames(header); 732 writeFileCTimes(header); 733 writeFileATimes(header); 734 writeFileMTimes(header); 735 writeFileWindowsAttributes(header); 736 header.write(NID.kEnd); 737 } 738 739 private void writeFileWindowsAttributes(final DataOutput header) throws IOException { 740 int numWindowsAttributes = 0; 741 for (final SevenZArchiveEntry entry : files) { 742 if (entry.getHasWindowsAttributes()) { 743 ++numWindowsAttributes; 744 } 745 } 746 if (numWindowsAttributes > 0) { 747 header.write(NID.kWinAttributes); 748 749 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 750 final DataOutputStream out = new DataOutputStream(baos); 751 if (numWindowsAttributes != files.size()) { 752 out.write(0); 753 final BitSet attributes = new BitSet(files.size()); 754 for (int i = 0; i < files.size(); i++) { 755 attributes.set(i, files.get(i).getHasWindowsAttributes()); 756 } 757 writeBits(out, attributes, files.size()); 758 } else { 759 out.write(1); // "allAreDefined" == true 760 } 761 out.write(0); 762 for (final SevenZArchiveEntry entry : files) { 763 if (entry.getHasWindowsAttributes()) { 764 out.writeInt(Integer.reverseBytes(entry.getWindowsAttributes())); 765 } 766 } 767 out.flush(); 768 final byte[] contents = baos.toByteArray(); 769 writeUint64(header, contents.length); 770 header.write(contents); 771 } 772 } 773 774 private void writeFolder(final DataOutput header, final SevenZArchiveEntry entry) throws IOException { 775 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 776 int numCoders = 0; 777 for (final SevenZMethodConfiguration m : getContentMethods(entry)) { 778 numCoders++; 779 writeSingleCodec(m, bos); 780 } 781 782 writeUint64(header, numCoders); 783 header.write(bos.toByteArray()); 784 for (long i = 0; i < numCoders - 1; i++) { 785 writeUint64(header, i + 1); 786 writeUint64(header, i); 787 } 788 } 789 790 private void writeHeader(final DataOutput header) throws IOException { 791 header.write(NID.kHeader); 792 793 header.write(NID.kMainStreamsInfo); 794 writeStreamsInfo(header); 795 writeFilesInfo(header); 796 header.write(NID.kEnd); 797 } 798 799 private void writePackInfo(final DataOutput header) throws IOException { 800 header.write(NID.kPackInfo); 801 802 writeUint64(header, 0); 803 writeUint64(header, 0xffffFFFFL & numNonEmptyStreams); 804 805 header.write(NID.kSize); 806 for (final SevenZArchiveEntry entry : files) { 807 if (entry.hasStream()) { 808 writeUint64(header, entry.getCompressedSize()); 809 } 810 } 811 812 header.write(NID.kCRC); 813 header.write(1); // "allAreDefined" == true 814 for (final SevenZArchiveEntry entry : files) { 815 if (entry.hasStream()) { 816 header.writeInt(Integer.reverseBytes((int) entry.getCompressedCrcValue())); 817 } 818 } 819 820 header.write(NID.kEnd); 821 } 822 823 private void writeSingleCodec(final SevenZMethodConfiguration m, final OutputStream bos) throws IOException { 824 final byte[] id = m.getMethod().getId(); 825 final byte[] properties = Coders.findByMethod(m.getMethod()) 826 .getOptionsAsProperties(m.getOptions()); 827 828 int codecFlags = id.length; 829 if (properties.length > 0) { 830 codecFlags |= 0x20; 831 } 832 bos.write(codecFlags); 833 bos.write(id); 834 835 if (properties.length > 0) { 836 bos.write(properties.length); 837 bos.write(properties); 838 } 839 } 840 841 private void writeStreamsInfo(final DataOutput header) throws IOException { 842 if (numNonEmptyStreams > 0) { 843 writePackInfo(header); 844 writeUnpackInfo(header); 845 } 846 847 writeSubStreamsInfo(header); 848 849 header.write(NID.kEnd); 850 } 851 852 private void writeSubStreamsInfo(final DataOutput header) throws IOException { 853 header.write(NID.kSubStreamsInfo); 854 // 855 // header.write(NID.kCRC); 856 // header.write(1); 857 // for (final SevenZArchiveEntry entry : files) { 858 // if (entry.getHasCrc()) { 859 // header.writeInt(Integer.reverseBytes(entry.getCrc())); 860 // } 861 // } 862 // 863 header.write(NID.kEnd); 864 } 865 866 private void writeUint64(final DataOutput header, long value) throws IOException { 867 int firstByte = 0; 868 int mask = 0x80; 869 int i; 870 for (i = 0; i < 8; i++) { 871 if (value < ((1L << ( 7 * (i + 1))))) { 872 firstByte |= (value >>> (8 * i)); 873 break; 874 } 875 firstByte |= mask; 876 mask >>>= 1; 877 } 878 header.write(firstByte); 879 for (; i > 0; i--) { 880 header.write((int) (0xff & value)); 881 value >>>= 8; 882 } 883 } 884 885 private void writeUnpackInfo(final DataOutput header) throws IOException { 886 header.write(NID.kUnpackInfo); 887 888 header.write(NID.kFolder); 889 writeUint64(header, numNonEmptyStreams); 890 header.write(0); 891 for (final SevenZArchiveEntry entry : files) { 892 if (entry.hasStream()) { 893 writeFolder(header, entry); 894 } 895 } 896 897 header.write(NID.kCodersUnpackSize); 898 for (final SevenZArchiveEntry entry : files) { 899 if (entry.hasStream()) { 900 final long[] moreSizes = additionalSizes.get(entry); 901 if (moreSizes != null) { 902 for (final long s : moreSizes) { 903 writeUint64(header, s); 904 } 905 } 906 writeUint64(header, entry.getSize()); 907 } 908 } 909 910 header.write(NID.kCRC); 911 header.write(1); // "allAreDefined" == true 912 for (final SevenZArchiveEntry entry : files) { 913 if (entry.hasStream()) { 914 header.writeInt(Integer.reverseBytes((int) entry.getCrcValue())); 915 } 916 } 917 918 header.write(NID.kEnd); 919 } 920 921}