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.io.file.attribute.FileTimes; 056import org.apache.commons.io.output.CountingOutputStream; 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 write(b, 0, b.length); 083 } 084 085 @Override 086 public void write(final byte[] b, final int off, final int len) throws IOException { 087 if (len > BUF_SIZE) { 088 channel.write(ByteBuffer.wrap(b, off, len)); 089 } else { 090 buffer.clear(); 091 buffer.put(b, off, len).flip(); 092 channel.write(buffer); 093 } 094 compressedCrc32.update(b, off, len); 095 fileBytesWritten += len; 096 } 097 098 @Override 099 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 * Opens file to write a 7z archive to. 131 * 132 * @param fileName the file to write to 133 * @throws IOException if opening the file fails 134 */ 135 public SevenZOutputFile(final File fileName) throws IOException { 136 this(fileName, null); 137 } 138 139 /** 140 * Opens file to write a 7z archive to. 141 * 142 * @param fileName the file to write to 143 * @param password optional password if the archive has to be encrypted 144 * @throws IOException if opening the file fails 145 * @since 1.23 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 * Prepares channel to write a 7z archive to. 154 * 155 * <p> 156 * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to write to an in-memory archive. 157 * </p> 158 * 159 * @param channel the channel to write to 160 * @throws IOException if the channel cannot be positioned properly 161 * @since 1.13 162 */ 163 public SevenZOutputFile(final SeekableByteChannel channel) throws IOException { 164 this(channel, null); 165 } 166 167 /** 168 * Prepares channel to write a 7z archive to. 169 * 170 * <p> 171 * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to write to an in-memory archive. 172 * </p> 173 * 174 * @param channel the channel to write to 175 * @param password optional password if the archive has to be encrypted 176 * @throws IOException if the channel cannot be positioned properly 177 * @since 1.23 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 * Closes the archive, calling {@link #finish} if necessary. 189 * 190 * @throws IOException on error 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 * Closes the archive entry. 205 * 206 * @throws IOException on error 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) { // this implies currentOutputStream != null 216 entry.setHasStream(true); 217 ++numNonEmptyStreams; 218 entry.setSize(currentOutputStream.getByteCount()); // NOSONAR 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 * Creates an archive entry using the inputFile and entryName provided. 243 * 244 * @param inputFile file to create an entry from 245 * @param entryName the name to use 246 * @return the ArchiveEntry set up with details from the file 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) { // NOSONAR 255 entry.setLastModifiedDate(new Date(inputFile.lastModified())); 256 } 257 return entry; 258 } 259 260 /** 261 * Creates an archive entry using the inputPath and entryName provided. 262 * 263 * @param inputPath path to create an entry from 264 * @param entryName the name to use 265 * @param options options indicating how symbolic links are handled. 266 * @return the ArchiveEntry set up with details from the file 267 * 268 * @throws IOException on error 269 * @since 1.21 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 * Finishes the addition of entries to this archive, without closing it. 288 * 289 * @throws IOException if archive is already closed. 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 /* version */ 311 + 4 /* start header CRC */ 312 + 8 /* next header position */ 313 + 8 /* next header length */ 314 + 4 /* next header CRC */).order(ByteOrder.LITTLE_ENDIAN); 315 // signature header 316 channel.position(0); 317 bb.put(SevenZFile.sevenZSignature); 318 // version 319 bb.put((byte) 0).put((byte) 2); 320 321 // placeholder for start header CRC 322 bb.putInt(0); 323 324 // start header 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 // prepend encryption 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 * Creation of output stream is deferred until data is actually written as some codecs might write header information even for empty streams and directories 348 * otherwise. 349 */ 350 private OutputStream getCurrentOutputStream() throws IOException { 351 if (currentOutputStream == null) { 352 currentOutputStream = setupFileOutputStream(); 353 } 354 return currentOutputStream; 355 } 356 357 /** 358 * Records an archive entry to add. 359 * 360 * The caller must then write the content to the archive and call {@link #closeArchiveEntry()} to complete the process. 361 * 362 * @param archiveEntry describes the entry 363 * @deprecated Use {@link #putArchiveEntry(SevenZArchiveEntry)}. 364 */ 365 @Deprecated 366 public void putArchiveEntry(final ArchiveEntry archiveEntry) { 367 putArchiveEntry((SevenZArchiveEntry) archiveEntry); 368 } 369 370 /** 371 * Records an archive entry to add. 372 * 373 * The caller must then write the content to the archive and call {@link #closeArchiveEntry()} to complete the process. 374 * 375 * @param archiveEntry describes the entry 376 * @since 1.25.0 377 */ 378 public void putArchiveEntry(final SevenZArchiveEntry archiveEntry) { 379 files.add(archiveEntry); 380 } 381 382 /** 383 * Sets the default compression method to use for entry contents - the default is LZMA2. 384 * 385 * <p> 386 * Currently only {@link SevenZMethod#COPY}, {@link SevenZMethod#LZMA2}, {@link SevenZMethod#BZIP2} and {@link SevenZMethod#DEFLATE} are supported. 387 * </p> 388 * 389 * <p> 390 * This is a short form for passing a single-element iterable to {@link #setContentMethods}. 391 * </p> 392 * 393 * @param method the default compression method 394 */ 395 public void setContentCompression(final SevenZMethod method) { 396 setContentMethods(Collections.singletonList(new SevenZMethodConfiguration(method))); 397 } 398 399 /** 400 * Sets the default (compression) methods to use for entry contents - the default is LZMA2. 401 * 402 * <p> 403 * Currently only {@link SevenZMethod#COPY}, {@link SevenZMethod#LZMA2}, {@link SevenZMethod#BZIP2} and {@link SevenZMethod#DEFLATE} are supported. 404 * </p> 405 * 406 * <p> 407 * The methods will be consulted in iteration order to create the final output. 408 * </p> 409 * 410 * @since 1.8 411 * @param methods the default (compression) methods 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 // doesn't need to be closed, just wraps the instance field channel 423 OutputStream out = new OutputStreamWrapper(); // NOSONAR 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 * Writes a byte array to the current archive entry. 461 * 462 * @param b The byte array to be written. 463 * @throws IOException on error 464 */ 465 public void write(final byte[] b) throws IOException { 466 write(b, 0, b.length); 467 } 468 469 /** 470 * Writes part of a byte array to the current archive entry. 471 * 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 * 486 * @param inputStream the data source. 487 * @throws IOException if an I/O error occurs. 488 * @since 1.21 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 * Writes a byte to the current archive entry. 500 * 501 * @param b The byte to be written. 502 * @throws IOException on error 503 */ 504 public void write(final int b) throws IOException { 505 getCurrentOutputStream().write(b); 506 } 507 508 /** 509 * Writes all of the given input stream to the current archive entry. 510 * 511 * @param path the data source. 512 * @param options options specifying how the file is opened. 513 * @throws IOException if an I/O error occurs. 514 * @since 1.21 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); // "allAreDefined" == true 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); // "allAreDefined" == true 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); // "allAreDefined" == true 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); // "allAreDefined" == true 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); // "allAreDefined" == true 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 // header.write(NID.kCRC); 858 // header.write(1); 859 // for (final SevenZArchiveEntry entry : files) { 860 // if (entry.getHasCrc()) { 861 // header.writeInt(Integer.reverseBytes(entry.getCrc())); 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); // "allAreDefined" == true 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}