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 * 017 */ 018package org.apache.commons.compress.archivers.sevenz; 019 020import java.io.BufferedInputStream; 021import java.io.ByteArrayInputStream; 022import java.io.Closeable; 023import java.io.DataInputStream; 024import java.io.File; 025import java.io.FilterInputStream; 026import java.io.IOException; 027import java.io.InputStream; 028import java.nio.Buffer; 029import java.nio.ByteBuffer; 030import java.nio.ByteOrder; 031import java.nio.CharBuffer; 032import java.nio.channels.SeekableByteChannel; 033import java.nio.charset.StandardCharsets; 034import java.nio.charset.CharsetEncoder; 035import java.nio.file.Files; 036import java.nio.file.StandardOpenOption; 037import java.util.ArrayList; 038import java.util.Arrays; 039import java.util.BitSet; 040import java.util.EnumSet; 041import java.util.LinkedList; 042import java.util.zip.CRC32; 043 044import org.apache.commons.compress.utils.BoundedInputStream; 045import org.apache.commons.compress.utils.CRC32VerifyingInputStream; 046import org.apache.commons.compress.utils.CharsetNames; 047import org.apache.commons.compress.utils.IOUtils; 048import org.apache.commons.compress.utils.InputStreamStatistics; 049 050/** 051 * Reads a 7z file, using SeekableByteChannel under 052 * the covers. 053 * <p> 054 * The 7z file format is a flexible container 055 * that can contain many compression and 056 * encryption types, but at the moment only 057 * only Copy, LZMA, LZMA2, BZIP2, Deflate and AES-256 + SHA-256 058 * are supported. 059 * <p> 060 * The format is very Windows/Intel specific, 061 * so it uses little-endian byte order, 062 * doesn't store user/group or permission bits, 063 * and represents times using NTFS timestamps 064 * (100 nanosecond units since 1 January 1601). 065 * Hence the official tools recommend against 066 * using it for backup purposes on *nix, and 067 * recommend .tar.7z or .tar.lzma or .tar.xz 068 * instead. 069 * <p> 070 * Both the header and file contents may be 071 * compressed and/or encrypted. With both 072 * encrypted, neither file names nor file 073 * contents can be read, but the use of 074 * encryption isn't plausibly deniable. 075 * 076 * <p>Multi volume archives can be read by concatenating the parts in 077 * correct order - either manually or by using {link 078 * org.apache.commons.compress.utils.MultiReadOnlySeekableByteChannel} 079 * for example.</p> 080 * 081 * @NotThreadSafe 082 * @since 1.6 083 */ 084public class SevenZFile implements Closeable { 085 static final int SIGNATURE_HEADER_SIZE = 32; 086 087 private static final String DEFAULT_FILE_NAME = "unknown archive"; 088 089 private final String fileName; 090 private SeekableByteChannel channel; 091 private final Archive archive; 092 private int currentEntryIndex = -1; 093 private int currentFolderIndex = -1; 094 private InputStream currentFolderInputStream = null; 095 private byte[] password; 096 private final SevenZFileOptions options; 097 098 private long compressedBytesReadFromCurrentEntry; 099 private long uncompressedBytesReadFromCurrentEntry; 100 101 private final ArrayList<InputStream> deferredBlockStreams = new ArrayList<>(); 102 103 // shared with SevenZOutputFile and tests, neither mutates it 104 static final byte[] sevenZSignature = { //NOSONAR 105 (byte)'7', (byte)'z', (byte)0xBC, (byte)0xAF, (byte)0x27, (byte)0x1C 106 }; 107 108 /** 109 * Reads a file as 7z archive 110 * 111 * @param fileName the file to read 112 * @param password optional password if the archive is encrypted 113 * @throws IOException if reading the archive fails 114 * @since 1.17 115 */ 116 public SevenZFile(final File fileName, final char[] password) throws IOException { 117 this(fileName, password, SevenZFileOptions.DEFAULT); 118 } 119 120 /** 121 * Reads a file as 7z archive with additional options. 122 * 123 * @param fileName the file to read 124 * @param password optional password if the archive is encrypted 125 * @param options the options to apply 126 * @throws IOException if reading the archive fails or the memory limit (if set) is too small 127 * @since 1.19 128 */ 129 public SevenZFile(final File fileName, final char[] password, SevenZFileOptions options) throws IOException { 130 this(Files.newByteChannel(fileName.toPath(), EnumSet.of(StandardOpenOption.READ)), 131 fileName.getAbsolutePath(), utf16Decode(password), true, options); 132 } 133 134 /** 135 * Reads a file as 7z archive 136 * 137 * @param fileName the file to read 138 * @param password optional password if the archive is encrypted - 139 * the byte array is supposed to be the UTF16-LE encoded 140 * representation of the password. 141 * @throws IOException if reading the archive fails 142 * @deprecated use the char[]-arg version for the password instead 143 */ 144 public SevenZFile(final File fileName, final byte[] password) throws IOException { 145 this(Files.newByteChannel(fileName.toPath(), EnumSet.of(StandardOpenOption.READ)), 146 fileName.getAbsolutePath(), password, true, SevenZFileOptions.DEFAULT); 147 } 148 149 /** 150 * Reads a SeekableByteChannel as 7z archive 151 * 152 * <p>{@link 153 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 154 * allows you to read from an in-memory archive.</p> 155 * 156 * @param channel the channel to read 157 * @throws IOException if reading the archive fails 158 * @since 1.13 159 */ 160 public SevenZFile(final SeekableByteChannel channel) throws IOException { 161 this(channel, SevenZFileOptions.DEFAULT); 162 } 163 164 /** 165 * Reads a SeekableByteChannel as 7z archive with addtional options. 166 * 167 * <p>{@link 168 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 169 * allows you to read from an in-memory archive.</p> 170 * 171 * @param channel the channel to read 172 * @param options the options to apply 173 * @throws IOException if reading the archive fails or the memory limit (if set) is too small 174 * @since 1.19 175 */ 176 public SevenZFile(final SeekableByteChannel channel, SevenZFileOptions options) throws IOException { 177 this(channel, DEFAULT_FILE_NAME, (char[]) null, options); 178 } 179 180 /** 181 * Reads a SeekableByteChannel as 7z archive 182 * 183 * <p>{@link 184 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 185 * allows you to read from an in-memory archive.</p> 186 * 187 * @param channel the channel to read 188 * @param password optional password if the archive is encrypted 189 * @throws IOException if reading the archive fails 190 * @since 1.17 191 */ 192 public SevenZFile(final SeekableByteChannel channel, 193 final char[] password) throws IOException { 194 this(channel, password, SevenZFileOptions.DEFAULT); 195 } 196 197 /** 198 * Reads a SeekableByteChannel as 7z archive with additional options. 199 * 200 * <p>{@link 201 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 202 * allows you to read from an in-memory archive.</p> 203 * 204 * @param channel the channel to read 205 * @param password optional password if the archive is encrypted 206 * @param options the options to apply 207 * @throws IOException if reading the archive fails or the memory limit (if set) is too small 208 * @since 1.19 209 */ 210 public SevenZFile(final SeekableByteChannel channel, final char[] password, final SevenZFileOptions options) 211 throws IOException { 212 this(channel, DEFAULT_FILE_NAME, password, options); 213 } 214 215 /** 216 * Reads a SeekableByteChannel as 7z archive 217 * 218 * <p>{@link 219 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 220 * allows you to read from an in-memory archive.</p> 221 * 222 * @param channel the channel to read 223 * @param fileName name of the archive - only used for error reporting 224 * @param password optional password if the archive is encrypted 225 * @throws IOException if reading the archive fails 226 * @since 1.17 227 */ 228 public SevenZFile(final SeekableByteChannel channel, String fileName, 229 final char[] password) throws IOException { 230 this(channel, fileName, password, SevenZFileOptions.DEFAULT); 231 } 232 233 /** 234 * Reads a SeekableByteChannel as 7z archive with addtional options. 235 * 236 * <p>{@link 237 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 238 * allows you to read from an in-memory archive.</p> 239 * 240 * @param channel the channel to read 241 * @param fileName name of the archive - only used for error reporting 242 * @param password optional password if the archive is encrypted 243 * @param options the options to apply 244 * @throws IOException if reading the archive fails or the memory limit (if set) is too small 245 * @since 1.19 246 */ 247 public SevenZFile(final SeekableByteChannel channel, String fileName, final char[] password, 248 final SevenZFileOptions options) throws IOException { 249 this(channel, fileName, utf16Decode(password), false, options); 250 } 251 252 /** 253 * Reads a SeekableByteChannel as 7z archive 254 * 255 * <p>{@link 256 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 257 * allows you to read from an in-memory archive.</p> 258 * 259 * @param channel the channel to read 260 * @param fileName name of the archive - only used for error reporting 261 * @throws IOException if reading the archive fails 262 * @since 1.17 263 */ 264 public SevenZFile(final SeekableByteChannel channel, String fileName) 265 throws IOException { 266 this(channel, fileName, SevenZFileOptions.DEFAULT); 267 } 268 269 /** 270 * Reads a SeekableByteChannel as 7z archive with additional options. 271 * 272 * <p>{@link 273 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 274 * allows you to read from an in-memory archive.</p> 275 * 276 * @param channel the channel to read 277 * @param fileName name of the archive - only used for error reporting 278 * @param options the options to apply 279 * @throws IOException if reading the archive fails or the memory limit (if set) is too small 280 * @since 1.19 281 */ 282 public SevenZFile(final SeekableByteChannel channel, String fileName, final SevenZFileOptions options) 283 throws IOException { 284 this(channel, fileName, null, false, options); 285 } 286 287 /** 288 * Reads a SeekableByteChannel as 7z archive 289 * 290 * <p>{@link 291 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 292 * allows you to read from an in-memory archive.</p> 293 * 294 * @param channel the channel to read 295 * @param password optional password if the archive is encrypted - 296 * the byte array is supposed to be the UTF16-LE encoded 297 * representation of the password. 298 * @throws IOException if reading the archive fails 299 * @since 1.13 300 * @deprecated use the char[]-arg version for the password instead 301 */ 302 public SevenZFile(final SeekableByteChannel channel, 303 final byte[] password) throws IOException { 304 this(channel, DEFAULT_FILE_NAME, password); 305 } 306 307 /** 308 * Reads a SeekableByteChannel as 7z archive 309 * 310 * <p>{@link 311 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 312 * allows you to read from an in-memory archive.</p> 313 * 314 * @param channel the channel to read 315 * @param fileName name of the archive - only used for error reporting 316 * @param password optional password if the archive is encrypted - 317 * the byte array is supposed to be the UTF16-LE encoded 318 * representation of the password. 319 * @throws IOException if reading the archive fails 320 * @since 1.13 321 * @deprecated use the char[]-arg version for the password instead 322 */ 323 public SevenZFile(final SeekableByteChannel channel, String fileName, 324 final byte[] password) throws IOException { 325 this(channel, fileName, password, false, SevenZFileOptions.DEFAULT); 326 } 327 328 private SevenZFile(final SeekableByteChannel channel, String filename, 329 final byte[] password, boolean closeOnError, SevenZFileOptions options) throws IOException { 330 boolean succeeded = false; 331 this.channel = channel; 332 this.fileName = filename; 333 this.options = options; 334 try { 335 archive = readHeaders(password); 336 if (password != null) { 337 this.password = Arrays.copyOf(password, password.length); 338 } else { 339 this.password = null; 340 } 341 succeeded = true; 342 } finally { 343 if (!succeeded && closeOnError) { 344 this.channel.close(); 345 } 346 } 347 } 348 349 /** 350 * Reads a file as unencrypted 7z archive 351 * 352 * @param fileName the file to read 353 * @throws IOException if reading the archive fails 354 */ 355 public SevenZFile(final File fileName) throws IOException { 356 this(fileName, SevenZFileOptions.DEFAULT); 357 } 358 359 /** 360 * Reads a file as unencrypted 7z archive 361 * 362 * @param fileName the file to read 363 * @param options the options to apply 364 * @throws IOException if reading the archive fails or the memory limit (if set) is too small 365 * @since 1.19 366 */ 367 public SevenZFile(final File fileName, final SevenZFileOptions options) throws IOException { 368 this(fileName, (char[]) null, options); 369 } 370 371 /** 372 * Closes the archive. 373 * @throws IOException if closing the file fails 374 */ 375 @Override 376 public void close() throws IOException { 377 if (channel != null) { 378 try { 379 channel.close(); 380 } finally { 381 channel = null; 382 if (password != null) { 383 Arrays.fill(password, (byte) 0); 384 } 385 password = null; 386 } 387 } 388 } 389 390 /** 391 * Returns the next Archive Entry in this archive. 392 * 393 * @return the next entry, 394 * or {@code null} if there are no more entries 395 * @throws IOException if the next entry could not be read 396 */ 397 public SevenZArchiveEntry getNextEntry() throws IOException { 398 if (currentEntryIndex >= archive.files.length - 1) { 399 return null; 400 } 401 ++currentEntryIndex; 402 final SevenZArchiveEntry entry = archive.files[currentEntryIndex]; 403 if (entry.getName() == null && options.getUseDefaultNameForUnnamedEntries()) { 404 entry.setName(getDefaultName()); 405 } 406 buildDecodingStream(); 407 uncompressedBytesReadFromCurrentEntry = compressedBytesReadFromCurrentEntry = 0; 408 return entry; 409 } 410 411 /** 412 * Returns meta-data of all archive entries. 413 * 414 * <p>This method only provides meta-data, the entries can not be 415 * used to read the contents, you still need to process all 416 * entries in order using {@link #getNextEntry} for that.</p> 417 * 418 * <p>The content methods are only available for entries that have 419 * already been reached via {@link #getNextEntry}.</p> 420 * 421 * @return meta-data of all archive entries. 422 * @since 1.11 423 */ 424 public Iterable<SevenZArchiveEntry> getEntries() { 425 return Arrays.asList(archive.files); 426 } 427 428 private Archive readHeaders(final byte[] password) throws IOException { 429 ByteBuffer buf = ByteBuffer.allocate(12 /* signature + 2 bytes version + 4 bytes CRC */) 430 .order(ByteOrder.LITTLE_ENDIAN); 431 readFully(buf); 432 final byte[] signature = new byte[6]; 433 buf.get(signature); 434 if (!Arrays.equals(signature, sevenZSignature)) { 435 throw new IOException("Bad 7z signature"); 436 } 437 // 7zFormat.txt has it wrong - it's first major then minor 438 final byte archiveVersionMajor = buf.get(); 439 final byte archiveVersionMinor = buf.get(); 440 if (archiveVersionMajor != 0) { 441 throw new IOException(String.format("Unsupported 7z version (%d,%d)", 442 archiveVersionMajor, archiveVersionMinor)); 443 } 444 445 final long startHeaderCrc = 0xffffFFFFL & buf.getInt(); 446 final StartHeader startHeader = readStartHeader(startHeaderCrc); 447 assertFitsIntoInt("nextHeaderSize", startHeader.nextHeaderSize); 448 final int nextHeaderSizeInt = (int) startHeader.nextHeaderSize; 449 channel.position(SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset); 450 buf = ByteBuffer.allocate(nextHeaderSizeInt).order(ByteOrder.LITTLE_ENDIAN); 451 readFully(buf); 452 final CRC32 crc = new CRC32(); 453 crc.update(buf.array()); 454 if (startHeader.nextHeaderCrc != crc.getValue()) { 455 throw new IOException("NextHeader CRC mismatch"); 456 } 457 458 Archive archive = new Archive(); 459 int nid = getUnsignedByte(buf); 460 if (nid == NID.kEncodedHeader) { 461 buf = readEncodedHeader(buf, archive, password); 462 // Archive gets rebuilt with the new header 463 archive = new Archive(); 464 nid = getUnsignedByte(buf); 465 } 466 if (nid == NID.kHeader) { 467 readHeader(buf, archive); 468 } else { 469 throw new IOException("Broken or unsupported archive: no Header"); 470 } 471 return archive; 472 } 473 474 private StartHeader readStartHeader(final long startHeaderCrc) throws IOException { 475 final StartHeader startHeader = new StartHeader(); 476 // using Stream rather than ByteBuffer for the benefit of the 477 // built-in CRC check 478 try (DataInputStream dataInputStream = new DataInputStream(new CRC32VerifyingInputStream( 479 new BoundedSeekableByteChannelInputStream(channel, 20), 20, startHeaderCrc))) { 480 startHeader.nextHeaderOffset = Long.reverseBytes(dataInputStream.readLong()); 481 startHeader.nextHeaderSize = Long.reverseBytes(dataInputStream.readLong()); 482 startHeader.nextHeaderCrc = 0xffffFFFFL & Integer.reverseBytes(dataInputStream.readInt()); 483 return startHeader; 484 } 485 } 486 487 private void readHeader(final ByteBuffer header, final Archive archive) throws IOException { 488 int nid = getUnsignedByte(header); 489 490 if (nid == NID.kArchiveProperties) { 491 readArchiveProperties(header); 492 nid = getUnsignedByte(header); 493 } 494 495 if (nid == NID.kAdditionalStreamsInfo) { 496 throw new IOException("Additional streams unsupported"); 497 //nid = header.readUnsignedByte(); 498 } 499 500 if (nid == NID.kMainStreamsInfo) { 501 readStreamsInfo(header, archive); 502 nid = getUnsignedByte(header); 503 } 504 505 if (nid == NID.kFilesInfo) { 506 readFilesInfo(header, archive); 507 nid = getUnsignedByte(header); 508 } 509 510 if (nid != NID.kEnd) { 511 throw new IOException("Badly terminated header, found " + nid); 512 } 513 } 514 515 private void readArchiveProperties(final ByteBuffer input) throws IOException { 516 // FIXME: the reference implementation just throws them away? 517 int nid = getUnsignedByte(input); 518 while (nid != NID.kEnd) { 519 final long propertySize = readUint64(input); 520 assertFitsIntoInt("propertySize", propertySize); 521 final byte[] property = new byte[(int)propertySize]; 522 input.get(property); 523 nid = getUnsignedByte(input); 524 } 525 } 526 527 private ByteBuffer readEncodedHeader(final ByteBuffer header, final Archive archive, 528 final byte[] password) throws IOException { 529 readStreamsInfo(header, archive); 530 531 // FIXME: merge with buildDecodingStream()/buildDecoderStack() at some stage? 532 final Folder folder = archive.folders[0]; 533 final int firstPackStreamIndex = 0; 534 final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos + 535 0; 536 537 channel.position(folderOffset); 538 InputStream inputStreamStack = new BoundedSeekableByteChannelInputStream(channel, 539 archive.packSizes[firstPackStreamIndex]); 540 for (final Coder coder : folder.getOrderedCoders()) { 541 if (coder.numInStreams != 1 || coder.numOutStreams != 1) { 542 throw new IOException("Multi input/output stream coders are not yet supported"); 543 } 544 inputStreamStack = Coders.addDecoder(fileName, inputStreamStack, //NOSONAR 545 folder.getUnpackSizeForCoder(coder), coder, password, options.getMaxMemoryLimitInKb()); 546 } 547 if (folder.hasCrc) { 548 inputStreamStack = new CRC32VerifyingInputStream(inputStreamStack, 549 folder.getUnpackSize(), folder.crc); 550 } 551 assertFitsIntoInt("unpackSize", folder.getUnpackSize()); 552 final byte[] nextHeader = new byte[(int)folder.getUnpackSize()]; 553 try (DataInputStream nextHeaderInputStream = new DataInputStream(inputStreamStack)) { 554 nextHeaderInputStream.readFully(nextHeader); 555 } 556 return ByteBuffer.wrap(nextHeader).order(ByteOrder.LITTLE_ENDIAN); 557 } 558 559 private void readStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException { 560 int nid = getUnsignedByte(header); 561 562 if (nid == NID.kPackInfo) { 563 readPackInfo(header, archive); 564 nid = getUnsignedByte(header); 565 } 566 567 if (nid == NID.kUnpackInfo) { 568 readUnpackInfo(header, archive); 569 nid = getUnsignedByte(header); 570 } else { 571 // archive without unpack/coders info 572 archive.folders = new Folder[0]; 573 } 574 575 if (nid == NID.kSubStreamsInfo) { 576 readSubStreamsInfo(header, archive); 577 nid = getUnsignedByte(header); 578 } 579 580 if (nid != NID.kEnd) { 581 throw new IOException("Badly terminated StreamsInfo"); 582 } 583 } 584 585 private void readPackInfo(final ByteBuffer header, final Archive archive) throws IOException { 586 archive.packPos = readUint64(header); 587 final long numPackStreams = readUint64(header); 588 assertFitsIntoInt("numPackStreams", numPackStreams); 589 final int numPackStreamsInt = (int) numPackStreams; 590 int nid = getUnsignedByte(header); 591 if (nid == NID.kSize) { 592 archive.packSizes = new long[numPackStreamsInt]; 593 for (int i = 0; i < archive.packSizes.length; i++) { 594 archive.packSizes[i] = readUint64(header); 595 } 596 nid = getUnsignedByte(header); 597 } 598 599 if (nid == NID.kCRC) { 600 archive.packCrcsDefined = readAllOrBits(header, numPackStreamsInt); 601 archive.packCrcs = new long[numPackStreamsInt]; 602 for (int i = 0; i < numPackStreamsInt; i++) { 603 if (archive.packCrcsDefined.get(i)) { 604 archive.packCrcs[i] = 0xffffFFFFL & header.getInt(); 605 } 606 } 607 608 nid = getUnsignedByte(header); 609 } 610 611 if (nid != NID.kEnd) { 612 throw new IOException("Badly terminated PackInfo (" + nid + ")"); 613 } 614 } 615 616 private void readUnpackInfo(final ByteBuffer header, final Archive archive) throws IOException { 617 int nid = getUnsignedByte(header); 618 if (nid != NID.kFolder) { 619 throw new IOException("Expected kFolder, got " + nid); 620 } 621 final long numFolders = readUint64(header); 622 assertFitsIntoInt("numFolders", numFolders); 623 final int numFoldersInt = (int) numFolders; 624 final Folder[] folders = new Folder[numFoldersInt]; 625 archive.folders = folders; 626 final int external = getUnsignedByte(header); 627 if (external != 0) { 628 throw new IOException("External unsupported"); 629 } 630 for (int i = 0; i < numFoldersInt; i++) { 631 folders[i] = readFolder(header); 632 } 633 634 nid = getUnsignedByte(header); 635 if (nid != NID.kCodersUnpackSize) { 636 throw new IOException("Expected kCodersUnpackSize, got " + nid); 637 } 638 for (final Folder folder : folders) { 639 assertFitsIntoInt("totalOutputStreams", folder.totalOutputStreams); 640 folder.unpackSizes = new long[(int)folder.totalOutputStreams]; 641 for (int i = 0; i < folder.totalOutputStreams; i++) { 642 folder.unpackSizes[i] = readUint64(header); 643 } 644 } 645 646 nid = getUnsignedByte(header); 647 if (nid == NID.kCRC) { 648 final BitSet crcsDefined = readAllOrBits(header, numFoldersInt); 649 for (int i = 0; i < numFoldersInt; i++) { 650 if (crcsDefined.get(i)) { 651 folders[i].hasCrc = true; 652 folders[i].crc = 0xffffFFFFL & header.getInt(); 653 } else { 654 folders[i].hasCrc = false; 655 } 656 } 657 658 nid = getUnsignedByte(header); 659 } 660 661 if (nid != NID.kEnd) { 662 throw new IOException("Badly terminated UnpackInfo"); 663 } 664 } 665 666 private void readSubStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException { 667 for (final Folder folder : archive.folders) { 668 folder.numUnpackSubStreams = 1; 669 } 670 int totalUnpackStreams = archive.folders.length; 671 672 int nid = getUnsignedByte(header); 673 if (nid == NID.kNumUnpackStream) { 674 totalUnpackStreams = 0; 675 for (final Folder folder : archive.folders) { 676 final long numStreams = readUint64(header); 677 assertFitsIntoInt("numStreams", numStreams); 678 folder.numUnpackSubStreams = (int)numStreams; 679 totalUnpackStreams += numStreams; 680 } 681 nid = getUnsignedByte(header); 682 } 683 684 final SubStreamsInfo subStreamsInfo = new SubStreamsInfo(); 685 subStreamsInfo.unpackSizes = new long[totalUnpackStreams]; 686 subStreamsInfo.hasCrc = new BitSet(totalUnpackStreams); 687 subStreamsInfo.crcs = new long[totalUnpackStreams]; 688 689 int nextUnpackStream = 0; 690 for (final Folder folder : archive.folders) { 691 if (folder.numUnpackSubStreams == 0) { 692 continue; 693 } 694 long sum = 0; 695 if (nid == NID.kSize) { 696 for (int i = 0; i < folder.numUnpackSubStreams - 1; i++) { 697 final long size = readUint64(header); 698 subStreamsInfo.unpackSizes[nextUnpackStream++] = size; 699 sum += size; 700 } 701 } 702 subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum; 703 } 704 if (nid == NID.kSize) { 705 nid = getUnsignedByte(header); 706 } 707 708 int numDigests = 0; 709 for (final Folder folder : archive.folders) { 710 if (folder.numUnpackSubStreams != 1 || !folder.hasCrc) { 711 numDigests += folder.numUnpackSubStreams; 712 } 713 } 714 715 if (nid == NID.kCRC) { 716 final BitSet hasMissingCrc = readAllOrBits(header, numDigests); 717 final long[] missingCrcs = new long[numDigests]; 718 for (int i = 0; i < numDigests; i++) { 719 if (hasMissingCrc.get(i)) { 720 missingCrcs[i] = 0xffffFFFFL & header.getInt(); 721 } 722 } 723 int nextCrc = 0; 724 int nextMissingCrc = 0; 725 for (final Folder folder: archive.folders) { 726 if (folder.numUnpackSubStreams == 1 && folder.hasCrc) { 727 subStreamsInfo.hasCrc.set(nextCrc, true); 728 subStreamsInfo.crcs[nextCrc] = folder.crc; 729 ++nextCrc; 730 } else { 731 for (int i = 0; i < folder.numUnpackSubStreams; i++) { 732 subStreamsInfo.hasCrc.set(nextCrc, hasMissingCrc.get(nextMissingCrc)); 733 subStreamsInfo.crcs[nextCrc] = missingCrcs[nextMissingCrc]; 734 ++nextCrc; 735 ++nextMissingCrc; 736 } 737 } 738 } 739 740 nid = getUnsignedByte(header); 741 } 742 743 if (nid != NID.kEnd) { 744 throw new IOException("Badly terminated SubStreamsInfo"); 745 } 746 747 archive.subStreamsInfo = subStreamsInfo; 748 } 749 750 private Folder readFolder(final ByteBuffer header) throws IOException { 751 final Folder folder = new Folder(); 752 753 final long numCoders = readUint64(header); 754 assertFitsIntoInt("numCoders", numCoders); 755 final Coder[] coders = new Coder[(int)numCoders]; 756 long totalInStreams = 0; 757 long totalOutStreams = 0; 758 for (int i = 0; i < coders.length; i++) { 759 coders[i] = new Coder(); 760 final int bits = getUnsignedByte(header); 761 final int idSize = bits & 0xf; 762 final boolean isSimple = (bits & 0x10) == 0; 763 final boolean hasAttributes = (bits & 0x20) != 0; 764 final boolean moreAlternativeMethods = (bits & 0x80) != 0; 765 766 coders[i].decompressionMethodId = new byte[idSize]; 767 header.get(coders[i].decompressionMethodId); 768 if (isSimple) { 769 coders[i].numInStreams = 1; 770 coders[i].numOutStreams = 1; 771 } else { 772 coders[i].numInStreams = readUint64(header); 773 coders[i].numOutStreams = readUint64(header); 774 } 775 totalInStreams += coders[i].numInStreams; 776 totalOutStreams += coders[i].numOutStreams; 777 if (hasAttributes) { 778 final long propertiesSize = readUint64(header); 779 assertFitsIntoInt("propertiesSize", propertiesSize); 780 coders[i].properties = new byte[(int)propertiesSize]; 781 header.get(coders[i].properties); 782 } 783 // would need to keep looping as above: 784 while (moreAlternativeMethods) { 785 throw new IOException("Alternative methods are unsupported, please report. " + 786 "The reference implementation doesn't support them either."); 787 } 788 } 789 folder.coders = coders; 790 assertFitsIntoInt("totalInStreams", totalInStreams); 791 folder.totalInputStreams = totalInStreams; 792 assertFitsIntoInt("totalOutStreams", totalOutStreams); 793 folder.totalOutputStreams = totalOutStreams; 794 795 if (totalOutStreams == 0) { 796 throw new IOException("Total output streams can't be 0"); 797 } 798 final long numBindPairs = totalOutStreams - 1; 799 assertFitsIntoInt("numBindPairs", numBindPairs); 800 final BindPair[] bindPairs = new BindPair[(int)numBindPairs]; 801 for (int i = 0; i < bindPairs.length; i++) { 802 bindPairs[i] = new BindPair(); 803 bindPairs[i].inIndex = readUint64(header); 804 bindPairs[i].outIndex = readUint64(header); 805 } 806 folder.bindPairs = bindPairs; 807 808 if (totalInStreams < numBindPairs) { 809 throw new IOException("Total input streams can't be less than the number of bind pairs"); 810 } 811 final long numPackedStreams = totalInStreams - numBindPairs; 812 assertFitsIntoInt("numPackedStreams", numPackedStreams); 813 final long packedStreams[] = new long[(int)numPackedStreams]; 814 if (numPackedStreams == 1) { 815 int i; 816 for (i = 0; i < (int)totalInStreams; i++) { 817 if (folder.findBindPairForInStream(i) < 0) { 818 break; 819 } 820 } 821 if (i == (int)totalInStreams) { 822 throw new IOException("Couldn't find stream's bind pair index"); 823 } 824 packedStreams[0] = i; 825 } else { 826 for (int i = 0; i < (int)numPackedStreams; i++) { 827 packedStreams[i] = readUint64(header); 828 } 829 } 830 folder.packedStreams = packedStreams; 831 832 return folder; 833 } 834 835 private BitSet readAllOrBits(final ByteBuffer header, final int size) throws IOException { 836 final int areAllDefined = getUnsignedByte(header); 837 final BitSet bits; 838 if (areAllDefined != 0) { 839 bits = new BitSet(size); 840 for (int i = 0; i < size; i++) { 841 bits.set(i, true); 842 } 843 } else { 844 bits = readBits(header, size); 845 } 846 return bits; 847 } 848 849 private BitSet readBits(final ByteBuffer header, final int size) throws IOException { 850 final BitSet bits = new BitSet(size); 851 int mask = 0; 852 int cache = 0; 853 for (int i = 0; i < size; i++) { 854 if (mask == 0) { 855 mask = 0x80; 856 cache = getUnsignedByte(header); 857 } 858 bits.set(i, (cache & mask) != 0); 859 mask >>>= 1; 860 } 861 return bits; 862 } 863 864 private void readFilesInfo(final ByteBuffer header, final Archive archive) throws IOException { 865 final long numFiles = readUint64(header); 866 assertFitsIntoInt("numFiles", numFiles); 867 final SevenZArchiveEntry[] files = new SevenZArchiveEntry[(int)numFiles]; 868 for (int i = 0; i < files.length; i++) { 869 files[i] = new SevenZArchiveEntry(); 870 } 871 BitSet isEmptyStream = null; 872 BitSet isEmptyFile = null; 873 BitSet isAnti = null; 874 while (true) { 875 final int propertyType = getUnsignedByte(header); 876 if (propertyType == 0) { 877 break; 878 } 879 final long size = readUint64(header); 880 switch (propertyType) { 881 case NID.kEmptyStream: { 882 isEmptyStream = readBits(header, files.length); 883 break; 884 } 885 case NID.kEmptyFile: { 886 if (isEmptyStream == null) { // protect against NPE 887 throw new IOException("Header format error: kEmptyStream must appear before kEmptyFile"); 888 } 889 isEmptyFile = readBits(header, isEmptyStream.cardinality()); 890 break; 891 } 892 case NID.kAnti: { 893 if (isEmptyStream == null) { // protect against NPE 894 throw new IOException("Header format error: kEmptyStream must appear before kAnti"); 895 } 896 isAnti = readBits(header, isEmptyStream.cardinality()); 897 break; 898 } 899 case NID.kName: { 900 final int external = getUnsignedByte(header); 901 if (external != 0) { 902 throw new IOException("Not implemented"); 903 } 904 if (((size - 1) & 1) != 0) { 905 throw new IOException("File names length invalid"); 906 } 907 assertFitsIntoInt("file names length", size - 1); 908 final byte[] names = new byte[(int)(size - 1)]; 909 header.get(names); 910 int nextFile = 0; 911 int nextName = 0; 912 for (int i = 0; i < names.length; i += 2) { 913 if (names[i] == 0 && names[i+1] == 0) { 914 files[nextFile++].setName(new String(names, nextName, i-nextName, CharsetNames.UTF_16LE)); 915 nextName = i + 2; 916 } 917 } 918 if (nextName != names.length || nextFile != files.length) { 919 throw new IOException("Error parsing file names"); 920 } 921 break; 922 } 923 case NID.kCTime: { 924 final BitSet timesDefined = readAllOrBits(header, files.length); 925 final int external = getUnsignedByte(header); 926 if (external != 0) { 927 throw new IOException("Unimplemented"); 928 } 929 for (int i = 0; i < files.length; i++) { 930 files[i].setHasCreationDate(timesDefined.get(i)); 931 if (files[i].getHasCreationDate()) { 932 files[i].setCreationDate(header.getLong()); 933 } 934 } 935 break; 936 } 937 case NID.kATime: { 938 final BitSet timesDefined = readAllOrBits(header, files.length); 939 final int external = getUnsignedByte(header); 940 if (external != 0) { 941 throw new IOException("Unimplemented"); 942 } 943 for (int i = 0; i < files.length; i++) { 944 files[i].setHasAccessDate(timesDefined.get(i)); 945 if (files[i].getHasAccessDate()) { 946 files[i].setAccessDate(header.getLong()); 947 } 948 } 949 break; 950 } 951 case NID.kMTime: { 952 final BitSet timesDefined = readAllOrBits(header, files.length); 953 final int external = getUnsignedByte(header); 954 if (external != 0) { 955 throw new IOException("Unimplemented"); 956 } 957 for (int i = 0; i < files.length; i++) { 958 files[i].setHasLastModifiedDate(timesDefined.get(i)); 959 if (files[i].getHasLastModifiedDate()) { 960 files[i].setLastModifiedDate(header.getLong()); 961 } 962 } 963 break; 964 } 965 case NID.kWinAttributes: { 966 final BitSet attributesDefined = readAllOrBits(header, files.length); 967 final int external = getUnsignedByte(header); 968 if (external != 0) { 969 throw new IOException("Unimplemented"); 970 } 971 for (int i = 0; i < files.length; i++) { 972 files[i].setHasWindowsAttributes(attributesDefined.get(i)); 973 if (files[i].getHasWindowsAttributes()) { 974 files[i].setWindowsAttributes(header.getInt()); 975 } 976 } 977 break; 978 } 979 case NID.kStartPos: { 980 throw new IOException("kStartPos is unsupported, please report"); 981 } 982 case NID.kDummy: { 983 // 7z 9.20 asserts the content is all zeros and ignores the property 984 // Compress up to 1.8.1 would throw an exception, now we ignore it (see COMPRESS-287 985 986 if (skipBytesFully(header, size) < size) { 987 throw new IOException("Incomplete kDummy property"); 988 } 989 break; 990 } 991 992 default: { 993 // Compress up to 1.8.1 would throw an exception, now we ignore it (see COMPRESS-287 994 if (skipBytesFully(header, size) < size) { 995 throw new IOException("Incomplete property of type " + propertyType); 996 } 997 break; 998 } 999 } 1000 } 1001 int nonEmptyFileCounter = 0; 1002 int emptyFileCounter = 0; 1003 for (int i = 0; i < files.length; i++) { 1004 files[i].setHasStream(isEmptyStream == null || !isEmptyStream.get(i)); 1005 if (files[i].hasStream()) { 1006 files[i].setDirectory(false); 1007 files[i].setAntiItem(false); 1008 files[i].setHasCrc(archive.subStreamsInfo.hasCrc.get(nonEmptyFileCounter)); 1009 files[i].setCrcValue(archive.subStreamsInfo.crcs[nonEmptyFileCounter]); 1010 files[i].setSize(archive.subStreamsInfo.unpackSizes[nonEmptyFileCounter]); 1011 ++nonEmptyFileCounter; 1012 } else { 1013 files[i].setDirectory(isEmptyFile == null || !isEmptyFile.get(emptyFileCounter)); 1014 files[i].setAntiItem(isAnti != null && isAnti.get(emptyFileCounter)); 1015 files[i].setHasCrc(false); 1016 files[i].setSize(0); 1017 ++emptyFileCounter; 1018 } 1019 } 1020 archive.files = files; 1021 calculateStreamMap(archive); 1022 } 1023 1024 private void calculateStreamMap(final Archive archive) throws IOException { 1025 final StreamMap streamMap = new StreamMap(); 1026 1027 int nextFolderPackStreamIndex = 0; 1028 final int numFolders = archive.folders != null ? archive.folders.length : 0; 1029 streamMap.folderFirstPackStreamIndex = new int[numFolders]; 1030 for (int i = 0; i < numFolders; i++) { 1031 streamMap.folderFirstPackStreamIndex[i] = nextFolderPackStreamIndex; 1032 nextFolderPackStreamIndex += archive.folders[i].packedStreams.length; 1033 } 1034 1035 long nextPackStreamOffset = 0; 1036 final int numPackSizes = archive.packSizes != null ? archive.packSizes.length : 0; 1037 streamMap.packStreamOffsets = new long[numPackSizes]; 1038 for (int i = 0; i < numPackSizes; i++) { 1039 streamMap.packStreamOffsets[i] = nextPackStreamOffset; 1040 nextPackStreamOffset += archive.packSizes[i]; 1041 } 1042 1043 streamMap.folderFirstFileIndex = new int[numFolders]; 1044 streamMap.fileFolderIndex = new int[archive.files.length]; 1045 int nextFolderIndex = 0; 1046 int nextFolderUnpackStreamIndex = 0; 1047 for (int i = 0; i < archive.files.length; i++) { 1048 if (!archive.files[i].hasStream() && nextFolderUnpackStreamIndex == 0) { 1049 streamMap.fileFolderIndex[i] = -1; 1050 continue; 1051 } 1052 if (nextFolderUnpackStreamIndex == 0) { 1053 for (; nextFolderIndex < archive.folders.length; ++nextFolderIndex) { 1054 streamMap.folderFirstFileIndex[nextFolderIndex] = i; 1055 if (archive.folders[nextFolderIndex].numUnpackSubStreams > 0) { 1056 break; 1057 } 1058 } 1059 if (nextFolderIndex >= archive.folders.length) { 1060 throw new IOException("Too few folders in archive"); 1061 } 1062 } 1063 streamMap.fileFolderIndex[i] = nextFolderIndex; 1064 if (!archive.files[i].hasStream()) { 1065 continue; 1066 } 1067 ++nextFolderUnpackStreamIndex; 1068 if (nextFolderUnpackStreamIndex >= archive.folders[nextFolderIndex].numUnpackSubStreams) { 1069 ++nextFolderIndex; 1070 nextFolderUnpackStreamIndex = 0; 1071 } 1072 } 1073 1074 archive.streamMap = streamMap; 1075 } 1076 1077 private void buildDecodingStream() throws IOException { 1078 final int folderIndex = archive.streamMap.fileFolderIndex[currentEntryIndex]; 1079 if (folderIndex < 0) { 1080 deferredBlockStreams.clear(); 1081 // TODO: previously it'd return an empty stream? 1082 // new BoundedInputStream(new ByteArrayInputStream(new byte[0]), 0); 1083 return; 1084 } 1085 final SevenZArchiveEntry file = archive.files[currentEntryIndex]; 1086 if (currentFolderIndex == folderIndex) { 1087 // (COMPRESS-320). 1088 // The current entry is within the same (potentially opened) folder. The 1089 // previous stream has to be fully decoded before we can start reading 1090 // but don't do it eagerly -- if the user skips over the entire folder nothing 1091 // is effectively decompressed. 1092 1093 file.setContentMethods(archive.files[currentEntryIndex - 1].getContentMethods()); 1094 } else { 1095 // We're opening a new folder. Discard any queued streams/ folder stream. 1096 currentFolderIndex = folderIndex; 1097 deferredBlockStreams.clear(); 1098 if (currentFolderInputStream != null) { 1099 currentFolderInputStream.close(); 1100 currentFolderInputStream = null; 1101 } 1102 1103 final Folder folder = archive.folders[folderIndex]; 1104 final int firstPackStreamIndex = archive.streamMap.folderFirstPackStreamIndex[folderIndex]; 1105 final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos + 1106 archive.streamMap.packStreamOffsets[firstPackStreamIndex]; 1107 currentFolderInputStream = buildDecoderStack(folder, folderOffset, firstPackStreamIndex, file); 1108 } 1109 1110 InputStream fileStream = new BoundedInputStream(currentFolderInputStream, file.getSize()); 1111 if (file.getHasCrc()) { 1112 fileStream = new CRC32VerifyingInputStream(fileStream, file.getSize(), file.getCrcValue()); 1113 } 1114 1115 deferredBlockStreams.add(fileStream); 1116 } 1117 1118 private InputStream buildDecoderStack(final Folder folder, final long folderOffset, 1119 final int firstPackStreamIndex, final SevenZArchiveEntry entry) throws IOException { 1120 channel.position(folderOffset); 1121 InputStream inputStreamStack = new FilterInputStream(new BufferedInputStream( 1122 new BoundedSeekableByteChannelInputStream(channel, 1123 archive.packSizes[firstPackStreamIndex]))) { 1124 @Override 1125 public int read() throws IOException { 1126 final int r = in.read(); 1127 if (r >= 0) { 1128 count(1); 1129 } 1130 return r; 1131 } 1132 @Override 1133 public int read(final byte[] b) throws IOException { 1134 return read(b, 0, b.length); 1135 } 1136 @Override 1137 public int read(final byte[] b, final int off, final int len) throws IOException { 1138 final int r = in.read(b, off, len); 1139 if (r >= 0) { 1140 count(r); 1141 } 1142 return r; 1143 } 1144 private void count(int c) { 1145 compressedBytesReadFromCurrentEntry += c; 1146 } 1147 }; 1148 final LinkedList<SevenZMethodConfiguration> methods = new LinkedList<>(); 1149 for (final Coder coder : folder.getOrderedCoders()) { 1150 if (coder.numInStreams != 1 || coder.numOutStreams != 1) { 1151 throw new IOException("Multi input/output stream coders are not yet supported"); 1152 } 1153 final SevenZMethod method = SevenZMethod.byId(coder.decompressionMethodId); 1154 inputStreamStack = Coders.addDecoder(fileName, inputStreamStack, 1155 folder.getUnpackSizeForCoder(coder), coder, password, options.getMaxMemoryLimitInKb()); 1156 methods.addFirst(new SevenZMethodConfiguration(method, 1157 Coders.findByMethod(method).getOptionsFromCoder(coder, inputStreamStack))); 1158 } 1159 entry.setContentMethods(methods); 1160 if (folder.hasCrc) { 1161 return new CRC32VerifyingInputStream(inputStreamStack, 1162 folder.getUnpackSize(), folder.crc); 1163 } 1164 return inputStreamStack; 1165 } 1166 1167 /** 1168 * Reads a byte of data. 1169 * 1170 * @return the byte read, or -1 if end of input is reached 1171 * @throws IOException 1172 * if an I/O error has occurred 1173 */ 1174 public int read() throws IOException { 1175 int b = getCurrentStream().read(); 1176 if (b >= 0) { 1177 uncompressedBytesReadFromCurrentEntry++; 1178 } 1179 return b; 1180 } 1181 1182 private InputStream getCurrentStream() throws IOException { 1183 if (archive.files[currentEntryIndex].getSize() == 0) { 1184 return new ByteArrayInputStream(new byte[0]); 1185 } 1186 if (deferredBlockStreams.isEmpty()) { 1187 throw new IllegalStateException("No current 7z entry (call getNextEntry() first)."); 1188 } 1189 1190 while (deferredBlockStreams.size() > 1) { 1191 // In solid compression mode we need to decompress all leading folder' 1192 // streams to get access to an entry. We defer this until really needed 1193 // so that entire blocks can be skipped without wasting time for decompression. 1194 try (final InputStream stream = deferredBlockStreams.remove(0)) { 1195 IOUtils.skip(stream, Long.MAX_VALUE); 1196 } 1197 compressedBytesReadFromCurrentEntry = 0; 1198 } 1199 1200 return deferredBlockStreams.get(0); 1201 } 1202 1203 /** 1204 * Reads data into an array of bytes. 1205 * 1206 * @param b the array to write data to 1207 * @return the number of bytes read, or -1 if end of input is reached 1208 * @throws IOException 1209 * if an I/O error has occurred 1210 */ 1211 public int read(final byte[] b) throws IOException { 1212 return read(b, 0, b.length); 1213 } 1214 1215 /** 1216 * Reads data into an array of bytes. 1217 * 1218 * @param b the array to write data to 1219 * @param off offset into the buffer to start filling at 1220 * @param len of bytes to read 1221 * @return the number of bytes read, or -1 if end of input is reached 1222 * @throws IOException 1223 * if an I/O error has occurred 1224 */ 1225 public int read(final byte[] b, final int off, final int len) throws IOException { 1226 int cnt = getCurrentStream().read(b, off, len); 1227 if (cnt > 0) { 1228 uncompressedBytesReadFromCurrentEntry += cnt; 1229 } 1230 return cnt; 1231 } 1232 1233 /** 1234 * Provides statistics for bytes read from the current entry. 1235 * 1236 * @return statistics for bytes read from the current entry 1237 * @since 1.17 1238 */ 1239 public InputStreamStatistics getStatisticsForCurrentEntry() { 1240 return new InputStreamStatistics() { 1241 @Override 1242 public long getCompressedCount() { 1243 return compressedBytesReadFromCurrentEntry; 1244 } 1245 @Override 1246 public long getUncompressedCount() { 1247 return uncompressedBytesReadFromCurrentEntry; 1248 } 1249 }; 1250 } 1251 1252 private static long readUint64(final ByteBuffer in) throws IOException { 1253 // long rather than int as it might get shifted beyond the range of an int 1254 final long firstByte = getUnsignedByte(in); 1255 int mask = 0x80; 1256 long value = 0; 1257 for (int i = 0; i < 8; i++) { 1258 if ((firstByte & mask) == 0) { 1259 return value | ((firstByte & (mask - 1)) << (8 * i)); 1260 } 1261 final long nextByte = getUnsignedByte(in); 1262 value |= nextByte << (8 * i); 1263 mask >>>= 1; 1264 } 1265 return value; 1266 } 1267 1268 private static int getUnsignedByte(ByteBuffer buf) { 1269 return buf.get() & 0xff; 1270 } 1271 1272 /** 1273 * Checks if the signature matches what is expected for a 7z file. 1274 * 1275 * @param signature 1276 * the bytes to check 1277 * @param length 1278 * the number of bytes to check 1279 * @return true, if this is the signature of a 7z archive. 1280 * @since 1.8 1281 */ 1282 public static boolean matches(final byte[] signature, final int length) { 1283 if (length < sevenZSignature.length) { 1284 return false; 1285 } 1286 1287 for (int i = 0; i < sevenZSignature.length; i++) { 1288 if (signature[i] != sevenZSignature[i]) { 1289 return false; 1290 } 1291 } 1292 return true; 1293 } 1294 1295 private static long skipBytesFully(final ByteBuffer input, long bytesToSkip) throws IOException { 1296 if (bytesToSkip < 1) { 1297 return 0; 1298 } 1299 int current = input.position(); 1300 int maxSkip = input.remaining(); 1301 if (maxSkip < bytesToSkip) { 1302 bytesToSkip = maxSkip; 1303 } 1304 input.position(current + (int) bytesToSkip); 1305 return bytesToSkip; 1306 } 1307 1308 private void readFully(ByteBuffer buf) throws IOException { 1309 ((Buffer)buf).rewind(); 1310 IOUtils.readFully(channel, buf); 1311 ((Buffer)buf).flip(); 1312 } 1313 1314 @Override 1315 public String toString() { 1316 return archive.toString(); 1317 } 1318 1319 /** 1320 * Derives a default file name from the archive name - if known. 1321 * 1322 * <p>This implements the same heuristics the 7z tools use. In 1323 * 7z's case if an archive contains entries without a name - 1324 * i.e. {@link SevenZArchiveEntry#getName} returns {@code null} - 1325 * then its command line and GUI tools will use this default name 1326 * when extracting the entries.</p> 1327 * 1328 * @return null if the name of the archive is unknown. Otherwise 1329 * if the name of the archive has got any extension, it is 1330 * stripped and the remainder returned. Finally if the name of the 1331 * archive hasn't got any extension then a {@code ~} character is 1332 * appended to the archive name. 1333 * 1334 * @since 1.19 1335 */ 1336 public String getDefaultName() { 1337 if (DEFAULT_FILE_NAME.equals(fileName) || fileName == null) { 1338 return null; 1339 } 1340 1341 final String lastSegment = new File(fileName).getName(); 1342 final int dotPos = lastSegment.lastIndexOf("."); 1343 if (dotPos > 0) { // if the file starts with a dot then this is not an extension 1344 return lastSegment.substring(0, dotPos); 1345 } 1346 return lastSegment + "~"; 1347 } 1348 1349 private static final CharsetEncoder PASSWORD_ENCODER = StandardCharsets.UTF_16LE.newEncoder(); 1350 1351 private static byte[] utf16Decode(char[] chars) throws IOException { 1352 if (chars == null) { 1353 return null; 1354 } 1355 ByteBuffer encoded = PASSWORD_ENCODER.encode(CharBuffer.wrap(chars)); 1356 if (encoded.hasArray()) { 1357 return encoded.array(); 1358 } 1359 byte[] e = new byte[encoded.remaining()]; 1360 encoded.get(e); 1361 return e; 1362 } 1363 1364 private static void assertFitsIntoInt(String what, long value) throws IOException { 1365 if (value > Integer.MAX_VALUE) { 1366 throw new IOException("Cannot handle " + what + value); 1367 } 1368 } 1369}