001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.compress.archivers.sevenz; 020 021import static java.nio.charset.StandardCharsets.UTF_16LE; 022 023import java.io.BufferedInputStream; 024import java.io.ByteArrayOutputStream; 025import java.io.Closeable; 026import java.io.DataOutput; 027import java.io.DataOutputStream; 028import java.io.File; 029import java.io.IOException; 030import java.io.InputStream; 031import java.io.OutputStream; 032import java.nio.ByteBuffer; 033import java.nio.ByteOrder; 034import java.nio.channels.SeekableByteChannel; 035import java.nio.file.Files; 036import java.nio.file.LinkOption; 037import java.nio.file.OpenOption; 038import java.nio.file.Path; 039import java.nio.file.StandardOpenOption; 040import java.nio.file.attribute.BasicFileAttributes; 041import java.util.ArrayList; 042import java.util.Arrays; 043import java.util.BitSet; 044import java.util.Collections; 045import java.util.Date; 046import java.util.EnumSet; 047import java.util.HashMap; 048import java.util.LinkedList; 049import java.util.List; 050import java.util.Map; 051import java.util.stream.Collectors; 052import java.util.stream.Stream; 053import java.util.stream.StreamSupport; 054import java.util.zip.CRC32; 055 056import org.apache.commons.compress.archivers.ArchiveEntry; 057import org.apache.commons.io.file.attribute.FileTimes; 058import org.apache.commons.io.output.CountingOutputStream; 059 060/** 061 * Writes a 7z file. 062 * 063 * @since 1.6 064 */ 065public class SevenZOutputFile implements Closeable { 066 067 private final class OutputStreamWrapper extends OutputStream { 068 069 private static final int BUF_SIZE = 8192; 070 private final ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); 071 072 @Override 073 public void close() throws IOException { 074 // the file will be closed by the containing class's close method 075 } 076 077 @Override 078 public void flush() throws IOException { 079 // no reason to flush the channel 080 } 081 082 @Override 083 public void write(final byte[] b) throws IOException { 084 write(b, 0, b.length); 085 } 086 087 @Override 088 public void write(final byte[] b, final int off, final int len) throws IOException { 089 if (len > BUF_SIZE) { 090 channel.write(ByteBuffer.wrap(b, off, len)); 091 } else { 092 buffer.clear(); 093 buffer.put(b, off, len).flip(); 094 channel.write(buffer); 095 } 096 compressedCrc32.update(b, off, len); 097 fileBytesWritten += len; 098 } 099 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 * 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(Files.newByteChannel(fileName.toPath(), EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)), 151 password); 152 } 153 154 /** 155 * Prepares channel to write a 7z archive to. 156 * 157 * <p> 158 * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to write to an in-memory archive. 159 * </p> 160 * 161 * @param channel the channel to write to 162 * @throws IOException if the channel cannot be positioned properly 163 * @since 1.13 164 */ 165 public SevenZOutputFile(final SeekableByteChannel channel) throws IOException { 166 this(channel, null); 167 } 168 169 /** 170 * Prepares channel to write a 7z archive to. 171 * 172 * <p> 173 * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to write to an in-memory archive. 174 * </p> 175 * 176 * @param channel the channel to write to 177 * @param password optional password if the archive has to be encrypted 178 * @throws IOException if the channel cannot be positioned properly 179 * @since 1.23 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 * Closes the archive, calling {@link #finish} if necessary. 191 * 192 * @throws IOException on error 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 * Closes the archive entry. 207 * 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.getByteCount()); // 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].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 * Creates 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, 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) { // NOSONAR 257 entry.setLastModifiedDate(new Date(inputFile.lastModified())); 258 } 259 return entry; 260 } 261 262 /** 263 * Creates an archive entry using the inputPath and entryName provided. 264 * 265 * @param inputPath path to create an entry from 266 * @param entryName the name to use 267 * @param options options indicating how symbolic links are handled. 268 * @return the ArchiveEntry set up with details from the file 269 * @throws IOException on error 270 * @since 1.21 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 * Finishes the addition of entries to this archive, without closing it. 289 * 290 * @throws IOException if archive is already closed. 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 /* version */ 312 + 4 /* start header CRC */ 313 + 8 /* next header position */ 314 + 8 /* next header length */ 315 + 4 /* next header CRC */).order(ByteOrder.LITTLE_ENDIAN); 316 // signature header 317 channel.position(0); 318 bb.put(SevenZFile.sevenZSignature); 319 // version 320 bb.put((byte) 0).put((byte) 2); 321 322 // placeholder for start header CRC 323 bb.putInt(0); 324 325 // start header 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 // prepend encryption 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 * Creation of output stream is deferred until data is actually written as some codecs might write header information even for empty streams and directories 349 * otherwise. 350 */ 351 private OutputStream getCurrentOutputStream() throws IOException { 352 if (currentOutputStream == null) { 353 currentOutputStream = setupFileOutputStream(); 354 } 355 return currentOutputStream; 356 } 357 358 /** 359 * Records an archive entry to add. 360 * 361 * The caller must then write the content to the archive and call {@link #closeArchiveEntry()} to complete the process. 362 * 363 * @param archiveEntry describes the entry 364 * @deprecated Use {@link #putArchiveEntry(SevenZArchiveEntry)}. 365 */ 366 @Deprecated 367 public void putArchiveEntry(final ArchiveEntry archiveEntry) { 368 putArchiveEntry((SevenZArchiveEntry) archiveEntry); 369 } 370 371 /** 372 * Records an archive entry to add. 373 * 374 * The caller must then write the content to the archive and call {@link #closeArchiveEntry()} to complete the process. 375 * 376 * @param archiveEntry describes the entry 377 * @since 1.25.0 378 */ 379 public void putArchiveEntry(final SevenZArchiveEntry archiveEntry) { 380 files.add(archiveEntry); 381 } 382 383 /** 384 * Sets the default compression method to use for entry contents - the default is LZMA2. 385 * 386 * <p> 387 * Currently only {@link SevenZMethod#COPY}, {@link SevenZMethod#LZMA2}, {@link SevenZMethod#BZIP2} and {@link SevenZMethod#DEFLATE} are supported. 388 * </p> 389 * 390 * <p> 391 * This is a short form for passing a single-element iterable to {@link #setContentMethods}. 392 * </p> 393 * 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 default is LZMA2. 402 * 403 * <p> 404 * Currently only {@link SevenZMethod#COPY}, {@link SevenZMethod#LZMA2}, {@link SevenZMethod#BZIP2} and {@link SevenZMethod#DEFLATE} are supported. 405 * </p> 406 * 407 * <p> 408 * The methods will be consulted in iteration order to create the final output. 409 * </p> 410 * 411 * @param methods the default (compression) methods 412 * @since 1.8 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) 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 * Writes a byte array to the current archive entry. 462 * 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 * 473 * @param b The byte array to be written. 474 * @param off offset into the array to start writing from 475 * @param len number of bytes to write 476 * @throws IOException on error 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 * Writes all of the given input stream to the current archive entry. 486 * 487 * @param inputStream the data source. 488 * @throws IOException if an I/O error occurs. 489 * @since 1.21 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 * Writes a byte to the current archive entry. 501 * 502 * @param b The byte to be written. 503 * @throws IOException on error 504 */ 505 public void write(final int b) throws IOException { 506 getCurrentOutputStream().write(b); 507 } 508 509 /** 510 * Writes all of the given input stream to the current archive entry. 511 * 512 * @param path the data source. 513 * @param options options specifying how the file is opened. 514 * @throws IOException if an I/O error occurs. 515 * @since 1.21 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); // "allAreDefined" == true 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); // "allAreDefined" == true 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); // "allAreDefined" == true 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); // "allAreDefined" == true 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); // "allAreDefined" == true 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 // header.write(NID.kCRC); 859 // header.write(1); 860 // for (final SevenZArchiveEntry entry : files) { 861 // if (entry.getHasCrc()) { 862 // header.writeInt(Integer.reverseBytes(entry.getCrc())); 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); // "allAreDefined" == true 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}