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