You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2022/12/11 08:02:49 UTC
[commons-compress] 01/05: Sort members
This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-compress.git
commit f5a37de3a4008cb00a49b9e92162836ff236f65e
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun Dec 11 01:36:45 2022 -0500
Sort members
---
.../commons/compress/MemoryLimitException.java | 16 +-
.../commons/compress/archivers/ArchiveEntry.java | 22 +-
.../compress/archivers/ArchiveInputStream.java | 88 +-
.../compress/archivers/ArchiveOutputStream.java | 118 +-
.../compress/archivers/ArchiveStreamFactory.java | 298 +-
.../apache/commons/compress/archivers/Lister.java | 100 +-
.../compress/archivers/ar/ArArchiveEntry.java | 108 +-
.../archivers/ar/ArArchiveInputStream.java | 364 +--
.../archivers/ar/ArArchiveOutputStream.java | 154 +-
.../compress/archivers/arj/ArjArchiveEntry.java | 140 +-
.../archivers/arj/ArjArchiveInputStream.java | 314 +-
.../compress/archivers/arj/LocalFileHeader.java | 126 +-
.../commons/compress/archivers/arj/MainHeader.java | 44 +-
.../compress/archivers/cpio/CpioArchiveEntry.java | 390 +--
.../archivers/cpio/CpioArchiveInputStream.java | 222 +-
.../archivers/cpio/CpioArchiveOutputStream.java | 466 +--
.../commons/compress/archivers/cpio/CpioUtil.java | 14 +-
.../commons/compress/archivers/dump/Dirent.java | 22 +-
.../archivers/dump/DumpArchiveConstants.java | 72 +-
.../compress/archivers/dump/DumpArchiveEntry.java | 782 ++---
.../archivers/dump/DumpArchiveException.java | 6 +-
.../archivers/dump/DumpArchiveInputStream.java | 330 +--
.../archivers/dump/DumpArchiveSummary.java | 248 +-
.../compress/archivers/dump/DumpArchiveUtil.java | 76 +-
.../compress/archivers/dump/TapeInputStream.java | 290 +-
.../compress/archivers/examples/Archiver.java | 18 +-
.../examples/CloseableConsumerAdapter.java | 10 +-
.../compress/archivers/jar/JarArchiveEntry.java | 30 +-
.../archivers/jar/JarArchiveInputStream.java | 34 +-
.../archivers/sevenz/AES256SHA256Decoder.java | 144 +-
.../commons/compress/archivers/sevenz/Archive.java | 16 +-
.../BoundedSeekableByteChannelInputStream.java | 10 +-
.../commons/compress/archivers/sevenz/CLI.java | 46 +-
.../commons/compress/archivers/sevenz/Coders.java | 228 +-
.../compress/archivers/sevenz/DeltaDecoder.java | 10 +-
.../commons/compress/archivers/sevenz/Folder.java | 46 +-
.../compress/archivers/sevenz/LZMA2Decoder.java | 46 +-
.../compress/archivers/sevenz/LZMADecoder.java | 26 +-
.../compress/archivers/sevenz/SevenZFile.java | 3036 ++++++++++----------
.../archivers/sevenz/SevenZFileOptions.java | 170 +-
.../compress/archivers/sevenz/SevenZMethod.java | 18 +-
.../sevenz/SevenZMethodConfiguration.java | 26 +-
.../archivers/sevenz/SevenZOutputFile.java | 872 +++---
.../archivers/tar/TarArchiveInputStream.java | 766 ++---
.../archivers/tar/TarArchiveOutputStream.java | 726 ++---
.../archivers/tar/TarArchiveSparseEntry.java | 8 +-
.../archivers/tar/TarArchiveStructSparse.java | 16 +-
.../commons/compress/archivers/tar/TarFile.java | 766 ++---
.../commons/compress/archivers/tar/TarUtils.java | 1004 +++----
.../archivers/zip/AbstractUnicodeExtraField.java | 124 +-
.../compress/archivers/zip/AsiExtraField.java | 234 +-
.../commons/compress/archivers/zip/BinaryTree.java | 138 +-
.../compress/archivers/zip/CircularBuffer.java | 30 +-
.../archivers/zip/ExplodingInputStream.java | 110 +-
.../compress/archivers/zip/ExtraFieldUtils.java | 474 +--
.../compress/archivers/zip/GeneralPurposeBit.java | 214 +-
.../zip/InflaterInputStreamWithStatistics.java | 20 +-
.../commons/compress/archivers/zip/JarMarker.java | 64 +-
.../compress/archivers/zip/NioZipEncoding.java | 154 +-
.../compress/archivers/zip/PKWareExtraHeader.java | 322 +--
.../archivers/zip/ParallelScatterZipCreator.java | 120 +-
.../archivers/zip/ResourceAlignmentExtraField.java | 44 +-
.../archivers/zip/ScatterZipOutputStream.java | 180 +-
.../compress/archivers/zip/StreamCompressor.java | 328 +--
.../archivers/zip/UnicodeCommentExtraField.java | 22 +-
.../archivers/zip/UnicodePathExtraField.java | 18 +-
.../archivers/zip/UnparseableExtraFieldData.java | 58 +-
.../archivers/zip/UnrecognizedExtraField.java | 122 +-
.../archivers/zip/UnshrinkingInputStream.java | 30 +-
.../zip/UnsupportedZipFeatureException.java | 122 +-
.../archivers/zip/X0015_CertificateIdForFile.java | 20 +-
.../X0016_CertificateIdForCentralDirectory.java | 20 +-
.../zip/X0017_StrongEncryptionHeader.java | 58 +-
.../archivers/zip/X5455_ExtendedTimestamp.java | 524 ++--
.../compress/archivers/zip/X7875_NewUnix.java | 318 +-
.../zip/Zip64ExtendedInformationExtraField.java | 192 +-
.../archivers/zip/Zip64RequiredException.java | 14 +-
.../compress/archivers/zip/ZipArchiveEntry.java | 1518 +++++-----
.../archivers/zip/ZipArchiveEntryRequest.java | 30 +-
.../archivers/zip/ZipArchiveInputStream.java | 1834 ++++++------
.../archivers/zip/ZipArchiveOutputStream.java | 2538 ++++++++--------
.../archivers/zip/ZipEightByteInteger.java | 156 +-
.../compress/archivers/zip/ZipEncoding.java | 14 +-
.../compress/archivers/zip/ZipEncodingHelper.java | 20 +-
.../compress/archivers/zip/ZipExtraField.java | 38 +-
.../commons/compress/archivers/zip/ZipLong.java | 154 +-
.../commons/compress/archivers/zip/ZipMethod.java | 28 +-
.../commons/compress/archivers/zip/ZipShort.java | 122 +-
.../archivers/zip/ZipSplitOutputStream.java | 210 +-
.../zip/ZipSplitReadOnlySeekableByteChannel.java | 256 +-
.../commons/compress/archivers/zip/ZipUtil.java | 364 +--
.../apache/commons/compress/changes/Change.java | 48 +-
.../apache/commons/compress/changes/ChangeSet.java | 40 +-
.../compress/changes/ChangeSetPerformer.java | 242 +-
.../commons/compress/changes/ChangeSetResults.java | 18 +-
.../compressors/CompressorInputStream.java | 30 +-
.../compressors/CompressorStreamFactory.java | 200 +-
.../commons/compress/compressors/FileNameUtil.java | 48 +-
.../brotli/BrotliCompressorInputStream.java | 32 +-
.../compress/compressors/brotli/BrotliUtils.java | 28 +-
.../bzip2/BZip2CompressorInputStream.java | 1122 ++++----
.../bzip2/BZip2CompressorOutputStream.java | 792 ++---
.../compress/compressors/bzip2/BZip2Utils.java | 34 +-
.../compress/compressors/bzip2/BlockSort.java | 624 ++--
.../commons/compress/compressors/bzip2/CRC.java | 12 +-
.../deflate/DeflateCompressorInputStream.java | 88 +-
.../deflate/DeflateCompressorOutputStream.java | 36 +-
.../compressors/deflate/DeflateParameters.java | 42 +-
.../deflate64/Deflate64CompressorInputStream.java | 64 +-
.../compressors/deflate64/HuffmanDecoder.java | 728 ++---
.../gzip/GzipCompressorInputStream.java | 96 +-
.../gzip/GzipCompressorOutputStream.java | 154 +-
.../compress/compressors/gzip/GzipParameters.java | 102 +-
.../compress/compressors/gzip/GzipUtils.java | 36 +-
.../lz4/BlockLZ4CompressorInputStream.java | 94 +-
.../lz4/BlockLZ4CompressorOutputStream.java | 432 +--
.../lz4/FramedLZ4CompressorInputStream.java | 308 +-
.../lz4/FramedLZ4CompressorOutputStream.java | 196 +-
.../commons/compress/compressors/lz4/XXHash32.java | 104 +-
.../AbstractLZ77CompressorInputStream.java | 152 +-
.../compressors/lz77support/LZ77Compressor.java | 452 +--
.../compressors/lz77support/Parameters.java | 322 +--
.../lzma/LZMACompressorInputStream.java | 74 +-
.../lzma/LZMACompressorOutputStream.java | 30 +-
.../compress/compressors/lzma/LZMAUtils.java | 100 +-
.../compress/compressors/lzw/LZWInputStream.java | 284 +-
.../pack200/Pack200CompressorInputStream.java | 248 +-
.../pack200/Pack200CompressorOutputStream.java | 50 +-
.../compress/compressors/pack200/Pack200Utils.java | 46 +-
.../compress/compressors/pack200/StreamBridge.java | 8 +-
.../snappy/FramedSnappyCompressorInputStream.java | 224 +-
.../snappy/FramedSnappyCompressorOutputStream.java | 62 +-
.../compressors/snappy/PureJavaCrc32C.java | 134 +-
.../snappy/SnappyCompressorInputStream.java | 92 +-
.../snappy/SnappyCompressorOutputStream.java | 208 +-
.../compressors/xz/XZCompressorInputStream.java | 42 +-
.../compressors/xz/XZCompressorOutputStream.java | 28 +-
.../commons/compress/compressors/xz/XZUtils.java | 124 +-
.../compressors/z/ZCompressorInputStream.java | 100 +-
.../zstandard/ZstdCompressorInputStream.java | 32 +-
.../zstandard/ZstdCompressorOutputStream.java | 56 +-
.../compress/compressors/zstandard/ZstdUtils.java | 56 +-
.../harmony/archive/internal/nls/Messages.java | 144 +-
.../commons/compress/harmony/pack200/Archive.java | 348 +--
.../harmony/pack200/AttributeDefinitionBands.java | 162 +-
.../compress/harmony/pack200/BHSDCodec.java | 192 +-
.../commons/compress/harmony/pack200/BandSet.java | 742 ++---
.../commons/compress/harmony/pack200/BcBands.java | 84 +-
.../commons/compress/harmony/pack200/CPClass.java | 10 +-
.../compress/harmony/pack200/CPMethodOrField.java | 26 +-
.../compress/harmony/pack200/CPNameAndType.java | 18 +-
.../compress/harmony/pack200/CPSignature.java | 18 +-
.../commons/compress/harmony/pack200/CPString.java | 8 +-
.../commons/compress/harmony/pack200/CPUTF8.java | 6 +-
.../compress/harmony/pack200/ClassBands.java | 2174 +++++++-------
.../commons/compress/harmony/pack200/Codec.java | 38 +-
.../compress/harmony/pack200/CodecEncoding.java | 12 +-
.../commons/compress/harmony/pack200/CpBands.java | 786 ++---
.../compress/harmony/pack200/FileBands.java | 30 +-
.../commons/compress/harmony/pack200/IcBands.java | 190 +-
.../commons/compress/harmony/pack200/IntList.java | 14 +-
.../harmony/pack200/MetadataBandGroup.java | 334 +--
.../compress/harmony/pack200/NewAttribute.java | 186 +-
.../harmony/pack200/NewAttributeBands.java | 1036 +++----
.../compress/harmony/pack200/Pack200Adapter.java | 24 +-
.../harmony/pack200/Pack200ClassReader.java | 36 +-
.../harmony/pack200/Pack200PackerAdapter.java | 62 +-
.../compress/harmony/pack200/PackingUtils.java | 104 +-
.../compress/harmony/pack200/PopulationCodec.java | 28 +-
.../commons/compress/harmony/pack200/RunCodec.java | 70 +-
.../commons/compress/harmony/pack200/Segment.java | 894 +++---
.../compress/harmony/pack200/SegmentHeader.java | 312 +-
.../compress/harmony/unpack200/Archive.java | 104 +-
.../harmony/unpack200/AttrDefinitionBands.java | 14 +-
.../harmony/unpack200/AttributeLayout.java | 42 +-
.../compress/harmony/unpack200/BandSet.java | 388 +--
.../compress/harmony/unpack200/BcBands.java | 192 +-
.../compress/harmony/unpack200/ClassBands.java | 1120 ++++----
.../compress/harmony/unpack200/CpBands.java | 514 ++--
.../compress/harmony/unpack200/FileBands.java | 74 +-
.../compress/harmony/unpack200/IcBands.java | 168 +-
.../compress/harmony/unpack200/IcTuple.java | 332 +--
.../harmony/unpack200/MetadataBandGroup.java | 92 +-
.../harmony/unpack200/NewAttributeBands.java | 1226 ++++----
.../harmony/unpack200/Pack200UnpackerAdapter.java | 32 +-
.../compress/harmony/unpack200/Segment.java | 160 +-
.../harmony/unpack200/SegmentConstantPool.java | 236 +-
.../unpack200/SegmentConstantPoolArrayCache.java | 142 +-
.../compress/harmony/unpack200/SegmentHeader.java | 250 +-
.../compress/harmony/unpack200/SegmentUtils.java | 28 +-
.../bytecode/AnnotationDefaultAttribute.java | 36 +-
.../unpack200/bytecode/AnnotationsAttribute.java | 76 +-
.../harmony/unpack200/bytecode/Attribute.java | 10 +-
.../unpack200/bytecode/BCIRenumberedAttribute.java | 30 +-
.../harmony/unpack200/bytecode/ByteCode.java | 246 +-
.../harmony/unpack200/bytecode/CPClass.java | 24 +-
.../harmony/unpack200/bytecode/CPConstant.java | 8 +-
.../harmony/unpack200/bytecode/CPDouble.java | 8 +-
.../harmony/unpack200/bytecode/CPFieldRef.java | 82 +-
.../harmony/unpack200/bytecode/CPFloat.java | 8 +-
.../harmony/unpack200/bytecode/CPInteger.java | 8 +-
.../unpack200/bytecode/CPInterfaceMethodRef.java | 28 +-
.../harmony/unpack200/bytecode/CPLong.java | 8 +-
.../harmony/unpack200/bytecode/CPMember.java | 74 +-
.../harmony/unpack200/bytecode/CPMethod.java | 18 +-
.../harmony/unpack200/bytecode/CPMethodRef.java | 18 +-
.../harmony/unpack200/bytecode/CPNameAndType.java | 86 +-
.../compress/harmony/unpack200/bytecode/CPRef.java | 4 +-
.../harmony/unpack200/bytecode/CPString.java | 46 +-
.../harmony/unpack200/bytecode/CPUTF8.java | 30 +-
.../unpack200/bytecode/ClassConstantPool.java | 62 +-
.../harmony/unpack200/bytecode/ClassFileEntry.java | 8 +-
.../harmony/unpack200/bytecode/CodeAttribute.java | 52 +-
.../unpack200/bytecode/ConstantPoolEntry.java | 20 +-
.../unpack200/bytecode/ConstantValueAttribute.java | 8 +-
.../unpack200/bytecode/DeprecatedAttribute.java | 12 +-
.../bytecode/EnclosingMethodAttribute.java | 40 +-
.../unpack200/bytecode/ExceptionTableEntry.java | 18 +-
.../unpack200/bytecode/ExceptionsAttribute.java | 8 +-
.../unpack200/bytecode/InnerClassesAttribute.java | 60 +-
.../bytecode/LineNumberTableAttribute.java | 52 +-
.../bytecode/LocalVariableTableAttribute.java | 78 +-
.../bytecode/LocalVariableTypeTableAttribute.java | 68 +-
.../harmony/unpack200/bytecode/NewAttribute.java | 244 +-
.../harmony/unpack200/bytecode/OperandManager.java | 122 +-
...timeVisibleorInvisibleAnnotationsAttribute.java | 30 +-
...leorInvisibleParameterAnnotationsAttribute.java | 106 +-
.../unpack200/bytecode/SignatureAttribute.java | 22 +-
.../unpack200/bytecode/SourceFileAttribute.java | 26 +-
.../unpack200/bytecode/forms/ByteCodeForm.java | 68 +-
.../unpack200/bytecode/forms/ClassRefForm.java | 20 +-
.../bytecode/forms/ClassSpecificReferenceForm.java | 4 +-
.../bytecode/forms/InitMethodReferenceForm.java | 8 +-
.../bytecode/forms/NarrowClassRefForm.java | 10 +-
.../unpack200/bytecode/forms/ReferenceForm.java | 22 +-
.../bytecode/forms/SingleByteReferenceForm.java | 10 +-
.../bytecode/forms/SuperFieldRefForm.java | 10 +-
.../bytecode/forms/SuperMethodRefForm.java | 10 +-
.../unpack200/bytecode/forms/ThisFieldRefForm.java | 10 +-
.../bytecode/forms/ThisMethodRefForm.java | 10 +-
.../bytecode/forms/VariableInstructionForm.java | 74 +-
.../FileBasedScatterGatherBackingStore.java | 20 +-
.../parallel/ScatterGatherBackingStore.java | 12 +-
.../commons/compress/utils/ArchiveUtils.java | 242 +-
.../commons/compress/utils/BitInputStream.java | 134 +-
.../commons/compress/utils/BoundedInputStream.java | 28 +-
.../apache/commons/compress/utils/ByteUtils.java | 172 +-
.../apache/commons/compress/utils/Charsets.java | 52 +-
.../utils/ChecksumCalculatingInputStream.java | 16 +-
.../compress/utils/CountingInputStream.java | 40 +-
.../compress/utils/CountingOutputStream.java | 32 +-
.../apache/commons/compress/utils/ExactMath.java | 8 +-
.../utils/FixedLengthBlockOutputStream.java | 240 +-
.../org/apache/commons/compress/utils/IOUtils.java | 268 +-
.../utils/MultiReadOnlySeekableByteChannel.java | 236 +-
.../utils/SeekableInMemoryByteChannel.java | 134 +-
.../org/apache/commons/compress/utils/Sets.java | 8 +-
.../apache/commons/compress/AbstractTestCase.java | 312 +-
.../apache/commons/compress/ArchiveReadTest.java | 18 +-
.../apache/commons/compress/ArchiveUtilsTest.java | 98 +-
.../apache/commons/compress/ChainingTestCase.java | 12 +-
.../commons/compress/DetectArchiverTestCase.java | 56 +-
.../org/apache/commons/compress/IOMethodsTest.java | 174 +-
.../org/apache/commons/compress/OsgiITest.java | 24 +-
.../commons/compress/archivers/ArTestCase.java | 194 +-
.../archivers/ArchiveOutputStreamTest.java | 188 +-
.../archivers/ArchiveStreamFactoryTest.java | 438 +--
.../commons/compress/archivers/CpioTestCase.java | 14 +-
.../commons/compress/archivers/DumpTestCase.java | 86 +-
.../compress/archivers/ExceptionMessageTest.java | 20 +-
.../commons/compress/archivers/LongPathTest.java | 16 +-
.../compress/archivers/LongSymLinkTest.java | 16 +-
.../commons/compress/archivers/SevenZTestCase.java | 222 +-
.../commons/compress/archivers/TarTestCase.java | 528 ++--
.../commons/compress/archivers/ZipTestCase.java | 1176 ++++----
.../archivers/ar/ArArchiveInputStreamTest.java | 46 +-
.../archivers/arj/ArjArchiveInputStreamTest.java | 46 +-
.../compress/archivers/arj/CoverageTest.java | 12 +-
.../archivers/dump/DumpArchiveInputStreamTest.java | 58 +-
.../archivers/dump/DumpArchiveUtilTest.java | 14 +-
.../compress/archivers/examples/ExpanderTest.java | 206 +-
.../examples/ParameterizedArchiverTest.java | 86 +-
.../examples/ParameterizedExpanderTest.java | 98 +-
.../archivers/examples/SevenZArchiverTest.java | 72 +-
.../compress/archivers/jar/ExpandApkTest.java | 10 +-
.../archivers/memory/MemoryArchiveEntry.java | 10 +-
.../archivers/memory/MemoryArchiveInputStream.java | 8 +-
.../compress/archivers/sevenz/CoverageTest.java | 10 +-
.../compress/archivers/sevenz/FolderTest.java | 28 +-
.../archivers/sevenz/SevenZArchiveEntryTest.java | 30 +-
.../compress/archivers/sevenz/SevenZFileTest.java | 926 +++---
.../archivers/sevenz/SevenZNativeHeapTest.java | 216 +-
.../archivers/sevenz/SevenZOutputFileTest.java | 572 ++--
.../commons/compress/archivers/tar/BigFilesIT.java | 58 +-
.../compress/archivers/tar/FileTimesIT.java | 406 +--
.../compress/archivers/tar/SparseFilesTest.java | 498 ++--
.../archivers/tar/TarArchiveEntryTest.java | 486 ++--
.../archivers/tar/TarArchiveInputStreamTest.java | 348 +--
.../archivers/tar/TarArchiveOutputStreamTest.java | 834 +++---
.../compress/archivers/tar/TarFileTest.java | 238 +-
.../commons/compress/archivers/tar/TarLister.java | 48 +-
.../archivers/tar/TarMemoryFileSystemTest.java | 60 +-
.../compress/archivers/tar/TarUtilsTest.java | 938 +++---
.../compress/archivers/zip/AsiExtraFieldTest.java | 56 +-
.../compress/archivers/zip/BitStreamTest.java | 42 +-
.../compress/archivers/zip/CircularBufferTest.java | 36 +-
.../compress/archivers/zip/DataDescriptorTest.java | 100 +-
.../archivers/zip/EncryptedArchiveTest.java | 32 +-
.../compress/archivers/zip/ExplodeSupportTest.java | 42 +-
.../archivers/zip/ExtraFieldUtilsTest.java | 228 +-
.../archivers/zip/GeneralPurposeBitTest.java | 72 +-
.../commons/compress/archivers/zip/Lister.java | 30 +-
.../archivers/zip/Maven221MultiVolumeTest.java | 10 +-
.../compress/archivers/zip/NioZipEncodingTest.java | 48 +-
.../zip/ParallelScatterZipCreatorTest.java | 150 +-
.../compress/archivers/zip/ScatterSampleTest.java | 38 +-
.../archivers/zip/ScatterZipOutputStreamTest.java | 8 +-
.../archivers/zip/StreamCompressorTest.java | 26 +-
.../compress/archivers/zip/UTF8ZipFilesTest.java | 476 +--
.../compress/archivers/zip/X000A_NTFSTest.java | 32 +-
.../archivers/zip/X5455_ExtendedTimestampTest.java | 488 ++--
.../compress/archivers/zip/X7875_NewUnixTest.java | 214 +-
.../Zip64ExtendedInformationExtraFieldTest.java | 204 +-
.../compress/archivers/zip/Zip64SupportIT.java | 2124 +++++++-------
.../archivers/zip/ZipArchiveEntryTest.java | 358 +--
.../archivers/zip/ZipArchiveInputStreamTest.java | 908 +++---
.../archivers/zip/ZipClassCoverageTest.java | 26 +-
.../archivers/zip/ZipEightByteIntegerTest.java | 66 +-
.../compress/archivers/zip/ZipEncodingTest.java | 124 +-
.../zip/ZipFileIgnoringLocalFileHeaderTest.java | 64 +-
.../compress/archivers/zip/ZipFileTest.java | 1156 ++++----
.../compress/archivers/zip/ZipLongTest.java | 80 +-
.../archivers/zip/ZipMemoryFileSystemTest.java | 490 ++--
.../compress/archivers/zip/ZipShortTest.java | 72 +-
.../archivers/zip/ZipSplitOutputStreamTest.java | 34 +-
.../compress/archivers/zip/ZipUtilTest.java | 172 +-
.../compress/changes/ChangeSetTestCase.java | 828 +++---
.../compress/compressors/BZip2TestCase.java | 46 +-
.../compress/compressors/BZip2UtilsTestCase.java | 52 +-
.../compress/compressors/DeflateTestCase.java | 36 +-
.../compressors/DetectCompressorTestCase.java | 208 +-
.../compress/compressors/FramedSnappyTestCase.java | 70 +-
.../commons/compress/compressors/GZipTestCase.java | 180 +-
.../compress/compressors/GzipUtilsTestCase.java | 76 +-
.../commons/compress/compressors/LZMATestCase.java | 56 +-
.../compress/compressors/Pack200TestCase.java | 172 +-
.../commons/compress/compressors/XZTestCase.java | 48 +-
.../commons/compress/compressors/ZTestCase.java | 34 +-
.../brotli/BrotliCompressorInputStreamTest.java | 122 +-
.../bzip2/BZip2CompressorInputStreamTest.java | 72 +-
.../compress/compressors/bzip2/BlockSortTest.java | 122 +-
.../bzip2/PythonTruncatedBzip2Test.java | 52 +-
.../deflate/DeflateCompressorInputStreamTest.java | 20 +-
.../compressors/deflate/DeflateParametersTest.java | 8 +-
.../compressors/deflate64/HuffmanDecoderTest.java | 160 +-
.../lz4/BlockLZ4CompressorInputStreamTest.java | 28 +-
.../lz4/BlockLZ4CompressorOutputStreamTest.java | 234 +-
.../lz4/BlockLZ4CompressorRoundtripTest.java | 42 +-
.../compress/compressors/lz4/FactoryTest.java | 8 +-
.../lz4/FramedLZ4CompressorInputStreamTest.java | 452 +--
.../lz4/FramedLZ4CompressorRoundtripTest.java | 40 +-
.../AbstractLZ77CompressorInputStreamTest.java | 22 +-
.../lz77support/LZ77CompressorTest.java | 302 +-
.../compressors/lz77support/ParametersTest.java | 92 +-
.../compressors/lzma/LZMAUtilsTestCase.java | 68 +-
.../FramedSnappyCompressorInputStreamTest.java | 202 +-
.../compressors/snappy/SnappyRoundtripTest.java | 124 +-
.../xz/XZCompressorInputStreamTest.java | 54 +-
.../compress/compressors/xz/XZUtilsTestCase.java | 72 +-
.../compressors/z/ZCompressorInputStreamTest.java | 26 +-
.../zstandard/ZstdCompressorInputStreamTest.java | 188 +-
.../compressors/zstandard/ZstdRoundtripTest.java | 34 +-
.../compressors/zstandard/ZstdUtilsTest.java | 24 +-
.../harmony/pack200/tests/ArchiveTest.java | 330 +--
.../harmony/pack200/tests/BHSDCodecTest.java | 20 +-
.../harmony/pack200/tests/CodecEncodingTest.java | 136 +-
.../compress/harmony/pack200/tests/CodecTest.java | 160 +-
.../compress/harmony/pack200/tests/HelloWorld.java | 8 +-
.../pack200/tests/NewAttributeBandsTest.java | 320 +--
.../harmony/pack200/tests/PackingOptionsTest.java | 568 ++--
.../harmony/pack200/tests/PopulationCodecTest.java | 46 +-
.../harmony/pack200/tests/RunCodecTest.java | 78 +-
.../unpack200/tests/AbstractBandsTestCase.java | 50 +-
.../harmony/unpack200/tests/ArchiveTest.java | 356 +--
.../unpack200/tests/AttributeLayoutTest.java | 78 +-
.../harmony/unpack200/tests/BcBandsTest.java | 654 ++---
.../harmony/unpack200/tests/ClassBandsTest.java | 50 +-
.../harmony/unpack200/tests/CodeAttributeTest.java | 8 +-
.../harmony/unpack200/tests/ICTupleTest.java | 34 +-
.../unpack200/tests/NewAttributeBandsTest.java | 220 +-
.../tests/SegmentConstantPoolArrayCacheTest.java | 36 +-
.../unpack200/tests/SegmentConstantPoolTest.java | 40 +-
.../harmony/unpack200/tests/SegmentTest.java | 36 +-
.../tests/bytecode/ClassFileEntryTest.java | 80 +-
.../unpack200/tests/bytecode/ConstantPoolTest.java | 26 +-
.../commons/compress/utils/BitInputStreamTest.java | 204 +-
.../commons/compress/utils/ByteUtilsTest.java | 124 +-
.../utils/ChecksumCalculatingInputStreamTest.java | 58 +-
.../commons/compress/utils/CountingStreamTest.java | 40 +-
.../commons/compress/utils/FileNameUtilsTest.java | 44 +-
.../utils/FixedLengthBlockOutputStreamTest.java | 384 +--
.../apache/commons/compress/utils/IOUtilsTest.java | 170 +-
.../MultiReadOnlySeekableByteChannelTest.java | 366 +--
.../utils/SeekableInMemoryByteChannelTest.java | 288 +-
.../compress/utils/ServiceLoaderIteratorTest.java | 14 +-
.../utils/SkipShieldingInputStreamTest.java | 30 +-
.../commons/compress/utils/TimeUtilsTest.java | 120 +-
.../ZipSplitReadOnlySeekableByteChannelTest.java | 130 +-
408 files changed, 38768 insertions(+), 38768 deletions(-)
diff --git a/src/main/java/org/apache/commons/compress/MemoryLimitException.java b/src/main/java/org/apache/commons/compress/MemoryLimitException.java
index 093d5aef..88aef325 100644
--- a/src/main/java/org/apache/commons/compress/MemoryLimitException.java
+++ b/src/main/java/org/apache/commons/compress/MemoryLimitException.java
@@ -32,8 +32,14 @@ public class MemoryLimitException extends IOException {
private static final long serialVersionUID = 1L;
+ private static String buildMessage(final long memoryNeededInKb, final int memoryLimitInKb) {
+ return memoryNeededInKb + " kb of memory would be needed; limit was "
+ + memoryLimitInKb + " kb. " +
+ "If the file is not corrupt, consider increasing the memory limit.";
+ }
/** long instead of int to account for overflow for corrupt files. */
private final long memoryNeededInKb;
+
private final int memoryLimitInKb;
public MemoryLimitException(final long memoryNeededInKb, final int memoryLimitInKb) {
@@ -48,17 +54,11 @@ public class MemoryLimitException extends IOException {
this.memoryLimitInKb = memoryLimitInKb;
}
- public long getMemoryNeededInKb() {
- return memoryNeededInKb;
- }
-
public int getMemoryLimitInKb() {
return memoryLimitInKb;
}
- private static String buildMessage(final long memoryNeededInKb, final int memoryLimitInKb) {
- return memoryNeededInKb + " kb of memory would be needed; limit was "
- + memoryLimitInKb + " kb. " +
- "If the file is not corrupt, consider increasing the memory limit.";
+ public long getMemoryNeededInKb() {
+ return memoryNeededInKb;
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/ArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/ArchiveEntry.java
index d5fa746a..ee611e17 100644
--- a/src/main/java/org/apache/commons/compress/archivers/ArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/ArchiveEntry.java
@@ -25,6 +25,17 @@ import java.util.Date;
*/
public interface ArchiveEntry {
+ /** Special value indicating that the size is unknown */
+ long SIZE_UNKNOWN = -1;
+
+ /**
+ * Gets the last modified date of this entry.
+ *
+ * @return the last modified date of this entry.
+ * @since 1.1
+ */
+ Date getLastModifiedDate();
+
/**
* Gets the name of the entry in this archive. May refer to a file or directory or other item.
*
@@ -41,21 +52,10 @@ public interface ArchiveEntry {
*/
long getSize();
- /** Special value indicating that the size is unknown */
- long SIZE_UNKNOWN = -1;
-
/**
* Returns true if this entry refers to a directory.
*
* @return true if this entry refers to a directory.
*/
boolean isDirectory();
-
- /**
- * Gets the last modified date of this entry.
- *
- * @return the last modified date of this entry.
- * @since 1.1
- */
- Date getLastModifiedDate();
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/ArchiveInputStream.java b/src/main/java/org/apache/commons/compress/archivers/ArchiveInputStream.java
index 2f03a45b..1cd9304e 100644
--- a/src/main/java/org/apache/commons/compress/archivers/ArchiveInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/ArchiveInputStream.java
@@ -39,20 +39,28 @@ import java.io.InputStream;
*/
public abstract class ArchiveInputStream extends InputStream {
- private final byte[] single = new byte[1];
private static final int BYTE_MASK = 0xFF;
+ private final byte[] single = new byte[1];
/** holds the number of bytes read in this stream */
private long bytesRead;
/**
- * Returns the next Archive Entry in this Stream.
+ * Whether this stream is able to read the given entry.
*
- * @return the next entry,
- * or {@code null} if there are no more entries
- * @throws IOException if the next entry could not be read
+ * <p>
+ * Some archive formats support variants or details that are not supported (yet).
+ * </p>
+ *
+ * @param archiveEntry
+ * the entry to test
+ * @return This implementation always returns true.
+ *
+ * @since 1.1
*/
- public abstract ArchiveEntry getNextEntry() throws IOException;
+ public boolean canReadEntryData(final ArchiveEntry archiveEntry) {
+ return true;
+ }
/*
* Note that subclasses also implement specific get() methods which
@@ -63,25 +71,6 @@ public abstract class ArchiveInputStream extends InputStream {
*/
// public abstract XXXArchiveEntry getNextXXXEntry() throws IOException;
- /**
- * Reads a byte of data. This method will block until enough input is
- * available.
- *
- * Simply calls the {@link #read(byte[], int, int)} method.
- *
- * MUST be overridden if the {@link #read(byte[], int, int)} method
- * is not overridden; may be overridden otherwise.
- *
- * @return the byte read, or -1 if end of input is reached
- * @throws IOException
- * if an I/O error has occurred
- */
- @Override
- public int read() throws IOException {
- final int num = read(single, 0, 1);
- return num == -1 ? -1 : single[0] & BYTE_MASK;
- }
-
/**
* Increments the counter of already read bytes.
* Doesn't increment if the EOF has been hit (read == -1)
@@ -106,13 +95,12 @@ public abstract class ArchiveInputStream extends InputStream {
}
/**
- * Decrements the counter of already read bytes.
- *
- * @param pushedBack the number of bytes pushed back.
+ * Returns the current number of bytes read from this stream.
+ * @return the number of read bytes
* @since 1.1
*/
- protected void pushedBackBytes(final long pushedBack) {
- bytesRead -= pushedBack;
+ public long getBytesRead() {
+ return bytesRead;
}
/**
@@ -127,29 +115,41 @@ public abstract class ArchiveInputStream extends InputStream {
}
/**
- * Returns the current number of bytes read from this stream.
- * @return the number of read bytes
+ * Returns the next Archive Entry in this Stream.
+ *
+ * @return the next entry,
+ * or {@code null} if there are no more entries
+ * @throws IOException if the next entry could not be read
+ */
+ public abstract ArchiveEntry getNextEntry() throws IOException;
+
+ /**
+ * Decrements the counter of already read bytes.
+ *
+ * @param pushedBack the number of bytes pushed back.
* @since 1.1
*/
- public long getBytesRead() {
- return bytesRead;
+ protected void pushedBackBytes(final long pushedBack) {
+ bytesRead -= pushedBack;
}
/**
- * Whether this stream is able to read the given entry.
+ * Reads a byte of data. This method will block until enough input is
+ * available.
*
- * <p>
- * Some archive formats support variants or details that are not supported (yet).
- * </p>
+ * Simply calls the {@link #read(byte[], int, int)} method.
*
- * @param archiveEntry
- * the entry to test
- * @return This implementation always returns true.
+ * MUST be overridden if the {@link #read(byte[], int, int)} method
+ * is not overridden; may be overridden otherwise.
*
- * @since 1.1
+ * @return the byte read, or -1 if end of input is reached
+ * @throws IOException
+ * if an I/O error has occurred
*/
- public boolean canReadEntryData(final ArchiveEntry archiveEntry) {
- return true;
+ @Override
+ public int read() throws IOException {
+ final int num = read(single, 0, 1);
+ return num == -1 ? -1 : single[0] & BYTE_MASK;
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/ArchiveOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/ArchiveOutputStream.java
index a686c467..2d5fbfa5 100644
--- a/src/main/java/org/apache/commons/compress/archivers/ArchiveOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/ArchiveOutputStream.java
@@ -48,23 +48,28 @@ import java.nio.file.Path;
*/
public abstract class ArchiveOutputStream extends OutputStream {
+ static final int BYTE_MASK = 0xFF;
/** Temporary buffer used for the {@link #write(int)} method */
private final byte[] oneByte = new byte[1];
- static final int BYTE_MASK = 0xFF;
/** holds the number of bytes written to this stream */
private long bytesWritten;
// Methods specific to ArchiveOutputStream
/**
- * Writes the headers for an archive entry to the output stream.
- * The caller must then write the content to the stream and call
- * {@link #closeArchiveEntry()} to complete the process.
+ * Whether this stream is able to write the given entry.
*
- * @param entry describes the entry
- * @throws IOException if an I/O error occurs
+ * <p>Some archive formats support variants or details that are
+ * not supported (yet).</p>
+ *
+ * @param archiveEntry
+ * the entry to test
+ * @return This implementation always returns true.
+ * @since 1.1
*/
- public abstract void putArchiveEntry(ArchiveEntry entry) throws IOException;
+ public boolean canWriteEntryData(final ArchiveEntry archiveEntry) {
+ return true;
+ }
/**
* Closes the archive entry, writing any trailer information that may
@@ -74,12 +79,27 @@ public abstract class ArchiveOutputStream extends OutputStream {
public abstract void closeArchiveEntry() throws IOException;
/**
- * Finishes the addition of entries to this stream, without closing it.
- * Additional data can be written, if the format supports it.
+ * Increments the counter of already written bytes.
+ * Doesn't increment if EOF has been hit ({@code written == -1}).
*
- * @throws IOException if the user forgets to close the entry.
+ * @param written the number of bytes written
*/
- public abstract void finish() throws IOException;
+ protected void count(final int written) {
+ count((long) written);
+ }
+
+ /**
+ * Increments the counter of already written bytes.
+ * Doesn't increment if EOF has been hit ({@code written == -1}).
+ *
+ * @param written the number of bytes written
+ * @since 1.1
+ */
+ protected void count(final long written) {
+ if (written != -1) {
+ bytesWritten = bytesWritten + written;
+ }
+ }
/**
* Create an archive entry using the inputFile and entryName provided.
@@ -92,6 +112,8 @@ public abstract class ArchiveOutputStream extends OutputStream {
*/
public abstract ArchiveEntry createArchiveEntry(File inputFile, String entryName) throws IOException;
+ // Generic implementations of OutputStream methods that may be useful to sub-classes
+
/**
* Create an archive entry using the inputPath and entryName provided.
*
@@ -112,46 +134,21 @@ public abstract class ArchiveOutputStream extends OutputStream {
return createArchiveEntry(inputPath.toFile(), entryName);
}
- // Generic implementations of OutputStream methods that may be useful to sub-classes
-
- /**
- * Writes a byte to the current archive entry.
- *
- * <p>This method simply calls {@code write( byte[], 0, 1 )}.
- *
- * <p>MUST be overridden if the {@link #write(byte[], int, int)} method
- * is not overridden; may be overridden otherwise.
- *
- * @param b The byte to be written.
- * @throws IOException on error
- */
- @Override
- public void write(final int b) throws IOException {
- oneByte[0] = (byte) (b & BYTE_MASK);
- write(oneByte, 0, 1);
- }
-
/**
- * Increments the counter of already written bytes.
- * Doesn't increment if EOF has been hit ({@code written == -1}).
+ * Finishes the addition of entries to this stream, without closing it.
+ * Additional data can be written, if the format supports it.
*
- * @param written the number of bytes written
+ * @throws IOException if the user forgets to close the entry.
*/
- protected void count(final int written) {
- count((long) written);
- }
+ public abstract void finish() throws IOException;
/**
- * Increments the counter of already written bytes.
- * Doesn't increment if EOF has been hit ({@code written == -1}).
- *
- * @param written the number of bytes written
+ * Returns the current number of bytes written to this stream.
+ * @return the number of written bytes
* @since 1.1
*/
- protected void count(final long written) {
- if (written != -1) {
- bytesWritten = bytesWritten + written;
- }
+ public long getBytesWritten() {
+ return bytesWritten;
}
/**
@@ -166,26 +163,29 @@ public abstract class ArchiveOutputStream extends OutputStream {
}
/**
- * Returns the current number of bytes written to this stream.
- * @return the number of written bytes
- * @since 1.1
+ * Writes the headers for an archive entry to the output stream.
+ * The caller must then write the content to the stream and call
+ * {@link #closeArchiveEntry()} to complete the process.
+ *
+ * @param entry describes the entry
+ * @throws IOException if an I/O error occurs
*/
- public long getBytesWritten() {
- return bytesWritten;
- }
+ public abstract void putArchiveEntry(ArchiveEntry entry) throws IOException;
/**
- * Whether this stream is able to write the given entry.
+ * Writes a byte to the current archive entry.
*
- * <p>Some archive formats support variants or details that are
- * not supported (yet).</p>
+ * <p>This method simply calls {@code write( byte[], 0, 1 )}.
*
- * @param archiveEntry
- * the entry to test
- * @return This implementation always returns true.
- * @since 1.1
+ * <p>MUST be overridden if the {@link #write(byte[], int, int)} method
+ * is not overridden; may be overridden otherwise.
+ *
+ * @param b The byte to be written.
+ * @throws IOException on error
*/
- public boolean canWriteEntryData(final ArchiveEntry archiveEntry) {
- return true;
+ @Override
+ public void write(final int b) throws IOException {
+ oneByte[0] = (byte) (b & BYTE_MASK);
+ write(oneByte, 0, 1);
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java b/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java
index fcc9f9e9..059dbd7f 100644
--- a/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java
+++ b/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java
@@ -187,30 +187,100 @@ public class ArchiveStreamFactory implements ArchiveStreamProvider {
*/
public static final String SEVEN_Z = "7z";
- /**
- * Entry encoding, null for the platform default.
- */
- private final String encoding;
+ private static Iterable<ArchiveStreamProvider> archiveStreamProviderIterable() {
+ return ServiceLoader.load(ArchiveStreamProvider.class, ClassLoader.getSystemClassLoader());
+ }
/**
- * Entry encoding, null for the default.
+ * Try to determine the type of Archiver
+ * @param in input stream
+ * @return type of archiver if found
+ * @throws ArchiveException if an archiver cannot be detected in the stream
+ * @since 1.14
*/
- private volatile String entryEncoding;
+ public static String detect(final InputStream in) throws ArchiveException {
+ if (in == null) {
+ throw new IllegalArgumentException("Stream must not be null.");
+ }
- private SortedMap<String, ArchiveStreamProvider> archiveInputStreamProviders;
+ if (!in.markSupported()) {
+ throw new IllegalArgumentException("Mark is not supported.");
+ }
- private SortedMap<String, ArchiveStreamProvider> archiveOutputStreamProviders;
+ final byte[] signature = new byte[SIGNATURE_SIZE];
+ in.mark(signature.length);
+ int signatureLength = -1;
+ try {
+ signatureLength = IOUtils.readFully(in, signature);
+ in.reset();
+ } catch (final IOException e) {
+ throw new ArchiveException("IOException while reading signature.", e);
+ }
- static void putAll(final Set<String> names, final ArchiveStreamProvider provider, final TreeMap<String, ArchiveStreamProvider> map) {
- names.forEach(name -> map.put(toKey(name), provider));
- }
+ if (ZipArchiveInputStream.matches(signature, signatureLength)) {
+ return ZIP;
+ }
+ if (JarArchiveInputStream.matches(signature, signatureLength)) {
+ return JAR;
+ }
+ if (ArArchiveInputStream.matches(signature, signatureLength)) {
+ return AR;
+ }
+ if (CpioArchiveInputStream.matches(signature, signatureLength)) {
+ return CPIO;
+ }
+ if (ArjArchiveInputStream.matches(signature, signatureLength)) {
+ return ARJ;
+ }
+ if (SevenZFile.matches(signature, signatureLength)) {
+ return SEVEN_Z;
+ }
- private static Iterable<ArchiveStreamProvider> archiveStreamProviderIterable() {
- return ServiceLoader.load(ArchiveStreamProvider.class, ClassLoader.getSystemClassLoader());
- }
+ // Dump needs a bigger buffer to check the signature;
+ final byte[] dumpsig = new byte[DUMP_SIGNATURE_SIZE];
+ in.mark(dumpsig.length);
+ try {
+ signatureLength = IOUtils.readFully(in, dumpsig);
+ in.reset();
+ } catch (final IOException e) {
+ throw new ArchiveException("IOException while reading dump signature", e);
+ }
+ if (DumpArchiveInputStream.matches(dumpsig, signatureLength)) {
+ return DUMP;
+ }
- private static String toKey(final String name) {
- return name.toUpperCase(Locale.ROOT);
+ // Tar needs an even bigger buffer to check the signature; read the first block
+ final byte[] tarHeader = new byte[TAR_HEADER_SIZE];
+ in.mark(tarHeader.length);
+ try {
+ signatureLength = IOUtils.readFully(in, tarHeader);
+ in.reset();
+ } catch (final IOException e) {
+ throw new ArchiveException("IOException while reading tar signature", e);
+ }
+ if (TarArchiveInputStream.matches(tarHeader, signatureLength)) {
+ return TAR;
+ }
+
+ // COMPRESS-117 - improve auto-recognition
+ if (signatureLength >= TAR_HEADER_SIZE) {
+ TarArchiveInputStream tais = null;
+ try {
+ tais = new TarArchiveInputStream(new ByteArrayInputStream(tarHeader));
+ // COMPRESS-191 - verify the header checksum
+ if (tais.getNextTarEntry().isCheckSumOK()) {
+ return TAR;
+ }
+ } catch (final Exception e) { // NOPMD NOSONAR
+ // can generate IllegalArgumentException as well
+ // as IOException
+ // autodetection, simply not a TAR
+ // ignored
+ } finally {
+ IOUtils.closeQuietly(tais);
+ }
+ }
+ throw new ArchiveException("No Archiver found for the stream signature");
}
/**
@@ -285,6 +355,28 @@ public class ArchiveStreamFactory implements ArchiveStreamProvider {
});
}
+ static void putAll(final Set<String> names, final ArchiveStreamProvider provider, final TreeMap<String, ArchiveStreamProvider> map) {
+ names.forEach(name -> map.put(toKey(name), provider));
+ }
+
+ private static String toKey(final String name) {
+ return name.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Entry encoding, null for the platform default.
+ */
+ private final String encoding;
+
+ /**
+ * Entry encoding, null for the default.
+ */
+ private volatile String entryEncoding;
+
+ private SortedMap<String, ArchiveStreamProvider> archiveInputStreamProviders;
+
+ private SortedMap<String, ArchiveStreamProvider> archiveOutputStreamProviders;
+
/**
* Create an instance using the platform default encoding.
*/
@@ -306,32 +398,20 @@ public class ArchiveStreamFactory implements ArchiveStreamProvider {
}
/**
- * Returns the encoding to use for arj, jar, zip, dump, cpio and tar
- * files, or null for the archiver default.
- *
- * @return entry encoding, or null for the archiver default
- * @since 1.5
- */
- public String getEntryEncoding() {
- return entryEncoding;
- }
-
- /**
- * Sets the encoding to use for arj, jar, zip, dump, cpio and tar files. Use null for the archiver default.
+ * Create an archive input stream from an input stream, autodetecting
+ * the archive type from the first few bytes of the stream. The InputStream
+ * must support marks, like BufferedInputStream.
*
- * @param entryEncoding the entry encoding, null uses the archiver default.
- * @since 1.5
- * @deprecated 1.10 use {@link #ArchiveStreamFactory(String)} to specify the encoding
- * @throws IllegalStateException if the constructor {@link #ArchiveStreamFactory(String)}
- * was used to specify the factory encoding.
+ * @param in the input stream
+ * @return the archive input stream
+ * @throws ArchiveException if the archiver name is not known
+ * @throws StreamingNotSupportedException if the format cannot be
+ * read from a stream
+ * @throws IllegalArgumentException if the stream is null or does not support mark
*/
- @Deprecated
- public void setEntryEncoding(final String entryEncoding) {
- // Note: this does not detect new ArchiveStreamFactory(null) but that does not set the encoding anyway
- if (encoding != null) {
- throw new IllegalStateException("Cannot overide encoding set by the constructor");
- }
- this.entryEncoding = entryEncoding;
+ public ArchiveInputStream createArchiveInputStream(final InputStream in)
+ throws ArchiveException {
+ return createArchiveInputStream(detect(in), in);
}
/**
@@ -481,115 +561,6 @@ public class ArchiveStreamFactory implements ArchiveStreamProvider {
throw new ArchiveException("Archiver: " + archiverName + " not found.");
}
- /**
- * Create an archive input stream from an input stream, autodetecting
- * the archive type from the first few bytes of the stream. The InputStream
- * must support marks, like BufferedInputStream.
- *
- * @param in the input stream
- * @return the archive input stream
- * @throws ArchiveException if the archiver name is not known
- * @throws StreamingNotSupportedException if the format cannot be
- * read from a stream
- * @throws IllegalArgumentException if the stream is null or does not support mark
- */
- public ArchiveInputStream createArchiveInputStream(final InputStream in)
- throws ArchiveException {
- return createArchiveInputStream(detect(in), in);
- }
-
- /**
- * Try to determine the type of Archiver
- * @param in input stream
- * @return type of archiver if found
- * @throws ArchiveException if an archiver cannot be detected in the stream
- * @since 1.14
- */
- public static String detect(final InputStream in) throws ArchiveException {
- if (in == null) {
- throw new IllegalArgumentException("Stream must not be null.");
- }
-
- if (!in.markSupported()) {
- throw new IllegalArgumentException("Mark is not supported.");
- }
-
- final byte[] signature = new byte[SIGNATURE_SIZE];
- in.mark(signature.length);
- int signatureLength = -1;
- try {
- signatureLength = IOUtils.readFully(in, signature);
- in.reset();
- } catch (final IOException e) {
- throw new ArchiveException("IOException while reading signature.", e);
- }
-
- if (ZipArchiveInputStream.matches(signature, signatureLength)) {
- return ZIP;
- }
- if (JarArchiveInputStream.matches(signature, signatureLength)) {
- return JAR;
- }
- if (ArArchiveInputStream.matches(signature, signatureLength)) {
- return AR;
- }
- if (CpioArchiveInputStream.matches(signature, signatureLength)) {
- return CPIO;
- }
- if (ArjArchiveInputStream.matches(signature, signatureLength)) {
- return ARJ;
- }
- if (SevenZFile.matches(signature, signatureLength)) {
- return SEVEN_Z;
- }
-
- // Dump needs a bigger buffer to check the signature;
- final byte[] dumpsig = new byte[DUMP_SIGNATURE_SIZE];
- in.mark(dumpsig.length);
- try {
- signatureLength = IOUtils.readFully(in, dumpsig);
- in.reset();
- } catch (final IOException e) {
- throw new ArchiveException("IOException while reading dump signature", e);
- }
- if (DumpArchiveInputStream.matches(dumpsig, signatureLength)) {
- return DUMP;
- }
-
- // Tar needs an even bigger buffer to check the signature; read the first block
- final byte[] tarHeader = new byte[TAR_HEADER_SIZE];
- in.mark(tarHeader.length);
- try {
- signatureLength = IOUtils.readFully(in, tarHeader);
- in.reset();
- } catch (final IOException e) {
- throw new ArchiveException("IOException while reading tar signature", e);
- }
- if (TarArchiveInputStream.matches(tarHeader, signatureLength)) {
- return TAR;
- }
-
- // COMPRESS-117 - improve auto-recognition
- if (signatureLength >= TAR_HEADER_SIZE) {
- TarArchiveInputStream tais = null;
- try {
- tais = new TarArchiveInputStream(new ByteArrayInputStream(tarHeader));
- // COMPRESS-191 - verify the header checksum
- if (tais.getNextTarEntry().isCheckSumOK()) {
- return TAR;
- }
- } catch (final Exception e) { // NOPMD NOSONAR
- // can generate IllegalArgumentException as well
- // as IOException
- // autodetection, simply not a TAR
- // ignored
- } finally {
- IOUtils.closeQuietly(tais);
- }
- }
- throw new ArchiveException("No Archiver found for the stream signature");
- }
-
public SortedMap<String, ArchiveStreamProvider> getArchiveInputStreamProviders() {
if (archiveInputStreamProviders == null) {
archiveInputStreamProviders = Collections
@@ -606,6 +577,17 @@ public class ArchiveStreamFactory implements ArchiveStreamProvider {
return archiveOutputStreamProviders;
}
+ /**
+ * Returns the encoding to use for arj, jar, zip, dump, cpio and tar
+ * files, or null for the archiver default.
+ *
+ * @return entry encoding, or null for the archiver default
+ * @since 1.5
+ */
+ public String getEntryEncoding() {
+ return entryEncoding;
+ }
+
@Override
public Set<String> getInputStreamArchiveNames() {
return Sets.newHashSet(AR, ARJ, ZIP, TAR, JAR, CPIO, DUMP, SEVEN_Z);
@@ -616,4 +598,22 @@ public class ArchiveStreamFactory implements ArchiveStreamProvider {
return Sets.newHashSet(AR, ZIP, TAR, JAR, CPIO, SEVEN_Z);
}
+ /**
+ * Sets the encoding to use for arj, jar, zip, dump, cpio and tar files. Use null for the archiver default.
+ *
+ * @param entryEncoding the entry encoding, null uses the archiver default.
+ * @since 1.5
+ * @deprecated 1.10 use {@link #ArchiveStreamFactory(String)} to specify the encoding
+ * @throws IllegalStateException if the constructor {@link #ArchiveStreamFactory(String)}
+ * was used to specify the factory encoding.
+ */
+ @Deprecated
+ public void setEntryEncoding(final String entryEncoding) {
+ // Note: this does not detect new ArchiveStreamFactory(null) but that does not set the encoding anyway
+ if (encoding != null) {
+ throw new IllegalStateException("Cannot overide encoding set by the constructor");
+ }
+ this.entryEncoding = entryEncoding;
+ }
+
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/Lister.java b/src/main/java/org/apache/commons/compress/archivers/Lister.java
index 55524f54..7c7a07db 100644
--- a/src/main/java/org/apache/commons/compress/archivers/Lister.java
+++ b/src/main/java/org/apache/commons/compress/archivers/Lister.java
@@ -42,52 +42,6 @@ public final class Lister {
private static final ArchiveStreamFactory FACTORY = ArchiveStreamFactory.DEFAULT;
- /**
- * Runs this class from the command line.
- * <p>
- * The name of the archive must be given as a command line argument.
- * </p>
- * <p>
- * The optional second argument defines the archive type, in case the format is not recognized.
- * </p>
- *
- * @param args name of the archive and optional argument archive type.
- * @throws ArchiveException Archiver related Exception.
- * @throws IOException an I/O exception.
- */
- public static void main(final String[] args) throws ArchiveException, IOException {
- if (args.length == 0) {
- usage();
- return;
- }
- System.out.println("Analysing " + args[0]);
- final File f = new File(args[0]);
- if (!f.isFile()) {
- System.err.println(f + " doesn't exist or is a directory");
- }
- final String format = args.length > 1 ? args[1] : detectFormat(f);
- if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format)) {
- list7z(f);
- } else if ("zipfile".equals(format)) {
- listZipUsingZipFile(f);
- } else if ("tarfile".equals(format)) {
- listZipUsingTarFile(f);
- } else {
- listStream(f, args);
- }
- }
-
- private static void listStream(final File f, final String[] args) throws ArchiveException, IOException {
- try (final InputStream fis = new BufferedInputStream(Files.newInputStream(f.toPath()));
- final ArchiveInputStream ais = createArchiveInputStream(args, fis)) {
- System.out.println("Created " + ais.toString());
- ArchiveEntry ae;
- while ((ae = ais.getNextEntry()) != null) {
- System.out.println(ae.getName());
- }
- }
- }
-
private static ArchiveInputStream createArchiveInputStream(final String[] args, final InputStream fis)
throws ArchiveException {
if (args.length > 1) {
@@ -114,6 +68,24 @@ public final class Lister {
}
}
+ private static void listStream(final File f, final String[] args) throws ArchiveException, IOException {
+ try (final InputStream fis = new BufferedInputStream(Files.newInputStream(f.toPath()));
+ final ArchiveInputStream ais = createArchiveInputStream(args, fis)) {
+ System.out.println("Created " + ais.toString());
+ ArchiveEntry ae;
+ while ((ae = ais.getNextEntry()) != null) {
+ System.out.println(ae.getName());
+ }
+ }
+ }
+
+ private static void listZipUsingTarFile(final File f) throws IOException {
+ try (TarFile t = new TarFile(f)) {
+ System.out.println("Created " + t);
+ t.getEntries().forEach(en -> System.out.println(en.getName()));
+ }
+ }
+
private static void listZipUsingZipFile(final File f) throws IOException {
try (ZipFile z = new ZipFile(f)) {
System.out.println("Created " + z);
@@ -123,10 +95,38 @@ public final class Lister {
}
}
- private static void listZipUsingTarFile(final File f) throws IOException {
- try (TarFile t = new TarFile(f)) {
- System.out.println("Created " + t);
- t.getEntries().forEach(en -> System.out.println(en.getName()));
+ /**
+ * Runs this class from the command line.
+ * <p>
+ * The name of the archive must be given as a command line argument.
+ * </p>
+ * <p>
+ * The optional second argument defines the archive type, in case the format is not recognized.
+ * </p>
+ *
+ * @param args name of the archive and optional argument archive type.
+ * @throws ArchiveException Archiver related Exception.
+ * @throws IOException an I/O exception.
+ */
+ public static void main(final String[] args) throws ArchiveException, IOException {
+ if (args.length == 0) {
+ usage();
+ return;
+ }
+ System.out.println("Analysing " + args[0]);
+ final File f = new File(args[0]);
+ if (!f.isFile()) {
+ System.err.println(f + " doesn't exist or is a directory");
+ }
+ final String format = args.length > 1 ? args[1] : detectFormat(f);
+ if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format)) {
+ list7z(f);
+ } else if ("zipfile".equals(format)) {
+ listZipUsingZipFile(f);
+ } else if ("tarfile".equals(format)) {
+ listZipUsingTarFile(f);
+ } else {
+ listStream(f, args);
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/ar/ArArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/ar/ArArchiveEntry.java
index e5eeab4b..54017404 100644
--- a/src/main/java/org/apache/commons/compress/archivers/ar/ArArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/ar/ArArchiveEntry.java
@@ -64,6 +64,7 @@ public class ArArchiveEntry implements ArchiveEntry {
/** The trailer for each entry */
public static final String TRAILER = "`\012";
+ private static final int DEFAULT_MODE = 33188; // = (octal) 0100644
/**
* SVR4/GNU adds a trailing / to names; BSD does not.
* They also vary in how names longer than 16 characters are represented.
@@ -73,10 +74,33 @@ public class ArArchiveEntry implements ArchiveEntry {
private final int userId;
private final int groupId;
private final int mode;
- private static final int DEFAULT_MODE = 33188; // = (octal) 0100644
private final long lastModified;
private final long length;
+ /**
+ * Creates a new instance using the attributes of the given file
+ * @param inputFile the file to create an entry from
+ * @param entryName the name of the entry
+ */
+ public ArArchiveEntry(final File inputFile, final String entryName) {
+ // TODO sort out mode
+ this(entryName, inputFile.isFile() ? inputFile.length() : 0,
+ 0, 0, DEFAULT_MODE, inputFile.lastModified() / 1000);
+ }
+
+ /**
+ * Creates a new instance using the attributes of the given file
+ * @param inputPath the file to create an entry from
+ * @param entryName the name of the entry
+ * @param options options indicating how symbolic links are handled.
+ * @throws IOException if an I/O error occurs.
+ * @since 1.21
+ */
+ public ArArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
+ this(entryName, Files.isRegularFile(inputPath, options) ? Files.size(inputPath) : 0, 0, 0, DEFAULT_MODE,
+ Files.getLastModifiedTime(inputPath, options).toMillis() / 1000);
+ }
+
/**
* Create a new instance using a couple of default values.
*
@@ -114,52 +138,25 @@ public class ArArchiveEntry implements ArchiveEntry {
this.lastModified = lastModified;
}
- /**
- * Creates a new instance using the attributes of the given file
- * @param inputFile the file to create an entry from
- * @param entryName the name of the entry
- */
- public ArArchiveEntry(final File inputFile, final String entryName) {
- // TODO sort out mode
- this(entryName, inputFile.isFile() ? inputFile.length() : 0,
- 0, 0, DEFAULT_MODE, inputFile.lastModified() / 1000);
- }
-
- /**
- * Creates a new instance using the attributes of the given file
- * @param inputPath the file to create an entry from
- * @param entryName the name of the entry
- * @param options options indicating how symbolic links are handled.
- * @throws IOException if an I/O error occurs.
- * @since 1.21
- */
- public ArArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
- this(entryName, Files.isRegularFile(inputPath, options) ? Files.size(inputPath) : 0, 0, 0, DEFAULT_MODE,
- Files.getLastModifiedTime(inputPath, options).toMillis() / 1000);
- }
-
- @Override
- public long getSize() {
- return this.getLength();
- }
-
@Override
- public String getName() {
- return name;
- }
-
- public int getUserId() {
- return userId;
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final ArArchiveEntry other = (ArArchiveEntry) obj;
+ if (name == null) {
+ return other.name == null;
+ }
+ return name.equals(other.name);
}
public int getGroupId() {
return groupId;
}
- public int getMode() {
- return mode;
- }
-
/**
* Last modified time in seconds since the epoch.
* @return the last modified date
@@ -177,9 +174,22 @@ public class ArArchiveEntry implements ArchiveEntry {
return length;
}
+ public int getMode() {
+ return mode;
+ }
+
@Override
- public boolean isDirectory() {
- return false;
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public long getSize() {
+ return this.getLength();
+ }
+
+ public int getUserId() {
+ return userId;
}
@Override
@@ -188,17 +198,7 @@ public class ArArchiveEntry implements ArchiveEntry {
}
@Override
- public boolean equals(final Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- final ArArchiveEntry other = (ArArchiveEntry) obj;
- if (name == null) {
- return other.name == null;
- }
- return name.equals(other.name);
+ public boolean isDirectory() {
+ return false;
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/ar/ArArchiveInputStream.java b/src/main/java/org/apache/commons/compress/archivers/ar/ArArchiveInputStream.java
index f30951de..4d234722 100644
--- a/src/main/java/org/apache/commons/compress/archivers/ar/ArArchiveInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/ar/ArArchiveInputStream.java
@@ -36,8 +36,101 @@ import org.apache.commons.compress.utils.IOUtils;
*/
public class ArArchiveInputStream extends ArchiveInputStream {
+ // offsets and length of meta data parts
+ private static final int NAME_OFFSET = 0;
+ private static final int NAME_LEN = 16;
+ private static final int LAST_MODIFIED_OFFSET = NAME_LEN;
+
+ private static final int LAST_MODIFIED_LEN = 12;
+
+ private static final int USER_ID_OFFSET = LAST_MODIFIED_OFFSET + LAST_MODIFIED_LEN;
+
+ private static final int USER_ID_LEN = 6;
+
+ private static final int GROUP_ID_OFFSET = USER_ID_OFFSET + USER_ID_LEN;
+ private static final int GROUP_ID_LEN = 6;
+ private static final int FILE_MODE_OFFSET = GROUP_ID_OFFSET + GROUP_ID_LEN;
+ private static final int FILE_MODE_LEN = 8;
+ private static final int LENGTH_OFFSET = FILE_MODE_OFFSET + FILE_MODE_LEN;
+ private static final int LENGTH_LEN = 10;
+ static final String BSD_LONGNAME_PREFIX = "#1/";
+ private static final int BSD_LONGNAME_PREFIX_LEN =
+ BSD_LONGNAME_PREFIX.length();
+ private static final String BSD_LONGNAME_PATTERN =
+ "^" + BSD_LONGNAME_PREFIX + "\\d+";
+ private static final String GNU_STRING_TABLE_NAME = "//";
+ private static final String GNU_LONGNAME_PATTERN = "^/\\d+";
+ /**
+ * Does the name look like it is a long name (or a name containing
+ * spaces) as encoded by BSD ar?
+ *
+ * <p>From the FreeBSD ar(5) man page:</p>
+ * <pre>
+ * BSD In the BSD variant, names that are shorter than 16
+ * characters and without embedded spaces are stored
+ * directly in this field. If a name has an embedded
+ * space, or if it is longer than 16 characters, then
+ * the string "#1/" followed by the decimal represen-
+ * tation of the length of the file name is placed in
+ * this field. The actual file name is stored immedi-
+ * ately after the archive header. The content of the
+ * archive member follows the file name. The ar_size
+ * field of the header (see below) will then hold the
+ * sum of the size of the file name and the size of
+ * the member.
+ * </pre>
+ *
+ * @since 1.3
+ */
+ private static boolean isBSDLongName(final String name) {
+ return name != null && name.matches(BSD_LONGNAME_PATTERN);
+ }
+
+ /**
+ * Is this the name of the "Archive String Table" as used by
+ * SVR4/GNU to store long file names?
+ *
+ * <p>GNU ar stores multiple extended file names in the data section
+ * of a file with the name "//", this record is referred to by
+ * future headers.</p>
+ *
+ * <p>A header references an extended file name by storing a "/"
+ * followed by a decimal offset to the start of the file name in
+ * the extended file name data section.</p>
+ *
+ * <p>The format of the "//" file itself is simply a list of the
+ * long file names, each separated by one or more LF
+ * characters. Note that the decimal offsets are number of
+ * characters, not line or string number within the "//" file.</p>
+ */
+ private static boolean isGNUStringTable(final String name) {
+ return GNU_STRING_TABLE_NAME.equals(name);
+ }
+
+ /**
+ * Checks if the signature matches ASCII "!<arch>" followed by a single LF
+ * control character
+ *
+ * @param signature
+ * the bytes to check
+ * @param length
+ * the number of bytes to check
+ * @return true, if this stream is an Ar archive stream, false otherwise
+ */
+ public static boolean matches(final byte[] signature, final int length) {
+ // 3c21 7261 6863 0a3e
+
+ return length >= 8 && signature[0] == 0x21 &&
+ signature[1] == 0x3c && signature[2] == 0x61 &&
+ signature[3] == 0x72 && signature[4] == 0x63 &&
+ signature[5] == 0x68 && signature[6] == 0x3e &&
+ signature[7] == 0x0a;
+ }
+
private final InputStream input;
+
private long offset;
+
private boolean closed;
/*
@@ -55,20 +148,6 @@ public class ArArchiveInputStream extends ArchiveInputStream {
*/
private long entryOffset = -1;
- // offsets and length of meta data parts
- private static final int NAME_OFFSET = 0;
- private static final int NAME_LEN = 16;
- private static final int LAST_MODIFIED_OFFSET = NAME_LEN;
- private static final int LAST_MODIFIED_LEN = 12;
- private static final int USER_ID_OFFSET = LAST_MODIFIED_OFFSET + LAST_MODIFIED_LEN;
- private static final int USER_ID_LEN = 6;
- private static final int GROUP_ID_OFFSET = USER_ID_OFFSET + USER_ID_LEN;
- private static final int GROUP_ID_LEN = 6;
- private static final int FILE_MODE_OFFSET = GROUP_ID_OFFSET + GROUP_ID_LEN;
- private static final int FILE_MODE_LEN = 8;
- private static final int LENGTH_OFFSET = FILE_MODE_OFFSET + FILE_MODE_LEN;
- private static final int LENGTH_LEN = 10;
-
// cached buffer for meta data - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
private final byte[] metaData =
new byte[NAME_LEN + LAST_MODIFIED_LEN + USER_ID_LEN + GROUP_ID_LEN + FILE_MODE_LEN + LENGTH_LEN];
@@ -84,6 +163,84 @@ public class ArArchiveInputStream extends ArchiveInputStream {
closed = false;
}
+ private int asInt(final byte[] byteArray, final int offset, final int len) {
+ return asInt(byteArray, offset, len, 10, false);
+ }
+
+ private int asInt(final byte[] byteArray, final int offset, final int len, final boolean treatBlankAsZero) {
+ return asInt(byteArray, offset, len, 10, treatBlankAsZero);
+ }
+
+ private int asInt(final byte[] byteArray, final int offset, final int len, final int base) {
+ return asInt(byteArray, offset, len, base, false);
+ }
+
+ private int asInt(final byte[] byteArray, final int offset, final int len, final int base, final boolean treatBlankAsZero) {
+ final String string = ArchiveUtils.toAsciiString(byteArray, offset, len).trim();
+ if (string.isEmpty() && treatBlankAsZero) {
+ return 0;
+ }
+ return Integer.parseInt(string, base);
+ }
+ private long asLong(final byte[] byteArray, final int offset, final int len) {
+ return Long.parseLong(ArchiveUtils.toAsciiString(byteArray, offset, len).trim());
+ }
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.InputStream#close()
+ */
+ @Override
+ public void close() throws IOException {
+ if (!closed) {
+ closed = true;
+ input.close();
+ }
+ currentEntry = null;
+ }
+
+ /**
+ * Reads the real name from the current stream assuming the very
+ * first bytes to be read are the real file name.
+ *
+ * @see #isBSDLongName
+ *
+ * @since 1.3
+ */
+ private String getBSDLongName(final String bsdLongName) throws IOException {
+ final int nameLen =
+ Integer.parseInt(bsdLongName.substring(BSD_LONGNAME_PREFIX_LEN));
+ final byte[] name = IOUtils.readRange(input, nameLen);
+ final int read = name.length;
+ trackReadBytes(read);
+ if (read != nameLen) {
+ throw new EOFException();
+ }
+ return ArchiveUtils.toAsciiString(name);
+ }
+
+ /**
+ * Get an extended name from the GNU extended name buffer.
+ *
+ * @param offset pointer to entry within the buffer
+ * @return the extended file name; without trailing "/" if present.
+ * @throws IOException if name not found or buffer not set up
+ */
+ private String getExtendedName(final int offset) throws IOException {
+ if (namebuffer == null) {
+ throw new IOException("Cannot process GNU long filename as no // record was found");
+ }
+ for (int i = offset; i < namebuffer.length; i++) {
+ if (namebuffer[i] == '\012' || namebuffer[i] == 0) {
+ if (namebuffer[i - 1] == '/') {
+ i--; // drop trailing /
+ }
+ return ArchiveUtils.toAsciiString(namebuffer, offset, i - offset);
+ }
+ }
+ throw new IOException("Failed to read entry: " + offset);
+ }
+
/**
* Returns the next AR entry in this stream.
*
@@ -183,52 +340,6 @@ public class ArArchiveInputStream extends ArchiveInputStream {
return currentEntry;
}
- /**
- * Get an extended name from the GNU extended name buffer.
- *
- * @param offset pointer to entry within the buffer
- * @return the extended file name; without trailing "/" if present.
- * @throws IOException if name not found or buffer not set up
- */
- private String getExtendedName(final int offset) throws IOException {
- if (namebuffer == null) {
- throw new IOException("Cannot process GNU long filename as no // record was found");
- }
- for (int i = offset; i < namebuffer.length; i++) {
- if (namebuffer[i] == '\012' || namebuffer[i] == 0) {
- if (namebuffer[i - 1] == '/') {
- i--; // drop trailing /
- }
- return ArchiveUtils.toAsciiString(namebuffer, offset, i - offset);
- }
- }
- throw new IOException("Failed to read entry: " + offset);
- }
-
- private long asLong(final byte[] byteArray, final int offset, final int len) {
- return Long.parseLong(ArchiveUtils.toAsciiString(byteArray, offset, len).trim());
- }
-
- private int asInt(final byte[] byteArray, final int offset, final int len) {
- return asInt(byteArray, offset, len, 10, false);
- }
-
- private int asInt(final byte[] byteArray, final int offset, final int len, final boolean treatBlankAsZero) {
- return asInt(byteArray, offset, len, 10, treatBlankAsZero);
- }
-
- private int asInt(final byte[] byteArray, final int offset, final int len, final int base) {
- return asInt(byteArray, offset, len, base, false);
- }
-
- private int asInt(final byte[] byteArray, final int offset, final int len, final int base, final boolean treatBlankAsZero) {
- final String string = ArchiveUtils.toAsciiString(byteArray, offset, len).trim();
- if (string.isEmpty() && treatBlankAsZero) {
- return 0;
- }
- return Integer.parseInt(string, base);
- }
-
/*
* (non-Javadoc)
*
@@ -240,18 +351,14 @@ public class ArArchiveInputStream extends ArchiveInputStream {
return getNextArEntry();
}
- /*
- * (non-Javadoc)
+ /**
+ * Does the name look like it is a long name (or a name containing
+ * spaces) as encoded by SVR4/GNU ar?
*
- * @see java.io.InputStream#close()
+ * @see #isGNUStringTable
*/
- @Override
- public void close() throws IOException {
- if (!closed) {
- closed = true;
- input.close();
- }
- currentEntry = null;
+ private boolean isGNULongName(final String name) {
+ return name != null && name.matches(GNU_LONGNAME_PATTERN);
}
/*
@@ -277,108 +384,6 @@ public class ArArchiveInputStream extends ArchiveInputStream {
return ret;
}
- /**
- * Checks if the signature matches ASCII "!<arch>" followed by a single LF
- * control character
- *
- * @param signature
- * the bytes to check
- * @param length
- * the number of bytes to check
- * @return true, if this stream is an Ar archive stream, false otherwise
- */
- public static boolean matches(final byte[] signature, final int length) {
- // 3c21 7261 6863 0a3e
-
- return length >= 8 && signature[0] == 0x21 &&
- signature[1] == 0x3c && signature[2] == 0x61 &&
- signature[3] == 0x72 && signature[4] == 0x63 &&
- signature[5] == 0x68 && signature[6] == 0x3e &&
- signature[7] == 0x0a;
- }
-
- static final String BSD_LONGNAME_PREFIX = "#1/";
- private static final int BSD_LONGNAME_PREFIX_LEN =
- BSD_LONGNAME_PREFIX.length();
- private static final String BSD_LONGNAME_PATTERN =
- "^" + BSD_LONGNAME_PREFIX + "\\d+";
-
- /**
- * Does the name look like it is a long name (or a name containing
- * spaces) as encoded by BSD ar?
- *
- * <p>From the FreeBSD ar(5) man page:</p>
- * <pre>
- * BSD In the BSD variant, names that are shorter than 16
- * characters and without embedded spaces are stored
- * directly in this field. If a name has an embedded
- * space, or if it is longer than 16 characters, then
- * the string "#1/" followed by the decimal represen-
- * tation of the length of the file name is placed in
- * this field. The actual file name is stored immedi-
- * ately after the archive header. The content of the
- * archive member follows the file name. The ar_size
- * field of the header (see below) will then hold the
- * sum of the size of the file name and the size of
- * the member.
- * </pre>
- *
- * @since 1.3
- */
- private static boolean isBSDLongName(final String name) {
- return name != null && name.matches(BSD_LONGNAME_PATTERN);
- }
-
- /**
- * Reads the real name from the current stream assuming the very
- * first bytes to be read are the real file name.
- *
- * @see #isBSDLongName
- *
- * @since 1.3
- */
- private String getBSDLongName(final String bsdLongName) throws IOException {
- final int nameLen =
- Integer.parseInt(bsdLongName.substring(BSD_LONGNAME_PREFIX_LEN));
- final byte[] name = IOUtils.readRange(input, nameLen);
- final int read = name.length;
- trackReadBytes(read);
- if (read != nameLen) {
- throw new EOFException();
- }
- return ArchiveUtils.toAsciiString(name);
- }
-
- private static final String GNU_STRING_TABLE_NAME = "//";
-
- /**
- * Is this the name of the "Archive String Table" as used by
- * SVR4/GNU to store long file names?
- *
- * <p>GNU ar stores multiple extended file names in the data section
- * of a file with the name "//", this record is referred to by
- * future headers.</p>
- *
- * <p>A header references an extended file name by storing a "/"
- * followed by a decimal offset to the start of the file name in
- * the extended file name data section.</p>
- *
- * <p>The format of the "//" file itself is simply a list of the
- * long file names, each separated by one or more LF
- * characters. Note that the decimal offsets are number of
- * characters, not line or string number within the "//" file.</p>
- */
- private static boolean isGNUStringTable(final String name) {
- return GNU_STRING_TABLE_NAME.equals(name);
- }
-
- private void trackReadBytes(final long read) {
- count(read);
- if (read > 0) {
- offset += read;
- }
- }
-
/**
* Reads the GNU archive String Table.
*
@@ -396,15 +401,10 @@ public class ArArchiveInputStream extends ArchiveInputStream {
return new ArArchiveEntry(GNU_STRING_TABLE_NAME, bufflen);
}
- private static final String GNU_LONGNAME_PATTERN = "^/\\d+";
-
- /**
- * Does the name look like it is a long name (or a name containing
- * spaces) as encoded by SVR4/GNU ar?
- *
- * @see #isGNUStringTable
- */
- private boolean isGNULongName(final String name) {
- return name != null && name.matches(GNU_LONGNAME_PATTERN);
+ private void trackReadBytes(final long read) {
+ count(read);
+ if (read > 0) {
+ offset += read;
+ }
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/ar/ArArchiveOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/ar/ArArchiveOutputStream.java
index 62c07631..bd5bfed3 100644
--- a/src/main/java/org/apache/commons/compress/archivers/ar/ArArchiveOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/ar/ArArchiveOutputStream.java
@@ -56,20 +56,18 @@ public class ArArchiveOutputStream extends ArchiveOutputStream {
}
/**
- * Set the long file mode.
- * This can be LONGFILE_ERROR(0) or LONGFILE_BSD(1).
- * This specifies the treatment of long file names (names >= 16).
- * Default is LONGFILE_ERROR.
- * @param longFileMode the mode to use
- * @since 1.3
+ * Calls finish if necessary, and then closes the OutputStream
*/
- public void setLongFileMode(final int longFileMode) {
- this.longFileMode = longFileMode;
- }
-
- private void writeArchiveHeader() throws IOException {
- final byte [] header = ArchiveUtils.toAsciiBytes(ArArchiveEntry.HEADER);
- out.write(header);
+ @Override
+ public void close() throws IOException {
+ try {
+ if (!finished) {
+ finish();
+ }
+ } finally {
+ out.close();
+ prevEntry = null;
+ }
}
@Override
@@ -86,6 +84,51 @@ public class ArArchiveOutputStream extends ArchiveOutputStream {
haveUnclosedEntry = false;
}
+ @Override
+ public ArchiveEntry createArchiveEntry(final File inputFile, final String entryName)
+ throws IOException {
+ if (finished) {
+ throw new IOException("Stream has already been finished");
+ }
+ return new ArArchiveEntry(inputFile, entryName);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 1.21
+ */
+ @Override
+ public ArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
+ if (finished) {
+ throw new IOException("Stream has already been finished");
+ }
+ return new ArArchiveEntry(inputPath, entryName, options);
+ }
+
+ private long fill(final long pOffset, final long pNewOffset, final char pFill) throws IOException {
+ final long diff = pNewOffset - pOffset;
+
+ if (diff > 0) {
+ for (int i = 0; i < diff; i++) {
+ write(pFill);
+ }
+ }
+
+ return pNewOffset;
+ }
+
+ @Override
+ public void finish() throws IOException {
+ if(haveUnclosedEntry) {
+ throw new IOException("This archive contains unclosed entries.");
+ }
+ if(finished) {
+ throw new IOException("This archive has already been finished");
+ }
+ finished = true;
+ }
+
@Override
public void putArchiveEntry(final ArchiveEntry pEntry) throws IOException {
if(finished) {
@@ -113,16 +156,23 @@ public class ArArchiveOutputStream extends ArchiveOutputStream {
haveUnclosedEntry = true;
}
- private long fill(final long pOffset, final long pNewOffset, final char pFill) throws IOException {
- final long diff = pNewOffset - pOffset;
-
- if (diff > 0) {
- for (int i = 0; i < diff; i++) {
- write(pFill);
- }
- }
+ /**
+ * Set the long file mode.
+ * This can be LONGFILE_ERROR(0) or LONGFILE_BSD(1).
+ * This specifies the treatment of long file names (names >= 16).
+ * Default is LONGFILE_ERROR.
+ * @param longFileMode the mode to use
+ * @since 1.3
+ */
+ public void setLongFileMode(final int longFileMode) {
+ this.longFileMode = longFileMode;
+ }
- return pNewOffset;
+ @Override
+ public void write(final byte[] b, final int off, final int len) throws IOException {
+ out.write(b, off, len);
+ count(len);
+ entryOffset += len;
}
private long write(final String data) throws IOException {
@@ -131,6 +181,11 @@ public class ArArchiveOutputStream extends ArchiveOutputStream {
return bytes.length;
}
+ private void writeArchiveHeader() throws IOException {
+ final byte [] header = ArchiveUtils.toAsciiBytes(ArArchiveEntry.HEADER);
+ out.write(header);
+ }
+
private void writeEntryHeader(final ArArchiveEntry pEntry) throws IOException {
long offset = 0;
@@ -195,59 +250,4 @@ public class ArArchiveOutputStream extends ArchiveOutputStream {
}
}
-
- @Override
- public void write(final byte[] b, final int off, final int len) throws IOException {
- out.write(b, off, len);
- count(len);
- entryOffset += len;
- }
-
- /**
- * Calls finish if necessary, and then closes the OutputStream
- */
- @Override
- public void close() throws IOException {
- try {
- if (!finished) {
- finish();
- }
- } finally {
- out.close();
- prevEntry = null;
- }
- }
-
- @Override
- public ArchiveEntry createArchiveEntry(final File inputFile, final String entryName)
- throws IOException {
- if (finished) {
- throw new IOException("Stream has already been finished");
- }
- return new ArArchiveEntry(inputFile, entryName);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 1.21
- */
- @Override
- public ArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
- if (finished) {
- throw new IOException("Stream has already been finished");
- }
- return new ArArchiveEntry(inputPath, entryName, options);
- }
-
- @Override
- public void finish() throws IOException {
- if(haveUnclosedEntry) {
- throw new IOException("This archive contains unclosed entries.");
- }
- if(finished) {
- throw new IOException("This archive has already been finished");
- }
- finished = true;
- }
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/arj/ArjArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/arj/ArjArchiveEntry.java
index 5c966faf..0ba34a6d 100644
--- a/src/main/java/org/apache/commons/compress/archivers/arj/ArjArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/arj/ArjArchiveEntry.java
@@ -30,6 +30,24 @@ import org.apache.commons.compress.archivers.zip.ZipUtil;
* @since 1.6
*/
public class ArjArchiveEntry implements ArchiveEntry {
+ /**
+ * The known values for HostOs.
+ */
+ public static class HostOs {
+ public static final int DOS = 0;
+ public static final int PRIMOS = 1;
+ public static final int UNIX = 2;
+ public static final int AMIGA = 3;
+ public static final int MAC_OS = 4;
+ public static final int OS_2 = 5;
+ public static final int APPLE_GS = 6;
+ public static final int ATARI_ST = 7;
+ public static final int NEXT = 8;
+ public static final int VAX_VMS = 9;
+ public static final int WIN95 = 10;
+ public static final int WIN32 = 11;
+ }
+
private final LocalFileHeader localFileHeader;
public ArjArchiveEntry() {
@@ -40,39 +58,25 @@ public class ArjArchiveEntry implements ArchiveEntry {
this.localFileHeader = localFileHeader;
}
- /**
- * Get this entry's name.
- *
- * <p>This method returns the raw name as it is stored inside of the archive.</p>
- *
- * @return This entry's name.
- */
@Override
- public String getName() {
- if ((localFileHeader.arjFlags & LocalFileHeader.Flags.PATHSYM) != 0) {
- return localFileHeader.name.replace("/",
- File.separator);
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
}
- return localFileHeader.name;
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final ArjArchiveEntry other = (ArjArchiveEntry) obj;
+ return localFileHeader.equals(other.localFileHeader);
}
/**
- * Get this entry's file size.
- *
- * @return This entry's file size.
- */
- @Override
- public long getSize() {
- return localFileHeader.originalSize;
- }
-
- /** True if the entry refers to a directory.
- *
- * @return True if the entry refers to a directory
+ * The operating system the archive has been created on.
+ * @see HostOs
+ * @return the host OS code
*/
- @Override
- public boolean isDirectory() {
- return localFileHeader.fileType == LocalFileHeader.FileTypes.DIRECTORY;
+ public int getHostOs() {
+ return localFileHeader.hostOS;
}
/**
@@ -97,6 +101,10 @@ public class ArjArchiveEntry implements ArchiveEntry {
return new Date(ts);
}
+ int getMethod() {
+ return localFileHeader.method;
+ }
+
/**
* File mode of this entry.
*
@@ -109,37 +117,40 @@ public class ArjArchiveEntry implements ArchiveEntry {
}
/**
- * File mode of this entry as Unix stat value.
+ * Get this entry's name.
*
- * <p>Will only be non-zero of the host os was UNIX.
+ * <p>This method returns the raw name as it is stored inside of the archive.</p>
*
- * @return the Unix mode
+ * @return This entry's name.
*/
- public int getUnixMode() {
- return isHostOsUnix() ? getMode() : 0;
+ @Override
+ public String getName() {
+ if ((localFileHeader.arjFlags & LocalFileHeader.Flags.PATHSYM) != 0) {
+ return localFileHeader.name.replace("/",
+ File.separator);
+ }
+ return localFileHeader.name;
}
/**
- * The operating system the archive has been created on.
- * @see HostOs
- * @return the host OS code
+ * Get this entry's file size.
+ *
+ * @return This entry's file size.
*/
- public int getHostOs() {
- return localFileHeader.hostOS;
+ @Override
+ public long getSize() {
+ return localFileHeader.originalSize;
}
/**
- * Is the operating system the archive has been created on one
- * that is considered a UNIX OS by arj?
- * @return whether the operating system the archive has been
- * created on is considered a UNIX OS by arj
+ * File mode of this entry as Unix stat value.
+ *
+ * <p>Will only be non-zero of the host os was UNIX.
+ *
+ * @return the Unix mode
*/
- public boolean isHostOsUnix() {
- return getHostOs() == HostOs.UNIX || getHostOs() == HostOs.NEXT;
- }
-
- int getMethod() {
- return localFileHeader.method;
+ public int getUnixMode() {
+ return isHostOsUnix() ? getMode() : 0;
}
@Override
@@ -148,34 +159,23 @@ public class ArjArchiveEntry implements ArchiveEntry {
return name == null ? 0 : name.hashCode();
}
+ /** True if the entry refers to a directory.
+ *
+ * @return True if the entry refers to a directory
+ */
@Override
- public boolean equals(final Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- final ArjArchiveEntry other = (ArjArchiveEntry) obj;
- return localFileHeader.equals(other.localFileHeader);
+ public boolean isDirectory() {
+ return localFileHeader.fileType == LocalFileHeader.FileTypes.DIRECTORY;
}
/**
- * The known values for HostOs.
+ * Is the operating system the archive has been created on one
+ * that is considered a UNIX OS by arj?
+ * @return whether the operating system the archive has been
+ * created on is considered a UNIX OS by arj
*/
- public static class HostOs {
- public static final int DOS = 0;
- public static final int PRIMOS = 1;
- public static final int UNIX = 2;
- public static final int AMIGA = 3;
- public static final int MAC_OS = 4;
- public static final int OS_2 = 5;
- public static final int APPLE_GS = 6;
- public static final int ATARI_ST = 7;
- public static final int NEXT = 8;
- public static final int VAX_VMS = 9;
- public static final int WIN95 = 10;
- public static final int WIN32 = 11;
+ public boolean isHostOsUnix() {
+ return getHostOs() == HostOs.UNIX || getHostOs() == HostOs.NEXT;
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/arj/ArjArchiveInputStream.java b/src/main/java/org/apache/commons/compress/archivers/arj/ArjArchiveInputStream.java
index 1c2bdc9b..28462171 100644
--- a/src/main/java/org/apache/commons/compress/archivers/arj/ArjArchiveInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/arj/ArjArchiveInputStream.java
@@ -46,12 +46,38 @@ import org.apache.commons.compress.utils.IOUtils;
public class ArjArchiveInputStream extends ArchiveInputStream {
private static final int ARJ_MAGIC_1 = 0x60;
private static final int ARJ_MAGIC_2 = 0xEA;
+ /**
+ * Checks if the signature matches what is expected for an arj file.
+ *
+ * @param signature
+ * the bytes to check
+ * @param length
+ * the number of bytes to check
+ * @return true, if this stream is an arj archive stream, false otherwise
+ */
+ public static boolean matches(final byte[] signature, final int length) {
+ return length >= 2 &&
+ (0xff & signature[0]) == ARJ_MAGIC_1 &&
+ (0xff & signature[1]) == ARJ_MAGIC_2;
+ }
private final DataInputStream in;
private final String charsetName;
private final MainHeader mainHeader;
private LocalFileHeader currentLocalFileHeader;
+
private InputStream currentInputStream;
+ /**
+ * Constructs the ArjInputStream, taking ownership of the inputStream that is passed in,
+ * and using the CP437 character encoding.
+ * @param inputStream the underlying stream, whose ownership is taken
+ * @throws ArchiveException if an exception occurs while reading
+ */
+ public ArjArchiveInputStream(final InputStream inputStream)
+ throws ArchiveException {
+ this(inputStream, "CP437");
+ }
+
/**
* Constructs the ArjInputStream, taking ownership of the inputStream that is passed in.
* @param inputStream the underlying stream, whose ownership is taken
@@ -76,15 +102,10 @@ public class ArjArchiveInputStream extends ArchiveInputStream {
}
}
- /**
- * Constructs the ArjInputStream, taking ownership of the inputStream that is passed in,
- * and using the CP437 character encoding.
- * @param inputStream the underlying stream, whose ownership is taken
- * @throws ArchiveException if an exception occurs while reading
- */
- public ArjArchiveInputStream(final InputStream inputStream)
- throws ArchiveException {
- this(inputStream, "CP437");
+ @Override
+ public boolean canReadEntryData(final ArchiveEntry ae) {
+ return ae instanceof ArjArchiveEntry
+ && ((ArjArchiveEntry) ae).getMethod() == LocalFileHeader.Methods.STORED;
}
@Override
@@ -92,10 +113,57 @@ public class ArjArchiveInputStream extends ArchiveInputStream {
in.close();
}
- private int read8(final DataInputStream dataIn) throws IOException {
- final int value = dataIn.readUnsignedByte();
- count(1);
- return value;
+ /**
+ * Gets the archive's comment.
+ * @return the archive's comment
+ */
+ public String getArchiveComment() {
+ return mainHeader.comment;
+ }
+
+ /**
+ * Gets the archive's recorded name.
+ * @return the archive's name
+ */
+ public String getArchiveName() {
+ return mainHeader.name;
+ }
+
+ @Override
+ public ArjArchiveEntry getNextEntry() throws IOException {
+ if (currentInputStream != null) {
+ // return value ignored as IOUtils.skip ensures the stream is drained completely
+ IOUtils.skip(currentInputStream, Long.MAX_VALUE);
+ currentInputStream.close();
+ currentLocalFileHeader = null;
+ currentInputStream = null;
+ }
+
+ currentLocalFileHeader = readLocalFileHeader();
+ if (currentLocalFileHeader != null) {
+ currentInputStream = new BoundedInputStream(in, currentLocalFileHeader.compressedSize);
+ if (currentLocalFileHeader.method == LocalFileHeader.Methods.STORED) {
+ currentInputStream = new CRC32VerifyingInputStream(currentInputStream,
+ currentLocalFileHeader.originalSize, currentLocalFileHeader.originalCrc32);
+ }
+ return new ArjArchiveEntry(currentLocalFileHeader);
+ }
+ currentInputStream = null;
+ return null;
+ }
+
+ @Override
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ if (len == 0) {
+ return 0;
+ }
+ if (currentLocalFileHeader == null) {
+ throw new IllegalStateException("No current arj entry");
+ }
+ if (currentLocalFileHeader.method != LocalFileHeader.Methods.STORED) {
+ throw new IOException("Unsupported compression method " + currentLocalFileHeader.method);
+ }
+ return currentInputStream.read(b, off, len);
}
private int read16(final DataInputStream dataIn) throws IOException {
@@ -110,24 +178,24 @@ public class ArjArchiveInputStream extends ArchiveInputStream {
return Integer.reverseBytes(value);
}
- private String readString(final DataInputStream dataIn) throws IOException {
- try (final ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
- int nextByte;
- while ((nextByte = dataIn.readUnsignedByte()) != 0) {
- buffer.write(nextByte);
- }
- return buffer.toString(Charsets.toCharset(charsetName).name());
- }
+ private int read8(final DataInputStream dataIn) throws IOException {
+ final int value = dataIn.readUnsignedByte();
+ count(1);
+ return value;
}
- private byte[] readRange(final InputStream in, final int len)
- throws IOException {
- final byte[] b = IOUtils.readRange(in, len);
- count(b.length);
- if (b.length < len) {
- throw new EOFException();
+ private void readExtraData(final int firstHeaderSize, final DataInputStream firstHeader,
+ final LocalFileHeader localFileHeader) throws IOException {
+ if (firstHeaderSize >= 33) {
+ localFileHeader.extendedFilePosition = read32(firstHeader);
+ if (firstHeaderSize >= 45) {
+ localFileHeader.dateTimeAccessed = read32(firstHeader);
+ localFileHeader.dateTimeCreated = read32(firstHeader);
+ localFileHeader.originalSizeEvenForVolumes = read32(firstHeader);
+ pushedBackBytes(12);
+ }
+ pushedBackBytes(4);
}
- return b;
}
private byte[] readHeader() throws IOException {
@@ -158,63 +226,6 @@ public class ArjArchiveInputStream extends ArchiveInputStream {
return basicHeaderBytes;
}
- private MainHeader readMainHeader() throws IOException {
- final byte[] basicHeaderBytes = readHeader();
- if (basicHeaderBytes == null) {
- throw new IOException("Archive ends without any headers");
- }
- final DataInputStream basicHeader = new DataInputStream(
- new ByteArrayInputStream(basicHeaderBytes));
-
- final int firstHeaderSize = basicHeader.readUnsignedByte();
- final byte[] firstHeaderBytes = readRange(basicHeader, firstHeaderSize - 1);
- pushedBackBytes(firstHeaderBytes.length);
-
- final DataInputStream firstHeader = new DataInputStream(
- new ByteArrayInputStream(firstHeaderBytes));
-
- final MainHeader hdr = new MainHeader();
- hdr.archiverVersionNumber = firstHeader.readUnsignedByte();
- hdr.minVersionToExtract = firstHeader.readUnsignedByte();
- hdr.hostOS = firstHeader.readUnsignedByte();
- hdr.arjFlags = firstHeader.readUnsignedByte();
- hdr.securityVersion = firstHeader.readUnsignedByte();
- hdr.fileType = firstHeader.readUnsignedByte();
- hdr.reserved = firstHeader.readUnsignedByte();
- hdr.dateTimeCreated = read32(firstHeader);
- hdr.dateTimeModified = read32(firstHeader);
- hdr.archiveSize = 0xffffFFFFL & read32(firstHeader);
- hdr.securityEnvelopeFilePosition = read32(firstHeader);
- hdr.fileSpecPosition = read16(firstHeader);
- hdr.securityEnvelopeLength = read16(firstHeader);
- pushedBackBytes(20); // count has already counted them via readRange
- hdr.encryptionVersion = firstHeader.readUnsignedByte();
- hdr.lastChapter = firstHeader.readUnsignedByte();
-
- if (firstHeaderSize >= 33) {
- hdr.arjProtectionFactor = firstHeader.readUnsignedByte();
- hdr.arjFlags2 = firstHeader.readUnsignedByte();
- firstHeader.readUnsignedByte();
- firstHeader.readUnsignedByte();
- }
-
- hdr.name = readString(basicHeader);
- hdr.comment = readString(basicHeader);
-
- final int extendedHeaderSize = read16(in);
- if (extendedHeaderSize > 0) {
- hdr.extendedHeaderBytes = readRange(in, extendedHeaderSize);
- final long extendedHeaderCrc32 = 0xffffFFFFL & read32(in);
- final CRC32 crc32 = new CRC32();
- crc32.update(hdr.extendedHeaderBytes);
- if (extendedHeaderCrc32 != crc32.getValue()) {
- throw new IOException("Extended header CRC32 verification failure");
- }
- }
-
- return hdr;
- }
-
private LocalFileHeader readLocalFileHeader() throws IOException {
final byte[] basicHeaderBytes = readHeader();
if (basicHeaderBytes == null) {
@@ -269,91 +280,80 @@ public class ArjArchiveInputStream extends ArchiveInputStream {
}
}
- private void readExtraData(final int firstHeaderSize, final DataInputStream firstHeader,
- final LocalFileHeader localFileHeader) throws IOException {
- if (firstHeaderSize >= 33) {
- localFileHeader.extendedFilePosition = read32(firstHeader);
- if (firstHeaderSize >= 45) {
- localFileHeader.dateTimeAccessed = read32(firstHeader);
- localFileHeader.dateTimeCreated = read32(firstHeader);
- localFileHeader.originalSizeEvenForVolumes = read32(firstHeader);
- pushedBackBytes(12);
- }
- pushedBackBytes(4);
+ private MainHeader readMainHeader() throws IOException {
+ final byte[] basicHeaderBytes = readHeader();
+ if (basicHeaderBytes == null) {
+ throw new IOException("Archive ends without any headers");
}
- }
+ final DataInputStream basicHeader = new DataInputStream(
+ new ByteArrayInputStream(basicHeaderBytes));
- /**
- * Checks if the signature matches what is expected for an arj file.
- *
- * @param signature
- * the bytes to check
- * @param length
- * the number of bytes to check
- * @return true, if this stream is an arj archive stream, false otherwise
- */
- public static boolean matches(final byte[] signature, final int length) {
- return length >= 2 &&
- (0xff & signature[0]) == ARJ_MAGIC_1 &&
- (0xff & signature[1]) == ARJ_MAGIC_2;
- }
+ final int firstHeaderSize = basicHeader.readUnsignedByte();
+ final byte[] firstHeaderBytes = readRange(basicHeader, firstHeaderSize - 1);
+ pushedBackBytes(firstHeaderBytes.length);
- /**
- * Gets the archive's recorded name.
- * @return the archive's name
- */
- public String getArchiveName() {
- return mainHeader.name;
- }
+ final DataInputStream firstHeader = new DataInputStream(
+ new ByteArrayInputStream(firstHeaderBytes));
- /**
- * Gets the archive's comment.
- * @return the archive's comment
- */
- public String getArchiveComment() {
- return mainHeader.comment;
- }
+ final MainHeader hdr = new MainHeader();
+ hdr.archiverVersionNumber = firstHeader.readUnsignedByte();
+ hdr.minVersionToExtract = firstHeader.readUnsignedByte();
+ hdr.hostOS = firstHeader.readUnsignedByte();
+ hdr.arjFlags = firstHeader.readUnsignedByte();
+ hdr.securityVersion = firstHeader.readUnsignedByte();
+ hdr.fileType = firstHeader.readUnsignedByte();
+ hdr.reserved = firstHeader.readUnsignedByte();
+ hdr.dateTimeCreated = read32(firstHeader);
+ hdr.dateTimeModified = read32(firstHeader);
+ hdr.archiveSize = 0xffffFFFFL & read32(firstHeader);
+ hdr.securityEnvelopeFilePosition = read32(firstHeader);
+ hdr.fileSpecPosition = read16(firstHeader);
+ hdr.securityEnvelopeLength = read16(firstHeader);
+ pushedBackBytes(20); // count has already counted them via readRange
+ hdr.encryptionVersion = firstHeader.readUnsignedByte();
+ hdr.lastChapter = firstHeader.readUnsignedByte();
- @Override
- public ArjArchiveEntry getNextEntry() throws IOException {
- if (currentInputStream != null) {
- // return value ignored as IOUtils.skip ensures the stream is drained completely
- IOUtils.skip(currentInputStream, Long.MAX_VALUE);
- currentInputStream.close();
- currentLocalFileHeader = null;
- currentInputStream = null;
+ if (firstHeaderSize >= 33) {
+ hdr.arjProtectionFactor = firstHeader.readUnsignedByte();
+ hdr.arjFlags2 = firstHeader.readUnsignedByte();
+ firstHeader.readUnsignedByte();
+ firstHeader.readUnsignedByte();
}
- currentLocalFileHeader = readLocalFileHeader();
- if (currentLocalFileHeader != null) {
- currentInputStream = new BoundedInputStream(in, currentLocalFileHeader.compressedSize);
- if (currentLocalFileHeader.method == LocalFileHeader.Methods.STORED) {
- currentInputStream = new CRC32VerifyingInputStream(currentInputStream,
- currentLocalFileHeader.originalSize, currentLocalFileHeader.originalCrc32);
+ hdr.name = readString(basicHeader);
+ hdr.comment = readString(basicHeader);
+
+ final int extendedHeaderSize = read16(in);
+ if (extendedHeaderSize > 0) {
+ hdr.extendedHeaderBytes = readRange(in, extendedHeaderSize);
+ final long extendedHeaderCrc32 = 0xffffFFFFL & read32(in);
+ final CRC32 crc32 = new CRC32();
+ crc32.update(hdr.extendedHeaderBytes);
+ if (extendedHeaderCrc32 != crc32.getValue()) {
+ throw new IOException("Extended header CRC32 verification failure");
}
- return new ArjArchiveEntry(currentLocalFileHeader);
}
- currentInputStream = null;
- return null;
- }
- @Override
- public boolean canReadEntryData(final ArchiveEntry ae) {
- return ae instanceof ArjArchiveEntry
- && ((ArjArchiveEntry) ae).getMethod() == LocalFileHeader.Methods.STORED;
+ return hdr;
}
- @Override
- public int read(final byte[] b, final int off, final int len) throws IOException {
- if (len == 0) {
- return 0;
- }
- if (currentLocalFileHeader == null) {
- throw new IllegalStateException("No current arj entry");
+ private byte[] readRange(final InputStream in, final int len)
+ throws IOException {
+ final byte[] b = IOUtils.readRange(in, len);
+ count(b.length);
+ if (b.length < len) {
+ throw new EOFException();
}
- if (currentLocalFileHeader.method != LocalFileHeader.Methods.STORED) {
- throw new IOException("Unsupported compression method " + currentLocalFileHeader.method);
+ return b;
+ }
+
+ private String readString(final DataInputStream dataIn) throws IOException {
+ try (final ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
+ int nextByte;
+ while ((nextByte = dataIn.readUnsignedByte()) != 0) {
+ buffer.write(nextByte);
+ }
+ return buffer.toString(Charsets.toCharset(charsetName).name());
}
- return currentInputStream.read(b, off, len);
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/arj/LocalFileHeader.java b/src/main/java/org/apache/commons/compress/archivers/arj/LocalFileHeader.java
index d33bb6c5..c3bba4a2 100644
--- a/src/main/java/org/apache/commons/compress/archivers/arj/LocalFileHeader.java
+++ b/src/main/java/org/apache/commons/compress/archivers/arj/LocalFileHeader.java
@@ -21,6 +21,30 @@ import java.util.Arrays;
import java.util.Objects;
class LocalFileHeader {
+ static class FileTypes {
+ static final int BINARY = 0;
+ static final int SEVEN_BIT_TEXT = 1;
+ static final int COMMENT_HEADER = 2;
+ static final int DIRECTORY = 3;
+ static final int VOLUME_LABEL = 4;
+ static final int CHAPTER_LABEL = 5;
+ }
+ static class Flags {
+ static final int GARBLED = 0x01;
+ static final int VOLUME = 0x04;
+ static final int EXTFILE = 0x08;
+ static final int PATHSYM = 0x10;
+ static final int BACKUP = 0x20;
+ }
+ static class Methods {
+ static final int STORED = 0;
+ static final int COMPRESSED_MOST = 1;
+ static final int COMPRESSED = 2;
+ static final int COMPRESSED_FASTER = 3;
+ static final int COMPRESSED_FASTEST = 4;
+ static final int NO_DATA_NO_CRC = 8;
+ static final int NO_DATA = 9;
+ }
int archiverVersionNumber;
int minVersionToExtract;
int hostOS;
@@ -33,45 +57,60 @@ class LocalFileHeader {
long originalSize;
long originalCrc32;
int fileSpecPosition;
+
int fileAccessMode;
int firstChapter;
int lastChapter;
-
int extendedFilePosition;
+
int dateTimeAccessed;
int dateTimeCreated;
+
int originalSizeEvenForVolumes;
String name;
+
String comment;
byte[][] extendedHeaders;
- static class Flags {
- static final int GARBLED = 0x01;
- static final int VOLUME = 0x04;
- static final int EXTFILE = 0x08;
- static final int PATHSYM = 0x10;
- static final int BACKUP = 0x20;
- }
-
- static class FileTypes {
- static final int BINARY = 0;
- static final int SEVEN_BIT_TEXT = 1;
- static final int COMMENT_HEADER = 2;
- static final int DIRECTORY = 3;
- static final int VOLUME_LABEL = 4;
- static final int CHAPTER_LABEL = 5;
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final LocalFileHeader other = (LocalFileHeader) obj;
+ return
+ archiverVersionNumber == other.archiverVersionNumber &&
+ minVersionToExtract == other.minVersionToExtract &&
+ hostOS == other.hostOS &&
+ arjFlags == other.arjFlags &&
+ method == other.method &&
+ fileType == other.fileType &&
+ reserved == other.reserved &&
+ dateTimeModified == other.dateTimeModified &&
+ compressedSize == other.compressedSize &&
+ originalSize == other.originalSize &&
+ originalCrc32 == other.originalCrc32 &&
+ fileSpecPosition == other.fileSpecPosition &&
+ fileAccessMode == other.fileAccessMode &&
+ firstChapter == other.firstChapter &&
+ lastChapter == other.lastChapter &&
+ extendedFilePosition == other.extendedFilePosition &&
+ dateTimeAccessed == other.dateTimeAccessed &&
+ dateTimeCreated == other.dateTimeCreated &&
+ originalSizeEvenForVolumes == other.originalSizeEvenForVolumes &&
+ Objects.equals(name, other.name) &&
+ Objects.equals(comment, other.comment) &&
+ Arrays.deepEquals(extendedHeaders, other.extendedHeaders);
}
- static class Methods {
- static final int STORED = 0;
- static final int COMPRESSED_MOST = 1;
- static final int COMPRESSED = 2;
- static final int COMPRESSED_FASTER = 3;
- static final int COMPRESSED_FASTEST = 4;
- static final int NO_DATA_NO_CRC = 8;
- static final int NO_DATA = 9;
+ @Override
+ public int hashCode() {
+ return name == null ? 0 : name.hashCode();
}
@Override
@@ -125,43 +164,4 @@ class LocalFileHeader {
return builder.toString();
}
- @Override
- public int hashCode() {
- return name == null ? 0 : name.hashCode();
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- final LocalFileHeader other = (LocalFileHeader) obj;
- return
- archiverVersionNumber == other.archiverVersionNumber &&
- minVersionToExtract == other.minVersionToExtract &&
- hostOS == other.hostOS &&
- arjFlags == other.arjFlags &&
- method == other.method &&
- fileType == other.fileType &&
- reserved == other.reserved &&
- dateTimeModified == other.dateTimeModified &&
- compressedSize == other.compressedSize &&
- originalSize == other.originalSize &&
- originalCrc32 == other.originalCrc32 &&
- fileSpecPosition == other.fileSpecPosition &&
- fileAccessMode == other.fileAccessMode &&
- firstChapter == other.firstChapter &&
- lastChapter == other.lastChapter &&
- extendedFilePosition == other.extendedFilePosition &&
- dateTimeAccessed == other.dateTimeAccessed &&
- dateTimeCreated == other.dateTimeCreated &&
- originalSizeEvenForVolumes == other.originalSizeEvenForVolumes &&
- Objects.equals(name, other.name) &&
- Objects.equals(comment, other.comment) &&
- Arrays.deepEquals(extendedHeaders, other.extendedHeaders);
- }
-
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/arj/MainHeader.java b/src/main/java/org/apache/commons/compress/archivers/arj/MainHeader.java
index 2dba92ee..af4101cd 100644
--- a/src/main/java/org/apache/commons/compress/archivers/arj/MainHeader.java
+++ b/src/main/java/org/apache/commons/compress/archivers/arj/MainHeader.java
@@ -20,27 +20,6 @@ package org.apache.commons.compress.archivers.arj;
import java.util.Arrays;
class MainHeader {
- int archiverVersionNumber;
- int minVersionToExtract;
- int hostOS;
- int arjFlags;
- int securityVersion;
- int fileType;
- int reserved;
- int dateTimeCreated;
- int dateTimeModified;
- long archiveSize;
- int securityEnvelopeFilePosition;
- int fileSpecPosition;
- int securityEnvelopeLength;
- int encryptionVersion;
- int lastChapter;
- int arjProtectionFactor;
- int arjFlags2;
- String name;
- String comment;
- byte[] extendedHeaderBytes;
-
static class Flags {
static final int GARBLED = 0x01;
static final int OLD_SECURED_NEW_ANSI_PAGE = 0x02;
@@ -51,7 +30,6 @@ class MainHeader {
static final int SECURED = 0x40;
static final int ALTNAME = 0x80;
}
-
static class HostOS {
static final int MS_DOS = 0;
static final int PRIMOS = 1;
@@ -66,6 +44,28 @@ class MainHeader {
static final int WIN95 = 10;
static final int WIN32 = 11;
}
+ int archiverVersionNumber;
+ int minVersionToExtract;
+ int hostOS;
+ int arjFlags;
+ int securityVersion;
+ int fileType;
+ int reserved;
+ int dateTimeCreated;
+ int dateTimeModified;
+ long archiveSize;
+ int securityEnvelopeFilePosition;
+ int fileSpecPosition;
+ int securityEnvelopeLength;
+ int encryptionVersion;
+ int lastChapter;
+ int arjProtectionFactor;
+ int arjFlags2;
+ String name;
+
+ String comment;
+
+ byte[] extendedHeaderBytes;
@Override
public String toString() {
diff --git a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveEntry.java
index 3b61a4e9..0b7f3b1b 100644
--- a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveEntry.java
@@ -196,6 +196,37 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
private long uid;
+ /**
+ * Creates a CpioArchiveEntry with a specified name for a
+ * specified file. The format of this entry will be the new
+ * format.
+ *
+ * @param inputFile
+ * The file to gather information from.
+ * @param entryName
+ * The name of this entry.
+ */
+ public CpioArchiveEntry(final File inputFile, final String entryName) {
+ this(FORMAT_NEW, inputFile, entryName);
+ }
+
+ /**
+ * Creates a CpioArchiveEntry with a specified name for a
+ * specified file. The format of this entry will be the new
+ * format.
+ *
+ * @param inputPath
+ * The file to gather information from.
+ * @param entryName
+ * The name of this entry.
+ * @param options options indicating how symbolic links are handled.
+ * @throws IOException if an I/O error occurs
+ * @since 1.21
+ */
+ public CpioArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
+ this(FORMAT_NEW, inputPath, entryName, options);
+ }
+
/**
* Creates a CpioArchiveEntry with a specified format.
*
@@ -234,111 +265,6 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
this.fileFormat = format;
}
- /**
- * Creates a CpioArchiveEntry with a specified name. The format of
- * this entry will be the new format.
- *
- * @param name
- * The name of this entry.
- */
- public CpioArchiveEntry(final String name) {
- this(FORMAT_NEW, name);
- }
-
- /**
- * Creates a CpioArchiveEntry with a specified name.
- *
- * @param format
- * The cpio format for this entry.
- * @param name
- * The name of this entry.
- * <p>
- * Possible format values are:
- * <pre>
- * CpioConstants.FORMAT_NEW
- * CpioConstants.FORMAT_NEW_CRC
- * CpioConstants.FORMAT_OLD_BINARY
- * CpioConstants.FORMAT_OLD_ASCII
- * </pre>
- *
- * @since 1.1
- */
- public CpioArchiveEntry(final short format, final String name) {
- this(format);
- this.name = name;
- }
-
- /**
- * Creates a CpioArchiveEntry with a specified name. The format of
- * this entry will be the new format.
- *
- * @param name
- * The name of this entry.
- * @param size
- * The size of this entry
- */
- public CpioArchiveEntry(final String name, final long size) {
- this(name);
- this.setSize(size);
- }
-
- /**
- * Creates a CpioArchiveEntry with a specified name.
- *
- * @param format
- * The cpio format for this entry.
- * @param name
- * The name of this entry.
- * @param size
- * The size of this entry
- * <p>
- * Possible format values are:
- * <pre>
- * CpioConstants.FORMAT_NEW
- * CpioConstants.FORMAT_NEW_CRC
- * CpioConstants.FORMAT_OLD_BINARY
- * CpioConstants.FORMAT_OLD_ASCII
- * </pre>
- *
- * @since 1.1
- */
- public CpioArchiveEntry(final short format, final String name,
- final long size) {
- this(format, name);
- this.setSize(size);
- }
-
- /**
- * Creates a CpioArchiveEntry with a specified name for a
- * specified file. The format of this entry will be the new
- * format.
- *
- * @param inputFile
- * The file to gather information from.
- * @param entryName
- * The name of this entry.
- */
- public CpioArchiveEntry(final File inputFile, final String entryName) {
- this(FORMAT_NEW, inputFile, entryName);
- }
-
- /**
- * Creates a CpioArchiveEntry with a specified name for a
- * specified file. The format of this entry will be the new
- * format.
- *
- * @param inputPath
- * The file to gather information from.
- * @param entryName
- * The name of this entry.
- * @param options options indicating how symbolic links are handled.
- * @throws IOException if an I/O error occurs
- * @since 1.21
- */
- public CpioArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
- this(FORMAT_NEW, inputPath, entryName, options);
- }
-
/**
* Creates a CpioArchiveEntry with a specified name for a
* specified file.
@@ -412,6 +338,80 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
setTime(Files.getLastModifiedTime(inputPath, options));
}
+ /**
+ * Creates a CpioArchiveEntry with a specified name.
+ *
+ * @param format
+ * The cpio format for this entry.
+ * @param name
+ * The name of this entry.
+ * <p>
+ * Possible format values are:
+ * <pre>
+ * CpioConstants.FORMAT_NEW
+ * CpioConstants.FORMAT_NEW_CRC
+ * CpioConstants.FORMAT_OLD_BINARY
+ * CpioConstants.FORMAT_OLD_ASCII
+ * </pre>
+ *
+ * @since 1.1
+ */
+ public CpioArchiveEntry(final short format, final String name) {
+ this(format);
+ this.name = name;
+ }
+
+ /**
+ * Creates a CpioArchiveEntry with a specified name.
+ *
+ * @param format
+ * The cpio format for this entry.
+ * @param name
+ * The name of this entry.
+ * @param size
+ * The size of this entry
+ * <p>
+ * Possible format values are:
+ * <pre>
+ * CpioConstants.FORMAT_NEW
+ * CpioConstants.FORMAT_NEW_CRC
+ * CpioConstants.FORMAT_OLD_BINARY
+ * CpioConstants.FORMAT_OLD_ASCII
+ * </pre>
+ *
+ * @since 1.1
+ */
+ public CpioArchiveEntry(final short format, final String name,
+ final long size) {
+ this(format, name);
+ this.setSize(size);
+ }
+
+ /**
+ * Creates a CpioArchiveEntry with a specified name. The format of
+ * this entry will be the new format.
+ *
+ * @param name
+ * The name of this entry.
+ */
+ public CpioArchiveEntry(final String name) {
+ this(FORMAT_NEW, name);
+ }
+
+ /**
+ * Creates a CpioArchiveEntry with a specified name. The format of
+ * this entry will be the new format.
+ *
+ * @param name
+ * The name of this entry.
+ * @param size
+ * The size of this entry
+ */
+ public CpioArchiveEntry(final String name, final long size) {
+ this(name);
+ this.setSize(size);
+ }
+
/**
* Checks if the method is allowed for the defined format.
*/
@@ -430,6 +430,33 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
}
}
+ /* (non-Javadoc)
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final CpioArchiveEntry other = (CpioArchiveEntry) obj;
+ if (name == null) {
+ return other.name == null;
+ }
+ return name.equals(other.name);
+ }
+
+ /**
+ * Gets the alignment boundary for this CPIO format
+ *
+ * @return Returns the aligment boundary (0, 2, 4) in bytes
+ */
+ public int getAlignmentBoundary() {
+ return this.alignmentBoundary;
+ }
+
/**
* Gets the checksum.
* Only supported for the new formats.
@@ -442,6 +469,23 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
return this.chksum & 0xFFFFFFFFL;
}
+ /**
+ * Gets the number of bytes needed to pad the data to the alignment boundary.
+ *
+ * @return the number of bytes needed to pad the data (0,1,2,3)
+ */
+ public int getDataPadCount() {
+ if (this.alignmentBoundary == 0) {
+ return 0;
+ }
+ final long size = this.filesize;
+ final int remain = (int) (size % this.alignmentBoundary);
+ if (remain > 0) {
+ return this.alignmentBoundary - remain;
+ }
+ return 0;
+ }
+
/**
* Gets the device id.
*
@@ -479,17 +523,6 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
return this.min;
}
- /**
- * Gets the filesize.
- *
- * @return Returns the filesize.
- * @see org.apache.commons.compress.archivers.ArchiveEntry#getSize()
- */
- @Override
- public long getSize() {
- return this.filesize;
- }
-
/**
* Gets the format for this entry.
*
@@ -508,24 +541,6 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
return this.gid;
}
- /**
- * Gets the header size for this CPIO format
- *
- * @return Returns the header size in bytes.
- */
- public int getHeaderSize() {
- return this.headerSize;
- }
-
- /**
- * Gets the alignment boundary for this CPIO format
- *
- * @return Returns the aligment boundary (0, 2, 4) in bytes
- */
- public int getAlignmentBoundary() {
- return this.alignmentBoundary;
- }
-
/**
* Gets the number of bytes needed to pad the header to the alignment boundary.
*
@@ -583,20 +598,12 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
}
/**
- * Gets the number of bytes needed to pad the data to the alignment boundary.
+ * Gets the header size for this CPIO format
*
- * @return the number of bytes needed to pad the data (0,1,2,3)
+ * @return Returns the header size in bytes.
*/
- public int getDataPadCount() {
- if (this.alignmentBoundary == 0) {
- return 0;
- }
- final long size = this.filesize;
- final int remain = (int) (size % this.alignmentBoundary);
- if (remain > 0) {
- return this.alignmentBoundary - remain;
- }
- return 0;
+ public int getHeaderSize() {
+ return this.headerSize;
}
/**
@@ -608,6 +615,11 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
return this.inode;
}
+ @Override
+ public Date getLastModifiedDate() {
+ return new Date(1000 * getTime());
+ }
+
/**
* Gets the mode of this entry (e.g. directory, regular file).
*
@@ -677,6 +689,17 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
return this.rmin;
}
+ /**
+ * Gets the filesize.
+ *
+ * @return Returns the filesize.
+ * @see org.apache.commons.compress.archivers.ArchiveEntry#getSize()
+ */
+ @Override
+ public long getSize() {
+ return this.filesize;
+ }
+
/**
* Gets the time in seconds.
*
@@ -686,11 +709,6 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
return this.mtime;
}
- @Override
- public Date getLastModifiedDate() {
- return new Date(1000 * getTime());
- }
-
/**
* Gets the user id.
*
@@ -700,6 +718,14 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
return this.uid;
}
+ /* (non-Javadoc)
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+
/**
* Checks if this entry represents a block device.
*
@@ -821,19 +847,6 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
this.min = min;
}
- /**
- * Sets the filesize.
- *
- * @param size
- * The filesize to set.
- */
- public void setSize(final long size) {
- if (size < 0 || size > 0xFFFFFFFFL) {
- throw new IllegalArgumentException("Invalid entry size <" + size + ">");
- }
- this.filesize = size;
- }
-
/**
* Sets the group id.
*
@@ -945,13 +958,16 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
}
/**
- * Sets the time in seconds.
+ * Sets the filesize.
*
- * @param time
- * The time to set.
+ * @param size
+ * The filesize to set.
*/
- public void setTime(final long time) {
- this.mtime = time;
+ public void setSize(final long size) {
+ if (size < 0 || size > 0xFFFFFFFFL) {
+ throw new IllegalArgumentException("Invalid entry size <" + size + ">");
+ }
+ this.filesize = size;
}
/**
@@ -964,6 +980,16 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
this.mtime = time.to(TimeUnit.SECONDS);
}
+ /**
+ * Sets the time in seconds.
+ *
+ * @param time
+ * The time to set.
+ */
+ public void setTime(final long time) {
+ this.mtime = time;
+ }
+
/**
* Sets the user id.
*
@@ -973,30 +999,4 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
public void setUID(final long uid) {
this.uid = uid;
}
-
- /* (non-Javadoc)
- * @see Object#hashCode()
- */
- @Override
- public int hashCode() {
- return Objects.hash(name);
- }
-
- /* (non-Javadoc)
- * @see Object#equals(Object)
- */
- @Override
- public boolean equals(final Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- final CpioArchiveEntry other = (CpioArchiveEntry) obj;
- if (name == null) {
- return other.name == null;
- }
- return name.equals(other.name);
- }
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStream.java b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStream.java
index 63ad8024..222f3144 100644
--- a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStream.java
@@ -67,6 +67,66 @@ import org.apache.commons.compress.utils.IOUtils;
public class CpioArchiveInputStream extends ArchiveInputStream implements
CpioConstants {
+ /**
+ * Checks if the signature matches one of the following magic values:
+ *
+ * Strings:
+ *
+ * "070701" - MAGIC_NEW
+ * "070702" - MAGIC_NEW_CRC
+ * "070707" - MAGIC_OLD_ASCII
+ *
+ * Octal Binary value:
+ *
+ * 070707 - MAGIC_OLD_BINARY (held as a short) = 0x71C7 or 0xC771
+ * @param signature data to match
+ * @param length length of data
+ * @return whether the buffer seems to contain CPIO data
+ */
+ public static boolean matches(final byte[] signature, final int length) {
+ if (length < 6) {
+ return false;
+ }
+
+ // Check binary values
+ if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7) {
+ return true;
+ }
+ if (signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) {
+ return true;
+ }
+
+ // Check Ascii (String) values
+ // 3037 3037 30nn
+ if (signature[0] != 0x30) {
+ return false;
+ }
+ if (signature[1] != 0x37) {
+ return false;
+ }
+ if (signature[2] != 0x30) {
+ return false;
+ }
+ if (signature[3] != 0x37) {
+ return false;
+ }
+ if (signature[4] != 0x30) {
+ return false;
+ }
+ // Check last byte
+ if (signature[5] == 0x31) {
+ return true;
+ }
+ if (signature[5] == 0x32) {
+ return true;
+ }
+ if (signature[5] == 0x37) {
+ return true;
+ }
+
+ return false;
+ }
+
private boolean closed;
private CpioArchiveEntry entry;
@@ -80,10 +140,10 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
private long crc;
private final InputStream in;
-
// cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
private final byte[] twoBytesBuf = new byte[2];
private final byte[] fourBytesBuf = new byte[4];
+
private final byte[] sixBytesBuf = new byte[6];
private final int blockSize;
@@ -108,21 +168,6 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
this(in, BLOCK_SIZE, CharsetNames.US_ASCII);
}
- /**
- * Construct the cpio input stream with a blocksize of {@link
- * CpioConstants#BLOCK_SIZE BLOCK_SIZE}.
- *
- * @param in
- * The cpio stream
- * @param encoding
- * The encoding of file names to expect - use null for
- * the platform's default.
- * @since 1.6
- */
- public CpioArchiveInputStream(final InputStream in, final String encoding) {
- this(in, BLOCK_SIZE, encoding);
- }
-
/**
* Construct the cpio input stream with a blocksize of {@link
* CpioConstants#BLOCK_SIZE BLOCK_SIZE} expecting ASCII file
@@ -161,6 +206,21 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
}
+ /**
+ * Construct the cpio input stream with a blocksize of {@link
+ * CpioConstants#BLOCK_SIZE BLOCK_SIZE}.
+ *
+ * @param in
+ * The cpio stream
+ * @param encoding
+ * The encoding of file names to expect - use null for
+ * the platform's default.
+ * @since 1.6
+ */
+ public CpioArchiveInputStream(final InputStream in, final String encoding) {
+ this(in, BLOCK_SIZE, encoding);
+ }
+
/**
* Returns 0 after EOF has reached for the current entry data, otherwise
* always return 1.
@@ -277,11 +337,9 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
return this.entry;
}
- private void skip(final int bytes) throws IOException{
- // bytes cannot be more than 3 bytes
- if (bytes > 0) {
- readFully(fourBytesBuf, 0, bytes);
- }
+ @Override
+ public ArchiveEntry getNextEntry() throws IOException {
+ return getNextCPIOEntry();
}
/**
@@ -344,24 +402,10 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
return tmpread;
}
- private final int readFully(final byte[] b, final int off, final int len)
- throws IOException {
- final int count = IOUtils.readFully(in, b, off, len);
- count(count);
- if (count < len) {
- throw new EOFException();
- }
- return count;
- }
-
- private final byte[] readRange(final int len)
+ private long readAsciiLong(final int length, final int radix)
throws IOException {
- final byte[] b = IOUtils.readRange(in, len);
- count(b.length);
- if (b.length < len) {
- throw new EOFException();
- }
- return b;
+ final byte[] tmpBuffer = readRange(length);
+ return Long.parseLong(ArchiveUtils.toAsciiString(tmpBuffer), radix);
}
private long readBinaryLong(final int length, final boolean swapHalfWord)
@@ -370,10 +414,23 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
return CpioUtil.byteArray2long(tmp, swapHalfWord);
}
- private long readAsciiLong(final int length, final int radix)
+ private String readCString(final int length) throws IOException {
+ // don't include trailing NUL in file name to decode
+ final byte[] tmpBuffer = readRange(length - 1);
+ if (this.in.read() == -1) {
+ throw new EOFException();
+ }
+ return zipEncoding.decode(tmpBuffer);
+ }
+
+ private final int readFully(final byte[] b, final int off, final int len)
throws IOException {
- final byte[] tmpBuffer = readRange(length);
- return Long.parseLong(ArchiveUtils.toAsciiString(tmpBuffer), radix);
+ final int count = IOUtils.readFully(in, b, off, len);
+ count(count);
+ if (count < len) {
+ throw new EOFException();
+ }
+ return count;
}
private CpioArchiveEntry readNewEntry(final boolean hasCrc)
@@ -487,13 +544,21 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
return ret;
}
- private String readCString(final int length) throws IOException {
- // don't include trailing NUL in file name to decode
- final byte[] tmpBuffer = readRange(length - 1);
- if (this.in.read() == -1) {
+ private final byte[] readRange(final int len)
+ throws IOException {
+ final byte[] b = IOUtils.readRange(in, len);
+ count(b.length);
+ if (b.length < len) {
throw new EOFException();
}
- return zipEncoding.decode(tmpBuffer);
+ return b;
+ }
+
+ private void skip(final int bytes) throws IOException{
+ // bytes cannot be more than 3 bytes
+ if (bytes > 0) {
+ readFully(fourBytesBuf, 0, bytes);
+ }
}
/**
@@ -531,11 +596,6 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
return total;
}
- @Override
- public ArchiveEntry getNextEntry() throws IOException {
- return getNextCPIOEntry();
- }
-
/**
* Skips the padding zeros written after the TRAILER!!! entry.
*/
@@ -551,64 +611,4 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
remainingBytes -= skipped;
}
}
-
- /**
- * Checks if the signature matches one of the following magic values:
- *
- * Strings:
- *
- * "070701" - MAGIC_NEW
- * "070702" - MAGIC_NEW_CRC
- * "070707" - MAGIC_OLD_ASCII
- *
- * Octal Binary value:
- *
- * 070707 - MAGIC_OLD_BINARY (held as a short) = 0x71C7 or 0xC771
- * @param signature data to match
- * @param length length of data
- * @return whether the buffer seems to contain CPIO data
- */
- public static boolean matches(final byte[] signature, final int length) {
- if (length < 6) {
- return false;
- }
-
- // Check binary values
- if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7) {
- return true;
- }
- if (signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) {
- return true;
- }
-
- // Check Ascii (String) values
- // 3037 3037 30nn
- if (signature[0] != 0x30) {
- return false;
- }
- if (signature[1] != 0x37) {
- return false;
- }
- if (signature[2] != 0x30) {
- return false;
- }
- if (signature[3] != 0x37) {
- return false;
- }
- if (signature[4] != 0x30) {
- return false;
- }
- // Check last byte
- if (signature[5] == 0x31) {
- return true;
- }
- if (signature[5] == 0x32) {
- return true;
- }
- if (signature[5] == 0x37) {
- return true;
- }
-
- return false;
- }
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveOutputStream.java
index 26faaff7..aaa777a2 100644
--- a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveOutputStream.java
@@ -100,6 +100,17 @@ public class CpioArchiveOutputStream extends ArchiveOutputStream implements
// the provided encoding (for unit tests)
final String encoding;
+ /**
+ * Construct the cpio output stream. The format for this CPIO stream is the
+ * "new" format using ASCII encoding for file names
+ *
+ * @param out
+ * The cpio stream
+ */
+ public CpioArchiveOutputStream(final OutputStream out) {
+ this(out, FORMAT_NEW);
+ }
+
/**
* Construct the cpio output stream with a specified format, a
* blocksize of {@link CpioConstants#BLOCK_SIZE BLOCK_SIZE} and
@@ -167,17 +178,6 @@ public class CpioArchiveOutputStream extends ArchiveOutputStream implements
this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
}
- /**
- * Construct the cpio output stream. The format for this CPIO stream is the
- * "new" format using ASCII encoding for file names
- *
- * @param out
- * The cpio stream
- */
- public CpioArchiveOutputStream(final OutputStream out) {
- this(out, FORMAT_NEW);
- }
-
/**
* Construct the cpio output stream. The format for this CPIO stream is the
* "new" format.
@@ -193,6 +193,101 @@ public class CpioArchiveOutputStream extends ArchiveOutputStream implements
this(out, FORMAT_NEW, BLOCK_SIZE, encoding);
}
+ /**
+ * Closes the CPIO output stream as well as the stream being filtered.
+ *
+ * @throws IOException
+ * if an I/O error has occurred or if a CPIO file error has
+ * occurred
+ */
+ @Override
+ public void close() throws IOException {
+ try {
+ if (!finished) {
+ finish();
+ }
+ } finally {
+ if (!this.closed) {
+ out.close();
+ this.closed = true;
+ }
+ }
+ }
+
+ /*(non-Javadoc)
+ *
+ * @see
+ * org.apache.commons.compress.archivers.ArchiveOutputStream#closeArchiveEntry
+ * ()
+ */
+ @Override
+ public void closeArchiveEntry() throws IOException {
+ if(finished) {
+ throw new IOException("Stream has already been finished");
+ }
+
+ ensureOpen();
+
+ if (entry == null) {
+ throw new IOException("Trying to close non-existent entry");
+ }
+
+ if (this.entry.getSize() != this.written) {
+ throw new IOException("Invalid entry size (expected "
+ + this.entry.getSize() + " but got " + this.written
+ + " bytes)");
+ }
+ pad(this.entry.getDataPadCount());
+ if (this.entry.getFormat() == FORMAT_NEW_CRC
+ && this.crc != this.entry.getChksum()) {
+ throw new IOException("CRC Error");
+ }
+ this.entry = null;
+ this.crc = 0;
+ this.written = 0;
+ }
+
+ /**
+ * Creates a new ArchiveEntry. The entryName must be an ASCII encoded string.
+ *
+ * @see org.apache.commons.compress.archivers.ArchiveOutputStream#createArchiveEntry(java.io.File, String)
+ */
+ @Override
+ public ArchiveEntry createArchiveEntry(final File inputFile, final String entryName)
+ throws IOException {
+ if(finished) {
+ throw new IOException("Stream has already been finished");
+ }
+ return new CpioArchiveEntry(inputFile, entryName);
+ }
+
+ /**
+ * Creates a new ArchiveEntry. The entryName must be an ASCII encoded string.
+ *
+ * @see org.apache.commons.compress.archivers.ArchiveOutputStream#createArchiveEntry(java.io.File, String)
+ */
+ @Override
+ public ArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options)
+ throws IOException {
+ if(finished) {
+ throw new IOException("Stream has already been finished");
+ }
+ return new CpioArchiveEntry(inputPath, entryName, options);
+ }
+
+ /**
+ * Encodes the given string using the configured encoding.
+ *
+ * @param str the String to write
+ * @throws IOException if the string couldn't be written
+ * @return result of encoding the string
+ */
+ private byte[] encode(final String str) throws IOException {
+ final ByteBuffer buf = zipEncoding.encode(str);
+ final int len = buf.limit() - buf.position();
+ return Arrays.copyOfRange(buf.array(), buf.arrayOffset(), buf.arrayOffset() + len);
+ }
+
/**
* Check to make sure that this stream has not been closed
*
@@ -205,6 +300,47 @@ public class CpioArchiveOutputStream extends ArchiveOutputStream implements
}
}
+ /**
+ * Finishes writing the contents of the CPIO output stream without closing
+ * the underlying stream. Use this method when applying multiple filters in
+ * succession to the same output stream.
+ *
+ * @throws IOException
+ * if an I/O exception has occurred or if a CPIO file error has
+ * occurred
+ */
+ @Override
+ public void finish() throws IOException {
+ ensureOpen();
+ if (finished) {
+ throw new IOException("This archive has already been finished");
+ }
+
+ if (this.entry != null) {
+ throw new IOException("This archive contains unclosed entries.");
+ }
+ this.entry = new CpioArchiveEntry(this.entryFormat);
+ this.entry.setName(CPIO_TRAILER);
+ this.entry.setNumberOfLinks(1);
+ writeHeader(this.entry);
+ closeArchiveEntry();
+
+ final int lengthOfLastBlock = (int) (getBytesWritten() % blockSize);
+ if (lengthOfLastBlock != 0) {
+ pad(blockSize - lengthOfLastBlock);
+ }
+
+ finished = true;
+ }
+
+ private void pad(final int count) throws IOException{
+ if (count > 0){
+ final byte[] buff = new byte[count];
+ out.write(buff);
+ count(count);
+ }
+ }
+
/**
* Begins writing a new CPIO file entry and positions the stream to the
* start of the entry data. Closes the current entry if still active. The
@@ -248,6 +384,92 @@ public class CpioArchiveOutputStream extends ArchiveOutputStream implements
this.written = 0;
}
+ /**
+ * Writes an array of bytes to the current CPIO entry data. This method will
+ * block until all the bytes are written.
+ *
+ * @param b
+ * the data to be written
+ * @param off
+ * the start offset in the data
+ * @param len
+ * the number of bytes that are written
+ * @throws IOException
+ * if an I/O error has occurred or if a CPIO file error has
+ * occurred
+ */
+ @Override
+ public void write(final byte[] b, final int off, final int len)
+ throws IOException {
+ ensureOpen();
+ if (off < 0 || len < 0 || off > b.length - len) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (len == 0) {
+ return;
+ }
+
+ if (this.entry == null) {
+ throw new IOException("No current CPIO entry");
+ }
+ if (this.written + len > this.entry.getSize()) {
+ throw new IOException("Attempt to write past end of STORED entry");
+ }
+ out.write(b, off, len);
+ this.written += len;
+ if (this.entry.getFormat() == FORMAT_NEW_CRC) {
+ for (int pos = 0; pos < len; pos++) {
+ this.crc += b[pos] & 0xFF;
+ this.crc &= 0xFFFFFFFFL;
+ }
+ }
+ count(len);
+ }
+
+ private void writeAsciiLong(final long number, final int length,
+ final int radix) throws IOException {
+ final StringBuilder tmp = new StringBuilder();
+ final String tmpStr;
+ if (radix == 16) {
+ tmp.append(Long.toHexString(number));
+ } else if (radix == 8) {
+ tmp.append(Long.toOctalString(number));
+ } else {
+ tmp.append(number);
+ }
+
+ if (tmp.length() <= length) {
+ final int insertLength = length - tmp.length();
+ for (int pos = 0; pos < insertLength; pos++) {
+ tmp.insert(0, "0");
+ }
+ tmpStr = tmp.toString();
+ } else {
+ tmpStr = tmp.substring(tmp.length() - length);
+ }
+ final byte[] b = ArchiveUtils.toAsciiBytes(tmpStr);
+ out.write(b);
+ count(b.length);
+ }
+
+ private void writeBinaryLong(final long number, final int length,
+ final boolean swapHalfWord) throws IOException {
+ final byte[] tmp = CpioUtil.long2byteArray(number, length, swapHalfWord);
+ out.write(tmp);
+ count(tmp.length);
+ }
+
+ /**
+ * Writes an encoded string to the stream followed by \0
+ * @param str the String to write
+ * @throws IOException if the string couldn't be written
+ */
+ private void writeCString(final byte[] str) throws IOException {
+ out.write(str);
+ out.write('\0');
+ count(str.length + 1);
+ }
+
private void writeHeader(final CpioArchiveEntry e) throws IOException {
switch (e.getFormat()) {
case FORMAT_NEW:
@@ -366,226 +588,4 @@ public class CpioArchiveOutputStream extends ArchiveOutputStream implements
pad(entry.getHeaderPadCount(name.length));
}
- /*(non-Javadoc)
- *
- * @see
- * org.apache.commons.compress.archivers.ArchiveOutputStream#closeArchiveEntry
- * ()
- */
- @Override
- public void closeArchiveEntry() throws IOException {
- if(finished) {
- throw new IOException("Stream has already been finished");
- }
-
- ensureOpen();
-
- if (entry == null) {
- throw new IOException("Trying to close non-existent entry");
- }
-
- if (this.entry.getSize() != this.written) {
- throw new IOException("Invalid entry size (expected "
- + this.entry.getSize() + " but got " + this.written
- + " bytes)");
- }
- pad(this.entry.getDataPadCount());
- if (this.entry.getFormat() == FORMAT_NEW_CRC
- && this.crc != this.entry.getChksum()) {
- throw new IOException("CRC Error");
- }
- this.entry = null;
- this.crc = 0;
- this.written = 0;
- }
-
- /**
- * Writes an array of bytes to the current CPIO entry data. This method will
- * block until all the bytes are written.
- *
- * @param b
- * the data to be written
- * @param off
- * the start offset in the data
- * @param len
- * the number of bytes that are written
- * @throws IOException
- * if an I/O error has occurred or if a CPIO file error has
- * occurred
- */
- @Override
- public void write(final byte[] b, final int off, final int len)
- throws IOException {
- ensureOpen();
- if (off < 0 || len < 0 || off > b.length - len) {
- throw new IndexOutOfBoundsException();
- }
- if (len == 0) {
- return;
- }
-
- if (this.entry == null) {
- throw new IOException("No current CPIO entry");
- }
- if (this.written + len > this.entry.getSize()) {
- throw new IOException("Attempt to write past end of STORED entry");
- }
- out.write(b, off, len);
- this.written += len;
- if (this.entry.getFormat() == FORMAT_NEW_CRC) {
- for (int pos = 0; pos < len; pos++) {
- this.crc += b[pos] & 0xFF;
- this.crc &= 0xFFFFFFFFL;
- }
- }
- count(len);
- }
-
- /**
- * Finishes writing the contents of the CPIO output stream without closing
- * the underlying stream. Use this method when applying multiple filters in
- * succession to the same output stream.
- *
- * @throws IOException
- * if an I/O exception has occurred or if a CPIO file error has
- * occurred
- */
- @Override
- public void finish() throws IOException {
- ensureOpen();
- if (finished) {
- throw new IOException("This archive has already been finished");
- }
-
- if (this.entry != null) {
- throw new IOException("This archive contains unclosed entries.");
- }
- this.entry = new CpioArchiveEntry(this.entryFormat);
- this.entry.setName(CPIO_TRAILER);
- this.entry.setNumberOfLinks(1);
- writeHeader(this.entry);
- closeArchiveEntry();
-
- final int lengthOfLastBlock = (int) (getBytesWritten() % blockSize);
- if (lengthOfLastBlock != 0) {
- pad(blockSize - lengthOfLastBlock);
- }
-
- finished = true;
- }
-
- /**
- * Closes the CPIO output stream as well as the stream being filtered.
- *
- * @throws IOException
- * if an I/O error has occurred or if a CPIO file error has
- * occurred
- */
- @Override
- public void close() throws IOException {
- try {
- if (!finished) {
- finish();
- }
- } finally {
- if (!this.closed) {
- out.close();
- this.closed = true;
- }
- }
- }
-
- private void pad(final int count) throws IOException{
- if (count > 0){
- final byte[] buff = new byte[count];
- out.write(buff);
- count(count);
- }
- }
-
- private void writeBinaryLong(final long number, final int length,
- final boolean swapHalfWord) throws IOException {
- final byte[] tmp = CpioUtil.long2byteArray(number, length, swapHalfWord);
- out.write(tmp);
- count(tmp.length);
- }
-
- private void writeAsciiLong(final long number, final int length,
- final int radix) throws IOException {
- final StringBuilder tmp = new StringBuilder();
- final String tmpStr;
- if (radix == 16) {
- tmp.append(Long.toHexString(number));
- } else if (radix == 8) {
- tmp.append(Long.toOctalString(number));
- } else {
- tmp.append(number);
- }
-
- if (tmp.length() <= length) {
- final int insertLength = length - tmp.length();
- for (int pos = 0; pos < insertLength; pos++) {
- tmp.insert(0, "0");
- }
- tmpStr = tmp.toString();
- } else {
- tmpStr = tmp.substring(tmp.length() - length);
- }
- final byte[] b = ArchiveUtils.toAsciiBytes(tmpStr);
- out.write(b);
- count(b.length);
- }
-
- /**
- * Encodes the given string using the configured encoding.
- *
- * @param str the String to write
- * @throws IOException if the string couldn't be written
- * @return result of encoding the string
- */
- private byte[] encode(final String str) throws IOException {
- final ByteBuffer buf = zipEncoding.encode(str);
- final int len = buf.limit() - buf.position();
- return Arrays.copyOfRange(buf.array(), buf.arrayOffset(), buf.arrayOffset() + len);
- }
-
- /**
- * Writes an encoded string to the stream followed by \0
- * @param str the String to write
- * @throws IOException if the string couldn't be written
- */
- private void writeCString(final byte[] str) throws IOException {
- out.write(str);
- out.write('\0');
- count(str.length + 1);
- }
-
- /**
- * Creates a new ArchiveEntry. The entryName must be an ASCII encoded string.
- *
- * @see org.apache.commons.compress.archivers.ArchiveOutputStream#createArchiveEntry(java.io.File, String)
- */
- @Override
- public ArchiveEntry createArchiveEntry(final File inputFile, final String entryName)
- throws IOException {
- if(finished) {
- throw new IOException("Stream has already been finished");
- }
- return new CpioArchiveEntry(inputFile, entryName);
- }
-
- /**
- * Creates a new ArchiveEntry. The entryName must be an ASCII encoded string.
- *
- * @see org.apache.commons.compress.archivers.ArchiveOutputStream#createArchiveEntry(java.io.File, String)
- */
- @Override
- public ArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options)
- throws IOException {
- if(finished) {
- throw new IOException("Stream has already been finished");
- }
- return new CpioArchiveEntry(inputPath, entryName, options);
- }
-
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioUtil.java b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioUtil.java
index b79650a3..ca0b3a80 100644
--- a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioUtil.java
+++ b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioUtil.java
@@ -25,13 +25,6 @@ package org.apache.commons.compress.archivers.cpio;
*/
class CpioUtil {
- /**
- * Extracts the file type bits from a mode.
- */
- static long fileType(final long mode) {
- return mode & CpioConstants.S_IFMT;
- }
-
/**
* Converts a byte array to a long. Halfwords can be swapped by setting
* swapHalfWord=true.
@@ -70,6 +63,13 @@ class CpioUtil {
return ret;
}
+ /**
+ * Extracts the file type bits from a mode.
+ */
+ static long fileType(final long mode) {
+ return mode & CpioConstants.S_IFMT;
+ }
+
/**
* Converts a long number to a byte array
* Halfwords can be swapped by setting swapHalfWord=true.
diff --git a/src/main/java/org/apache/commons/compress/archivers/dump/Dirent.java b/src/main/java/org/apache/commons/compress/archivers/dump/Dirent.java
index b8fd1ce0..30a522e1 100644
--- a/src/main/java/org/apache/commons/compress/archivers/dump/Dirent.java
+++ b/src/main/java/org/apache/commons/compress/archivers/dump/Dirent.java
@@ -50,6 +50,17 @@ class Dirent {
return ino;
}
+ /**
+ * Get name of directory entry.
+ *
+ * <p>This method returns the raw name as it is stored inside of the archive.</p>
+ *
+ * @return the directory name
+ */
+ String getName() {
+ return name;
+ }
+
/**
* Get ino of parent directory.
* @return the parent i-node
@@ -66,17 +77,6 @@ class Dirent {
return type;
}
- /**
- * Get name of directory entry.
- *
- * <p>This method returns the raw name as it is stored inside of the archive.</p>
- *
- * @return the directory name
- */
- String getName() {
- return name;
- }
-
/**
* @see Object#toString()
*/
diff --git a/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveConstants.java b/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveConstants.java
index 920a6230..2f2fbff4 100644
--- a/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveConstants.java
+++ b/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveConstants.java
@@ -22,20 +22,30 @@ package org.apache.commons.compress.archivers.dump;
* Various constants associated with dump archives.
*/
public final class DumpArchiveConstants {
- public static final int TP_SIZE = 1024;
- public static final int NTREC = 10;
- public static final int HIGH_DENSITY_NTREC = 32;
- public static final int OFS_MAGIC = 60011;
- public static final int NFS_MAGIC = 60012;
- public static final int FS_UFS2_MAGIC = 0x19540119;
- public static final int CHECKSUM = 84446;
- public static final int LBLSIZE = 16;
- public static final int NAMELEN = 64;
+ /**
+ * The type of compression.
+ */
+ public enum COMPRESSION_TYPE {
+ ZLIB(0),
+ BZLIB(1),
+ LZO(2);
- /* do not instantiate */
- private DumpArchiveConstants() {
- }
+ public static COMPRESSION_TYPE find(final int code) {
+ for (final COMPRESSION_TYPE t : values()) {
+ if (t.code == code) {
+ return t;
+ }
+ }
+
+ return null;
+ }
+
+ final int code;
+ COMPRESSION_TYPE(final int code) {
+ this.code = code;
+ }
+ }
/**
* The type of tape segment.
*/
@@ -47,12 +57,6 @@ public final class DumpArchiveConstants {
END(5),
CLRI(6);
- final int code;
-
- SEGMENT_TYPE(final int code) {
- this.code = code;
- }
-
public static SEGMENT_TYPE find(final int code) {
for (final SEGMENT_TYPE t : values()) {
if (t.code == code) {
@@ -62,30 +66,26 @@ public final class DumpArchiveConstants {
return null;
}
- }
-
- /**
- * The type of compression.
- */
- public enum COMPRESSION_TYPE {
- ZLIB(0),
- BZLIB(1),
- LZO(2);
final int code;
- COMPRESSION_TYPE(final int code) {
+ SEGMENT_TYPE(final int code) {
this.code = code;
}
+ }
+ public static final int TP_SIZE = 1024;
+ public static final int NTREC = 10;
+ public static final int HIGH_DENSITY_NTREC = 32;
+ public static final int OFS_MAGIC = 60011;
+ public static final int NFS_MAGIC = 60012;
+ public static final int FS_UFS2_MAGIC = 0x19540119;
+ public static final int CHECKSUM = 84446;
- public static COMPRESSION_TYPE find(final int code) {
- for (final COMPRESSION_TYPE t : values()) {
- if (t.code == code) {
- return t;
- }
- }
+ public static final int LBLSIZE = 16;
- return null;
- }
+ public static final int NAMELEN = 64;
+
+ /* do not instantiate */
+ private DumpArchiveConstants() {
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveEntry.java
index abea9995..c0b1ef19 100644
--- a/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveEntry.java
@@ -180,12 +180,188 @@ import org.apache.commons.compress.archivers.ArchiveEntry;
* @NotThreadSafe
*/
public class DumpArchiveEntry implements ArchiveEntry {
+ public enum PERMISSION {
+ SETUID(04000),
+ SETGUI(02000),
+ STICKY(01000),
+ USER_READ(00400),
+ USER_WRITE(00200),
+ USER_EXEC(00100),
+ GROUP_READ(00040),
+ GROUP_WRITE(00020),
+ GROUP_EXEC(00010),
+ WORLD_READ(00004),
+ WORLD_WRITE(00002),
+ WORLD_EXEC(00001);
+
+ public static Set<PERMISSION> find(final int code) {
+ final Set<PERMISSION> set = new HashSet<>();
+
+ for (final PERMISSION p : PERMISSION.values()) {
+ if ((code & p.code) == p.code) {
+ set.add(p);
+ }
+ }
+
+ if (set.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ return EnumSet.copyOf(set);
+ }
+
+ private final int code;
+
+ PERMISSION(final int code) {
+ this.code = code;
+ }
+ }
+ /**
+ * Archive entry as stored on tape. There is one TSH for (at most)
+ * every 512k in the file.
+ */
+ static class TapeSegmentHeader {
+ private DumpArchiveConstants.SEGMENT_TYPE type;
+ private int volume;
+ private int ino;
+ private int count;
+ private int holes;
+ private final byte[] cdata = new byte[512]; // map of any 'holes'
+
+ public int getCdata(final int idx) {
+ return cdata[idx];
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public int getHoles() {
+ return holes;
+ }
+
+ public int getIno() {
+ return ino;
+ }
+
+ public DumpArchiveConstants.SEGMENT_TYPE getType() {
+ return type;
+ }
+
+ public int getVolume() {
+ return volume;
+ }
+
+ void setIno(final int ino) {
+ this.ino = ino;
+ }
+ }
+ public enum TYPE {
+ WHITEOUT(14),
+ SOCKET(12),
+ LINK(10),
+ FILE(8),
+ BLKDEV(6),
+ DIRECTORY(4),
+ CHRDEV(2),
+ FIFO(1),
+ UNKNOWN(15);
+
+ public static TYPE find(final int code) {
+ TYPE type = UNKNOWN;
+
+ for (final TYPE t : TYPE.values()) {
+ if (code == t.code) {
+ type = t;
+ }
+ }
+
+ return type;
+ }
+
+ private final int code;
+
+ TYPE(final int code) {
+ this.code = code;
+ }
+ }
+ /**
+ * Populate the dump archive entry and tape segment header with
+ * the contents of the buffer.
+ *
+ * @param buffer buffer to read content from
+ */
+ static DumpArchiveEntry parse(final byte[] buffer) {
+ final DumpArchiveEntry entry = new DumpArchiveEntry();
+ final TapeSegmentHeader header = entry.header;
+
+ header.type = DumpArchiveConstants.SEGMENT_TYPE.find(DumpArchiveUtil.convert32(
+ buffer, 0));
+
+ //header.dumpDate = new Date(1000L * DumpArchiveUtil.convert32(buffer, 4));
+ //header.previousDumpDate = new Date(1000L * DumpArchiveUtil.convert32(
+ // buffer, 8));
+ header.volume = DumpArchiveUtil.convert32(buffer, 12);
+ //header.tapea = DumpArchiveUtil.convert32(buffer, 16);
+ entry.ino = header.ino = DumpArchiveUtil.convert32(buffer, 20);
+
+ //header.magic = DumpArchiveUtil.convert32(buffer, 24);
+ //header.checksum = DumpArchiveUtil.convert32(buffer, 28);
+ final int m = DumpArchiveUtil.convert16(buffer, 32);
+
+ // determine the type of the file.
+ entry.setType(TYPE.find((m >> 12) & 0x0F));
+
+ // determine the standard permissions
+ entry.setMode(m);
+
+ entry.nlink = DumpArchiveUtil.convert16(buffer, 34);
+ // inumber, oldids?
+ entry.setSize(DumpArchiveUtil.convert64(buffer, 40));
+
+ long t = (1000L * DumpArchiveUtil.convert32(buffer, 48)) +
+ (DumpArchiveUtil.convert32(buffer, 52) / 1000);
+ entry.setAccessTime(new Date(t));
+ t = (1000L * DumpArchiveUtil.convert32(buffer, 56)) +
+ (DumpArchiveUtil.convert32(buffer, 60) / 1000);
+ entry.setLastModifiedDate(new Date(t));
+ t = (1000L * DumpArchiveUtil.convert32(buffer, 64)) +
+ (DumpArchiveUtil.convert32(buffer, 68) / 1000);
+ entry.ctime = t;
+
+ // db: 72-119 - direct blocks
+ // id: 120-131 - indirect blocks
+ //entry.flags = DumpArchiveUtil.convert32(buffer, 132);
+ //entry.blocks = DumpArchiveUtil.convert32(buffer, 136);
+ entry.generation = DumpArchiveUtil.convert32(buffer, 140);
+ entry.setUserId(DumpArchiveUtil.convert32(buffer, 144));
+ entry.setGroupId(DumpArchiveUtil.convert32(buffer, 148));
+ // two 32-bit spare values.
+ header.count = DumpArchiveUtil.convert32(buffer, 160);
+
+ header.holes = 0;
+
+ for (int i = 0; (i < 512) && (i < header.count); i++) {
+ if (buffer[164 + i] == 0) {
+ header.holes++;
+ }
+ }
+
+ System.arraycopy(buffer, 164, header.cdata, 0, 512);
+
+ entry.volume = header.getVolume();
+
+ //entry.isSummaryOnly = false;
+ return entry;
+ }
private String name;
private TYPE type = TYPE.UNKNOWN;
private int mode;
private Set<PERMISSION> permissions = Collections.emptySet();
private long size;
+
private long atime;
+
private long mtime;
private int uid;
private int gid;
@@ -194,19 +370,21 @@ public class DumpArchiveEntry implements ArchiveEntry {
* Currently unused
*/
private final DumpArchiveSummary summary = null;
-
// this information is available from standard index.
private final TapeSegmentHeader header = new TapeSegmentHeader();
private String simpleName;
private String originalName;
-
// this information is available from QFA index
private int volume;
private long offset;
private int ino;
+
private int nlink;
+
private long ctime;
+
private int generation;
+
private boolean isDeleted;
/**
@@ -242,44 +420,36 @@ public class DumpArchiveEntry implements ArchiveEntry {
this.offset = 0;
}
- /**
- * Returns the path of the entry.
- * @return the path of the entry.
- */
- public String getSimpleName() {
- return simpleName;
- }
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null || !o.getClass().equals(getClass())) {
+ return false;
+ }
- /**
- * Sets the path of the entry.
- * @param simpleName the simple name
- */
- protected void setSimpleName(final String simpleName) {
- this.simpleName = simpleName;
- }
+ final DumpArchiveEntry rhs = (DumpArchiveEntry) o;
- /**
- * Returns the ino of the entry.
- * @return the ino
- */
- public int getIno() {
- return header.getIno();
- }
+ if (ino != rhs.ino) {
+ return false;
+ }
- /**
- * Return the number of hard links to the entry.
- * @return the number of hard links
- */
- public int getNlink() {
- return nlink;
+ // summary is always null right now, but this may change some day
+ if ((summary == null && rhs.summary != null) // NOSONAR
+ || (summary != null && !summary.equals(rhs.summary))) { // NOSONAR
+ return false;
+ }
+
+ return true;
}
/**
- * Set the number of hard links.
- * @param nlink the number of hard links
+ * Returns the time the file was last accessed.
+ * @return the access time
*/
- public void setNlink(final int nlink) {
- this.nlink = nlink;
+ public Date getAccessTime() {
+ return new Date(atime);
}
/**
@@ -291,11 +461,10 @@ public class DumpArchiveEntry implements ArchiveEntry {
}
/**
- * Set the file creation time.
- * @param ctime the creation time
+ * Returns the size of the entry as read from the archive.
*/
- public void setCreationTime(final Date ctime) {
- this.ctime = ctime.getTime();
+ long getEntrySize() {
+ return size;
}
/**
@@ -307,301 +476,174 @@ public class DumpArchiveEntry implements ArchiveEntry {
}
/**
- * Set the generation of the file.
- * @param generation the generation
+ * Return the group id
+ * @return the group id
*/
- public void setGeneration(final int generation) {
- this.generation = generation;
+ public int getGroupId() {
+ return gid;
}
/**
- * Has this file been deleted? (On valid on incremental dumps.)
- * @return whether the file has been deleted
+ * Return the number of records in this segment.
+ * @return the number of records
*/
- public boolean isDeleted() {
- return isDeleted;
+ public int getHeaderCount() {
+ return header.getCount();
}
/**
- * Set whether this file has been deleted.
- * @param isDeleted whether the file has been deleted
+ * Return the number of sparse records in this segment.
+ * @return the number of sparse records
*/
- public void setDeleted(final boolean isDeleted) {
- this.isDeleted = isDeleted;
+ public int getHeaderHoles() {
+ return header.getHoles();
}
/**
- * Return the offset within the archive
- * @return the offset
+ * Return the type of the tape segment header.
+ * @return the segment header
*/
- public long getOffset() {
- return offset;
+ public DumpArchiveConstants.SEGMENT_TYPE getHeaderType() {
+ return header.getType();
}
/**
- * Set the offset within the archive.
- * @param offset the offset
+ * Returns the ino of the entry.
+ * @return the ino
*/
- public void setOffset(final long offset) {
- this.offset = offset;
+ public int getIno() {
+ return header.getIno();
}
/**
- * Return the tape volume where this file is located.
- * @return the volume
+ * The last modified date.
+ * @return the last modified date
*/
- public int getVolume() {
- return volume;
+ @Override
+ public Date getLastModifiedDate() {
+ return new Date(mtime);
}
/**
- * Set the tape volume.
- * @param volume the volume
+ * Return the access permissions on the entry.
+ * @return the access permissions
*/
- public void setVolume(final int volume) {
- this.volume = volume;
+ public int getMode() {
+ return mode;
}
/**
- * Return the type of the tape segment header.
- * @return the segment header
+ * Returns the name of the entry.
+ *
+ * <p>This method returns the raw name as it is stored inside of the archive.</p>
+ *
+ * @return the name of the entry.
*/
- public DumpArchiveConstants.SEGMENT_TYPE getHeaderType() {
- return header.getType();
+ @Override
+ public String getName() {
+ return name;
}
/**
- * Return the number of records in this segment.
- * @return the number of records
+ * Return the number of hard links to the entry.
+ * @return the number of hard links
*/
- public int getHeaderCount() {
- return header.getCount();
+ public int getNlink() {
+ return nlink;
}
/**
- * Return the number of sparse records in this segment.
- * @return the number of sparse records
+ * Return the offset within the archive
+ * @return the offset
*/
- public int getHeaderHoles() {
- return header.getHoles();
+ public long getOffset() {
+ return offset;
}
/**
- * Is this a sparse record?
- * @param idx index of the record to check
- * @return whether this is a sparse record
+ * Returns the unmodified name of the entry.
+ * @return the name of the entry.
*/
- public boolean isSparseRecord(final int idx) {
- return (header.getCdata(idx) & 0x01) == 0;
+ String getOriginalName() {
+ return originalName;
}
- @Override
- public int hashCode() {
- return ino;
+ /**
+ * Returns the permissions on the entry.
+ * @return the permissions
+ */
+ public Set<PERMISSION> getPermissions() {
+ return permissions;
}
- @Override
- public boolean equals(final Object o) {
- if (o == this) {
- return true;
- }
- if (o == null || !o.getClass().equals(getClass())) {
- return false;
- }
-
- final DumpArchiveEntry rhs = (DumpArchiveEntry) o;
-
- if (ino != rhs.ino) {
- return false;
- }
-
- // summary is always null right now, but this may change some day
- if ((summary == null && rhs.summary != null) // NOSONAR
- || (summary != null && !summary.equals(rhs.summary))) { // NOSONAR
- return false;
- }
-
- return true;
+ /**
+ * Returns the path of the entry.
+ * @return the path of the entry.
+ */
+ public String getSimpleName() {
+ return simpleName;
}
+ /**
+ * Returns the size of the entry.
+ * @return the size
+ */
@Override
- public String toString() {
- return getName();
+ public long getSize() {
+ return isDirectory() ? SIZE_UNKNOWN : size;
}
/**
- * Populate the dump archive entry and tape segment header with
- * the contents of the buffer.
- *
- * @param buffer buffer to read content from
+ * Get the type of the entry.
+ * @return the type
*/
- static DumpArchiveEntry parse(final byte[] buffer) {
- final DumpArchiveEntry entry = new DumpArchiveEntry();
- final TapeSegmentHeader header = entry.header;
-
- header.type = DumpArchiveConstants.SEGMENT_TYPE.find(DumpArchiveUtil.convert32(
- buffer, 0));
-
- //header.dumpDate = new Date(1000L * DumpArchiveUtil.convert32(buffer, 4));
- //header.previousDumpDate = new Date(1000L * DumpArchiveUtil.convert32(
- // buffer, 8));
- header.volume = DumpArchiveUtil.convert32(buffer, 12);
- //header.tapea = DumpArchiveUtil.convert32(buffer, 16);
- entry.ino = header.ino = DumpArchiveUtil.convert32(buffer, 20);
-
- //header.magic = DumpArchiveUtil.convert32(buffer, 24);
- //header.checksum = DumpArchiveUtil.convert32(buffer, 28);
- final int m = DumpArchiveUtil.convert16(buffer, 32);
-
- // determine the type of the file.
- entry.setType(TYPE.find((m >> 12) & 0x0F));
-
- // determine the standard permissions
- entry.setMode(m);
-
- entry.nlink = DumpArchiveUtil.convert16(buffer, 34);
- // inumber, oldids?
- entry.setSize(DumpArchiveUtil.convert64(buffer, 40));
-
- long t = (1000L * DumpArchiveUtil.convert32(buffer, 48)) +
- (DumpArchiveUtil.convert32(buffer, 52) / 1000);
- entry.setAccessTime(new Date(t));
- t = (1000L * DumpArchiveUtil.convert32(buffer, 56)) +
- (DumpArchiveUtil.convert32(buffer, 60) / 1000);
- entry.setLastModifiedDate(new Date(t));
- t = (1000L * DumpArchiveUtil.convert32(buffer, 64)) +
- (DumpArchiveUtil.convert32(buffer, 68) / 1000);
- entry.ctime = t;
-
- // db: 72-119 - direct blocks
- // id: 120-131 - indirect blocks
- //entry.flags = DumpArchiveUtil.convert32(buffer, 132);
- //entry.blocks = DumpArchiveUtil.convert32(buffer, 136);
- entry.generation = DumpArchiveUtil.convert32(buffer, 140);
- entry.setUserId(DumpArchiveUtil.convert32(buffer, 144));
- entry.setGroupId(DumpArchiveUtil.convert32(buffer, 148));
- // two 32-bit spare values.
- header.count = DumpArchiveUtil.convert32(buffer, 160);
-
- header.holes = 0;
-
- for (int i = 0; (i < 512) && (i < header.count); i++) {
- if (buffer[164 + i] == 0) {
- header.holes++;
- }
- }
-
- System.arraycopy(buffer, 164, header.cdata, 0, 512);
-
- entry.volume = header.getVolume();
-
- //entry.isSummaryOnly = false;
- return entry;
+ public TYPE getType() {
+ return type;
}
/**
- * Update entry with information from next tape segment header.
+ * Return the user id.
+ * @return the user id
*/
- void update(final byte[] buffer) {
- header.volume = DumpArchiveUtil.convert32(buffer, 16);
- header.count = DumpArchiveUtil.convert32(buffer, 160);
-
- header.holes = 0;
-
- for (int i = 0; (i < 512) && (i < header.count); i++) {
- if (buffer[164 + i] == 0) {
- header.holes++;
- }
- }
-
- System.arraycopy(buffer, 164, header.cdata, 0, 512);
+ public int getUserId() {
+ return uid;
}
/**
- * Archive entry as stored on tape. There is one TSH for (at most)
- * every 512k in the file.
+ * Return the tape volume where this file is located.
+ * @return the volume
*/
- static class TapeSegmentHeader {
- private DumpArchiveConstants.SEGMENT_TYPE type;
- private int volume;
- private int ino;
- private int count;
- private int holes;
- private final byte[] cdata = new byte[512]; // map of any 'holes'
-
- public DumpArchiveConstants.SEGMENT_TYPE getType() {
- return type;
- }
-
- public int getVolume() {
- return volume;
- }
-
- public int getIno() {
- return ino;
- }
-
- void setIno(final int ino) {
- this.ino = ino;
- }
-
- public int getCount() {
- return count;
- }
-
- public int getHoles() {
- return holes;
- }
-
- public int getCdata(final int idx) {
- return cdata[idx];
- }
+ public int getVolume() {
+ return volume;
}
- /**
- * Returns the name of the entry.
- *
- * <p>This method returns the raw name as it is stored inside of the archive.</p>
- *
- * @return the name of the entry.
- */
@Override
- public String getName() {
- return name;
+ public int hashCode() {
+ return ino;
}
/**
- * Returns the unmodified name of the entry.
- * @return the name of the entry.
+ * Is this a block device?
+ * @return whether this is a block device
*/
- String getOriginalName() {
- return originalName;
+ public boolean isBlkDev() {
+ return type == TYPE.BLKDEV;
}
/**
- * Sets the name of the entry.
- * @param name the name
+ * Is this a character device?
+ * @return whether this is a character device
*/
- public final void setName(String name) {
- this.originalName = name;
- if (name != null) {
- if (isDirectory() && !name.endsWith("/")) {
- name += "/";
- }
- if (name.startsWith("./")) {
- name = name.substring(2);
- }
- }
- this.name = name;
+ public boolean isChrDev() {
+ return type == TYPE.CHRDEV;
}
/**
- * The last modified date.
- * @return the last modified date
+ * Has this file been deleted? (On valid on incremental dumps.)
+ * @return whether the file has been deleted
*/
- @Override
- public Date getLastModifiedDate() {
- return new Date(mtime);
+ public boolean isDeleted() {
+ return isDeleted;
}
/**
@@ -613,6 +655,14 @@ public class DumpArchiveEntry implements ArchiveEntry {
return type == TYPE.DIRECTORY;
}
+ /**
+ * Is this a fifo/pipe?
+ * @return whether this is a fifo
+ */
+ public boolean isFifo() {
+ return type == TYPE.FIFO;
+ }
+
/**
* Is this a regular file?
* @return whether this is a regular file
@@ -630,51 +680,60 @@ public class DumpArchiveEntry implements ArchiveEntry {
}
/**
- * Is this a character device?
- * @return whether this is a character device
+ * Is this a sparse record?
+ * @param idx index of the record to check
+ * @return whether this is a sparse record
*/
- public boolean isChrDev() {
- return type == TYPE.CHRDEV;
+ public boolean isSparseRecord(final int idx) {
+ return (header.getCdata(idx) & 0x01) == 0;
}
/**
- * Is this a block device?
- * @return whether this is a block device
+ * Set the time the file was last accessed.
+ * @param atime the access time
*/
- public boolean isBlkDev() {
- return type == TYPE.BLKDEV;
+ public void setAccessTime(final Date atime) {
+ this.atime = atime.getTime();
}
/**
- * Is this a fifo/pipe?
- * @return whether this is a fifo
+ * Set the file creation time.
+ * @param ctime the creation time
*/
- public boolean isFifo() {
- return type == TYPE.FIFO;
+ public void setCreationTime(final Date ctime) {
+ this.ctime = ctime.getTime();
}
/**
- * Get the type of the entry.
- * @return the type
+ * Set whether this file has been deleted.
+ * @param isDeleted whether the file has been deleted
*/
- public TYPE getType() {
- return type;
+ public void setDeleted(final boolean isDeleted) {
+ this.isDeleted = isDeleted;
}
/**
- * Set the type of the entry.
- * @param type the type
+ * Set the generation of the file.
+ * @param generation the generation
*/
- public void setType(final TYPE type) {
- this.type = type;
+ public void setGeneration(final int generation) {
+ this.generation = generation;
}
/**
- * Return the access permissions on the entry.
- * @return the access permissions
+ * Set the group id.
+ * @param gid the group id
*/
- public int getMode() {
- return mode;
+ public void setGroupId(final int gid) {
+ this.gid = gid;
+ }
+
+ /**
+ * Set the time the file was last modified.
+ * @param mtime the last modified time
+ */
+ public void setLastModifiedDate(final Date mtime) {
+ this.mtime = mtime.getTime();
}
/**
@@ -687,67 +746,60 @@ public class DumpArchiveEntry implements ArchiveEntry {
}
/**
- * Returns the permissions on the entry.
- * @return the permissions
- */
- public Set<PERMISSION> getPermissions() {
- return permissions;
- }
-
- /**
- * Returns the size of the entry.
- * @return the size
- */
- @Override
- public long getSize() {
- return isDirectory() ? SIZE_UNKNOWN : size;
- }
-
- /**
- * Returns the size of the entry as read from the archive.
+ * Sets the name of the entry.
+ * @param name the name
*/
- long getEntrySize() {
- return size;
+ public final void setName(String name) {
+ this.originalName = name;
+ if (name != null) {
+ if (isDirectory() && !name.endsWith("/")) {
+ name += "/";
+ }
+ if (name.startsWith("./")) {
+ name = name.substring(2);
+ }
+ }
+ this.name = name;
}
/**
- * Set the size of the entry.
- * @param size the size
+ * Set the number of hard links.
+ * @param nlink the number of hard links
*/
- public void setSize(final long size) {
- this.size = size;
+ public void setNlink(final int nlink) {
+ this.nlink = nlink;
}
/**
- * Set the time the file was last modified.
- * @param mtime the last modified time
+ * Set the offset within the archive.
+ * @param offset the offset
*/
- public void setLastModifiedDate(final Date mtime) {
- this.mtime = mtime.getTime();
+ public void setOffset(final long offset) {
+ this.offset = offset;
}
/**
- * Returns the time the file was last accessed.
- * @return the access time
+ * Sets the path of the entry.
+ * @param simpleName the simple name
*/
- public Date getAccessTime() {
- return new Date(atime);
+ protected void setSimpleName(final String simpleName) {
+ this.simpleName = simpleName;
}
/**
- * Set the time the file was last accessed.
- * @param atime the access time
+ * Set the size of the entry.
+ * @param size the size
*/
- public void setAccessTime(final Date atime) {
- this.atime = atime.getTime();
+ public void setSize(final long size) {
+ this.size = size;
}
/**
- * Return the user id.
- * @return the user id
+ * Set the type of the entry.
+ * @param type the type
*/
- public int getUserId() {
- return uid;
+ public void setType(final TYPE type) {
+ this.type = type;
}
/**
@@ -759,85 +811,33 @@ public class DumpArchiveEntry implements ArchiveEntry {
}
/**
- * Return the group id
- * @return the group id
+ * Set the tape volume.
+ * @param volume the volume
*/
- public int getGroupId() {
- return gid;
+ public void setVolume(final int volume) {
+ this.volume = volume;
}
- /**
- * Set the group id.
- * @param gid the group id
- */
- public void setGroupId(final int gid) {
- this.gid = gid;
+ @Override
+ public String toString() {
+ return getName();
}
- public enum TYPE {
- WHITEOUT(14),
- SOCKET(12),
- LINK(10),
- FILE(8),
- BLKDEV(6),
- DIRECTORY(4),
- CHRDEV(2),
- FIFO(1),
- UNKNOWN(15);
-
- private final int code;
-
- TYPE(final int code) {
- this.code = code;
- }
+ /**
+ * Update entry with information from next tape segment header.
+ */
+ void update(final byte[] buffer) {
+ header.volume = DumpArchiveUtil.convert32(buffer, 16);
+ header.count = DumpArchiveUtil.convert32(buffer, 160);
- public static TYPE find(final int code) {
- TYPE type = UNKNOWN;
+ header.holes = 0;
- for (final TYPE t : TYPE.values()) {
- if (code == t.code) {
- type = t;
- }
+ for (int i = 0; (i < 512) && (i < header.count); i++) {
+ if (buffer[164 + i] == 0) {
+ header.holes++;
}
-
- return type;
- }
- }
-
- public enum PERMISSION {
- SETUID(04000),
- SETGUI(02000),
- STICKY(01000),
- USER_READ(00400),
- USER_WRITE(00200),
- USER_EXEC(00100),
- GROUP_READ(00040),
- GROUP_WRITE(00020),
- GROUP_EXEC(00010),
- WORLD_READ(00004),
- WORLD_WRITE(00002),
- WORLD_EXEC(00001);
-
- private final int code;
-
- PERMISSION(final int code) {
- this.code = code;
}
- public static Set<PERMISSION> find(final int code) {
- final Set<PERMISSION> set = new HashSet<>();
-
- for (final PERMISSION p : PERMISSION.values()) {
- if ((code & p.code) == p.code) {
- set.add(p);
- }
- }
-
- if (set.isEmpty()) {
- return Collections.emptySet();
- }
-
- return EnumSet.copyOf(set);
- }
+ System.arraycopy(buffer, 164, header.cdata, 0, 512);
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveException.java b/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveException.java
index 635b1d9f..0252a23f 100644
--- a/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveException.java
+++ b/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveException.java
@@ -34,12 +34,12 @@ public class DumpArchiveException extends IOException {
super(msg);
}
- public DumpArchiveException(final Throwable cause) {
+ public DumpArchiveException(final String msg, final Throwable cause) {
+ super(msg);
initCause(cause);
}
- public DumpArchiveException(final String msg, final Throwable cause) {
- super(msg);
+ public DumpArchiveException(final Throwable cause) {
initCause(cause);
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveInputStream.java b/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveInputStream.java
index cc5a5b94..d3c69969 100644
--- a/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveInputStream.java
@@ -48,6 +48,29 @@ import org.apache.commons.compress.utils.IOUtils;
* @NotThreadSafe
*/
public class DumpArchiveInputStream extends ArchiveInputStream {
+ /**
+ * Look at the first few bytes of the file to decide if it's a dump
+ * archive. With 32 bytes we can look at the magic value, with a full
+ * 1k we can verify the checksum.
+ * @param buffer data to match
+ * @param length length of data
+ * @return whether the buffer seems to contain dump data
+ */
+ public static boolean matches(final byte[] buffer, final int length) {
+ // do we have enough of the header?
+ if (length < 32) {
+ return false;
+ }
+
+ // this is the best test
+ if (length >= DumpArchiveConstants.TP_SIZE) {
+ return DumpArchiveUtil.verify(buffer);
+ }
+
+ // this will work in a pinch.
+ return DumpArchiveConstants.NFS_MAGIC == DumpArchiveUtil.convert32(buffer,
+ 24);
+ }
private final DumpArchiveSummary summary;
private DumpArchiveEntry active;
private boolean isClosed;
@@ -59,6 +82,7 @@ public class DumpArchiveInputStream extends ArchiveInputStream {
private byte[] blockBuffer;
private int recordOffset;
private long filepos;
+
protected TapeInputStream raw;
// map of ino -> dirent entry. We can use this to reconstruct full paths.
@@ -145,10 +169,15 @@ public class DumpArchiveInputStream extends ArchiveInputStream {
});
}
- @Deprecated
+ /**
+ * Closes the stream for this entry.
+ */
@Override
- public int getCount() {
- return (int) getBytesRead();
+ public void close() throws IOException {
+ if (!isClosed) {
+ isClosed = true;
+ raw.close();
+ }
}
@Override
@@ -156,60 +185,10 @@ public class DumpArchiveInputStream extends ArchiveInputStream {
return raw.getBytesRead();
}
- /**
- * Return the archive summary information.
- * @return the summary
- */
- public DumpArchiveSummary getSummary() {
- return summary;
- }
-
- /**
- * Read CLRI (deleted inode) segment.
- */
- private void readCLRI() throws IOException {
- final byte[] buffer = raw.readRecord();
-
- if (!DumpArchiveUtil.verify(buffer)) {
- throw new InvalidFormatException();
- }
-
- active = DumpArchiveEntry.parse(buffer);
-
- if (DumpArchiveConstants.SEGMENT_TYPE.CLRI != active.getHeaderType()) {
- throw new InvalidFormatException();
- }
-
- // we don't do anything with this yet.
- if (raw.skip((long) DumpArchiveConstants.TP_SIZE * active.getHeaderCount())
- == -1) {
- throw new EOFException();
- }
- readIdx = active.getHeaderCount();
- }
-
- /**
- * Read BITS segment.
- */
- private void readBITS() throws IOException {
- final byte[] buffer = raw.readRecord();
-
- if (!DumpArchiveUtil.verify(buffer)) {
- throw new InvalidFormatException();
- }
-
- active = DumpArchiveEntry.parse(buffer);
-
- if (DumpArchiveConstants.SEGMENT_TYPE.BITS != active.getHeaderType()) {
- throw new InvalidFormatException();
- }
-
- // we don't do anything with this yet.
- if (raw.skip((long) DumpArchiveConstants.TP_SIZE * active.getHeaderCount())
- == -1) {
- throw new EOFException();
- }
- readIdx = active.getHeaderCount();
+ @Deprecated
+ @Override
+ public int getCount() {
+ return (int) getBytesRead();
}
/**
@@ -314,92 +293,6 @@ public class DumpArchiveInputStream extends ArchiveInputStream {
return entry;
}
- /**
- * Read directory entry.
- */
- private void readDirectoryEntry(DumpArchiveEntry entry)
- throws IOException {
- long size = entry.getEntrySize();
- boolean first = true;
-
- while (first ||
- DumpArchiveConstants.SEGMENT_TYPE.ADDR == entry.getHeaderType()) {
- // read the header that we just peeked at.
- if (!first) {
- raw.readRecord();
- }
-
- if (!names.containsKey(entry.getIno()) &&
- DumpArchiveConstants.SEGMENT_TYPE.INODE == entry.getHeaderType()) {
- pending.put(entry.getIno(), entry);
- }
-
- final int datalen = DumpArchiveConstants.TP_SIZE * entry.getHeaderCount();
-
- if (blockBuffer.length < datalen) {
- blockBuffer = IOUtils.readRange(raw, datalen);
- if (blockBuffer.length != datalen) {
- throw new EOFException();
- }
- } else if (raw.read(blockBuffer, 0, datalen) != datalen) {
- throw new EOFException();
- }
-
- int reclen = 0;
-
- for (int i = 0; i < datalen - 8 && i < size - 8;
- i += reclen) {
- final int ino = DumpArchiveUtil.convert32(blockBuffer, i);
- reclen = DumpArchiveUtil.convert16(blockBuffer, i + 4);
-
- final byte type = blockBuffer[i + 6];
-
- final String name = DumpArchiveUtil.decode(zipEncoding, blockBuffer, i + 8, blockBuffer[i + 7]);
-
- if (".".equals(name) || "..".equals(name)) {
- // do nothing...
- continue;
- }
-
- final Dirent d = new Dirent(ino, entry.getIno(), type, name);
-
- /*
- if ((type == 4) && names.containsKey(ino)) {
- System.out.println("we already have ino: " +
- names.get(ino));
- }
- */
-
- names.put(ino, d);
-
- // check whether this allows us to fill anything in the pending list.
- pending.forEach((k, v) -> {
- final String path = getPath(v);
-
- if (path != null) {
- v.setName(path);
- v.setSimpleName(names.get(k).getName());
- queue.add(v);
- }
- });
-
- // remove anything that we found. (We can't do it earlier
- // because of concurrent modification exceptions.)
- queue.forEach(e -> pending.remove(e.getIno()));
- }
-
- final byte[] peekBytes = raw.peek();
-
- if (!DumpArchiveUtil.verify(peekBytes)) {
- throw new InvalidFormatException();
- }
-
- entry = DumpArchiveEntry.parse(peekBytes);
- first = false;
- size -= DumpArchiveConstants.TP_SIZE;
- }
- }
-
/**
* Get full path for specified archive entry, or null if there's a gap.
*
@@ -444,6 +337,14 @@ public class DumpArchiveInputStream extends ArchiveInputStream {
return sb.toString();
}
+ /**
+ * Return the archive summary information.
+ * @return the summary
+ */
+ public DumpArchiveSummary getSummary() {
+ return summary;
+ }
+
/**
* Reads bytes from the current dump archive entry.
*
@@ -520,38 +421,137 @@ public class DumpArchiveInputStream extends ArchiveInputStream {
}
/**
- * Closes the stream for this entry.
+ * Read BITS segment.
*/
- @Override
- public void close() throws IOException {
- if (!isClosed) {
- isClosed = true;
- raw.close();
+ private void readBITS() throws IOException {
+ final byte[] buffer = raw.readRecord();
+
+ if (!DumpArchiveUtil.verify(buffer)) {
+ throw new InvalidFormatException();
+ }
+
+ active = DumpArchiveEntry.parse(buffer);
+
+ if (DumpArchiveConstants.SEGMENT_TYPE.BITS != active.getHeaderType()) {
+ throw new InvalidFormatException();
}
+
+ // we don't do anything with this yet.
+ if (raw.skip((long) DumpArchiveConstants.TP_SIZE * active.getHeaderCount())
+ == -1) {
+ throw new EOFException();
+ }
+ readIdx = active.getHeaderCount();
}
/**
- * Look at the first few bytes of the file to decide if it's a dump
- * archive. With 32 bytes we can look at the magic value, with a full
- * 1k we can verify the checksum.
- * @param buffer data to match
- * @param length length of data
- * @return whether the buffer seems to contain dump data
+ * Read CLRI (deleted inode) segment.
*/
- public static boolean matches(final byte[] buffer, final int length) {
- // do we have enough of the header?
- if (length < 32) {
- return false;
+ private void readCLRI() throws IOException {
+ final byte[] buffer = raw.readRecord();
+
+ if (!DumpArchiveUtil.verify(buffer)) {
+ throw new InvalidFormatException();
}
- // this is the best test
- if (length >= DumpArchiveConstants.TP_SIZE) {
- return DumpArchiveUtil.verify(buffer);
+ active = DumpArchiveEntry.parse(buffer);
+
+ if (DumpArchiveConstants.SEGMENT_TYPE.CLRI != active.getHeaderType()) {
+ throw new InvalidFormatException();
}
- // this will work in a pinch.
- return DumpArchiveConstants.NFS_MAGIC == DumpArchiveUtil.convert32(buffer,
- 24);
+ // we don't do anything with this yet.
+ if (raw.skip((long) DumpArchiveConstants.TP_SIZE * active.getHeaderCount())
+ == -1) {
+ throw new EOFException();
+ }
+ readIdx = active.getHeaderCount();
+ }
+
+ /**
+ * Read directory entry.
+ */
+ private void readDirectoryEntry(DumpArchiveEntry entry)
+ throws IOException {
+ long size = entry.getEntrySize();
+ boolean first = true;
+
+ while (first ||
+ DumpArchiveConstants.SEGMENT_TYPE.ADDR == entry.getHeaderType()) {
+ // read the header that we just peeked at.
+ if (!first) {
+ raw.readRecord();
+ }
+
+ if (!names.containsKey(entry.getIno()) &&
+ DumpArchiveConstants.SEGMENT_TYPE.INODE == entry.getHeaderType()) {
+ pending.put(entry.getIno(), entry);
+ }
+
+ final int datalen = DumpArchiveConstants.TP_SIZE * entry.getHeaderCount();
+
+ if (blockBuffer.length < datalen) {
+ blockBuffer = IOUtils.readRange(raw, datalen);
+ if (blockBuffer.length != datalen) {
+ throw new EOFException();
+ }
+ } else if (raw.read(blockBuffer, 0, datalen) != datalen) {
+ throw new EOFException();
+ }
+
+ int reclen = 0;
+
+ for (int i = 0; i < datalen - 8 && i < size - 8;
+ i += reclen) {
+ final int ino = DumpArchiveUtil.convert32(blockBuffer, i);
+ reclen = DumpArchiveUtil.convert16(blockBuffer, i + 4);
+
+ final byte type = blockBuffer[i + 6];
+
+ final String name = DumpArchiveUtil.decode(zipEncoding, blockBuffer, i + 8, blockBuffer[i + 7]);
+
+ if (".".equals(name) || "..".equals(name)) {
+ // do nothing...
+ continue;
+ }
+
+ final Dirent d = new Dirent(ino, entry.getIno(), type, name);
+
+ /*
+ if ((type == 4) && names.containsKey(ino)) {
+ System.out.println("we already have ino: " +
+ names.get(ino));
+ }
+ */
+
+ names.put(ino, d);
+
+ // check whether this allows us to fill anything in the pending list.
+ pending.forEach((k, v) -> {
+ final String path = getPath(v);
+
+ if (path != null) {
+ v.setName(path);
+ v.setSimpleName(names.get(k).getName());
+ queue.add(v);
+ }
+ });
+
+ // remove anything that we found. (We can't do it earlier
+ // because of concurrent modification exceptions.)
+ queue.forEach(e -> pending.remove(e.getIno()));
+ }
+
+ final byte[] peekBytes = raw.peek();
+
+ if (!DumpArchiveUtil.verify(peekBytes)) {
+ throw new InvalidFormatException();
+ }
+
+ entry = DumpArchiveEntry.parse(peekBytes);
+ first = false;
+ size -= DumpArchiveConstants.TP_SIZE;
+ }
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveSummary.java b/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveSummary.java
index b197564c..b9378f5d 100644
--- a/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveSummary.java
+++ b/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveSummary.java
@@ -62,6 +62,29 @@ public class DumpArchiveSummary {
//extAttributes = DumpArchiveUtil.convert32(buffer, 900);
}
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DumpArchiveSummary other = (DumpArchiveSummary) obj;
+ return Objects.equals(devname, other.devname) && dumpDate == other.dumpDate && Objects.equals(hostname, other.hostname);
+ }
+
+ /**
+ * Get the device name, e.g., /dev/sda3 or /dev/mapper/vg0-home.
+ * @return device name
+ */
+ public String getDevname() {
+ return devname;
+ }
+
/**
* Get the date of this dump.
* @return the date of this dump.
@@ -71,43 +94,44 @@ public class DumpArchiveSummary {
}
/**
- * Set dump date.
- * @param dumpDate the dump date
+ * Get the last mountpoint, e.g., /home.
+ * @return last mountpoint
*/
- public void setDumpDate(final Date dumpDate) {
- this.dumpDate = dumpDate.getTime();
+ public String getFilesystem() {
+ return filesys;
}
/**
- * Get the date of the previous dump at this level higher.
- * @return dumpdate may be null
+ * Get the inode of the first record on this volume.
+ * @return inode of the first record on this volume.
*/
- public Date getPreviousDumpDate() {
- return new Date(previousDumpDate);
+ public int getFirstRecord() {
+ return firstrec;
}
/**
- * Set previous dump date.
- * @param previousDumpDate the previous dump dat
+ * Get the miscellaneous flags. See below.
+ * @return flags
*/
- public void setPreviousDumpDate(final Date previousDumpDate) {
- this.previousDumpDate = previousDumpDate.getTime();
+ public int getFlags() {
+ return flags;
}
/**
- * Get volume (tape) number.
- * @return volume (tape) number.
+ * Get the hostname of the system where the dump was performed.
+ * @return hostname the host name
*/
- public int getVolume() {
- return volume;
+ public String getHostname() {
+ return hostname;
}
/**
- * Set volume (tape) number.
- * @param volume the volume number
+ * Get dump label. This may be autogenerated or it may be specified
+ * bu the user.
+ * @return dump label
*/
- public void setVolume(final int volume) {
- this.volume = volume;
+ public String getLabel() {
+ return label;
}
/**
@@ -123,100 +147,101 @@ public class DumpArchiveSummary {
}
/**
- * Set level.
- * @param level the level
+ * Get the number of records per tape block. This is typically
+ * between 10 and 32.
+ * @return the number of records per tape block
*/
- public void setLevel(final int level) {
- this.level = level;
+ public int getNTRec() {
+ return ntrec;
}
/**
- * Get dump label. This may be autogenerated or it may be specified
- * bu the user.
- * @return dump label
+ * Get the date of the previous dump at this level higher.
+ * @return dumpdate may be null
*/
- public String getLabel() {
- return label;
+ public Date getPreviousDumpDate() {
+ return new Date(previousDumpDate);
}
/**
- * Set dump label.
- * @param label the label
+ * Get volume (tape) number.
+ * @return volume (tape) number.
*/
- public void setLabel(final String label) {
- this.label = label;
+ public int getVolume() {
+ return volume;
}
- /**
- * Get the last mountpoint, e.g., /home.
- * @return last mountpoint
- */
- public String getFilesystem() {
- return filesys;
+ @Override
+ public int hashCode() {
+ return Objects.hash(devname, dumpDate, hostname);
}
/**
- * Set the last mountpoint.
- * @param fileSystem the last mountpoint
+ * Is this volume compressed? N.B., individual blocks may or may not be compressed.
+ * The first block is never compressed.
+ * @return true if volume is compressed
*/
- public void setFilesystem(final String fileSystem) {
- this.filesys = fileSystem;
+ public boolean isCompressed() {
+ return (flags & 0x0080) == 0x0080;
}
/**
- * Get the device name, e.g., /dev/sda3 or /dev/mapper/vg0-home.
- * @return device name
+ * Does this volume contain extended attributes.
+ * @return true if volume contains extended attributes.
*/
- public String getDevname() {
- return devname;
+ public boolean isExtendedAttributes() {
+ return (flags & 0x8000) == 0x8000;
}
/**
- * Set the device name.
- * @param devname the device name
+ * Does this volume only contain metadata?
+ * @return true if volume only contains meta-data
*/
- public void setDevname(final String devname) {
- this.devname = devname;
+ public boolean isMetaDataOnly() {
+ return (flags & 0x0100) == 0x0100;
}
/**
- * Get the hostname of the system where the dump was performed.
- * @return hostname the host name
+ * Is this the new header format? (We do not currently support the
+ * old format.)
+ *
+ * @return true if using new header format
*/
- public String getHostname() {
- return hostname;
+ public boolean isNewHeader() {
+ return (flags & 0x0001) == 0x0001;
}
/**
- * Set the hostname.
- * @param hostname the host name
+ * Is this the new inode format? (We do not currently support the
+ * old format.)
+ * @return true if using new inode format
*/
- public void setHostname(final String hostname) {
- this.hostname = hostname;
+ public boolean isNewInode() {
+ return (flags & 0x0002) == 0x0002;
}
/**
- * Get the miscellaneous flags. See below.
- * @return flags
+ * Set the device name.
+ * @param devname the device name
*/
- public int getFlags() {
- return flags;
+ public void setDevname(final String devname) {
+ this.devname = devname;
}
/**
- * Set the miscellaneous flags.
- * @param flags flags
+ * Set dump date.
+ * @param dumpDate the dump date
*/
- public void setFlags(final int flags) {
- this.flags = flags;
+ public void setDumpDate(final Date dumpDate) {
+ this.dumpDate = dumpDate.getTime();
}
/**
- * Get the inode of the first record on this volume.
- * @return inode of the first record on this volume.
+ * Set the last mountpoint.
+ * @param fileSystem the last mountpoint
*/
- public int getFirstRecord() {
- return firstrec;
+ public void setFilesystem(final String fileSystem) {
+ this.filesys = fileSystem;
}
/**
@@ -228,83 +253,58 @@ public class DumpArchiveSummary {
}
/**
- * Get the number of records per tape block. This is typically
- * between 10 and 32.
- * @return the number of records per tape block
+ * Set the miscellaneous flags.
+ * @param flags flags
*/
- public int getNTRec() {
- return ntrec;
+ public void setFlags(final int flags) {
+ this.flags = flags;
}
/**
- * Set the number of records per tape block.
- * @param ntrec the number of records per tape block
+ * Set the hostname.
+ * @param hostname the host name
*/
- public void setNTRec(final int ntrec) {
- this.ntrec = ntrec;
+ public void setHostname(final String hostname) {
+ this.hostname = hostname;
}
/**
- * Is this the new header format? (We do not currently support the
- * old format.)
- *
- * @return true if using new header format
+ * Set dump label.
+ * @param label the label
*/
- public boolean isNewHeader() {
- return (flags & 0x0001) == 0x0001;
+ public void setLabel(final String label) {
+ this.label = label;
}
/**
- * Is this the new inode format? (We do not currently support the
- * old format.)
- * @return true if using new inode format
+ * Set level.
+ * @param level the level
*/
- public boolean isNewInode() {
- return (flags & 0x0002) == 0x0002;
+ public void setLevel(final int level) {
+ this.level = level;
}
/**
- * Is this volume compressed? N.B., individual blocks may or may not be compressed.
- * The first block is never compressed.
- * @return true if volume is compressed
+ * Set the number of records per tape block.
+ * @param ntrec the number of records per tape block
*/
- public boolean isCompressed() {
- return (flags & 0x0080) == 0x0080;
+ public void setNTRec(final int ntrec) {
+ this.ntrec = ntrec;
}
/**
- * Does this volume only contain metadata?
- * @return true if volume only contains meta-data
+ * Set previous dump date.
+ * @param previousDumpDate the previous dump dat
*/
- public boolean isMetaDataOnly() {
- return (flags & 0x0100) == 0x0100;
+ public void setPreviousDumpDate(final Date previousDumpDate) {
+ this.previousDumpDate = previousDumpDate.getTime();
}
/**
- * Does this volume contain extended attributes.
- * @return true if volume contains extended attributes.
+ * Set volume (tape) number.
+ * @param volume the volume number
*/
- public boolean isExtendedAttributes() {
- return (flags & 0x8000) == 0x8000;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(devname, dumpDate, hostname);
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- DumpArchiveSummary other = (DumpArchiveSummary) obj;
- return Objects.equals(devname, other.devname) && dumpDate == other.dumpDate && Objects.equals(hostname, other.hostname);
+ public void setVolume(final int volume) {
+ this.volume = volume;
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveUtil.java b/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveUtil.java
index 516df050..80cd9358 100644
--- a/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveUtil.java
+++ b/src/main/java/org/apache/commons/compress/archivers/dump/DumpArchiveUtil.java
@@ -28,12 +28,6 @@ import org.apache.commons.compress.utils.ByteUtils;
* Various utilities for dump archives.
*/
class DumpArchiveUtil {
- /**
- * Private constructor to prevent instantiation.
- */
- private DumpArchiveUtil() {
- }
-
/**
* Calculate checksum for buffer.
*
@@ -52,33 +46,25 @@ class DumpArchiveUtil {
}
/**
- * Verifies that the buffer contains a tape segment header.
+ * Read 2-byte integer from buffer.
*
* @param buffer
- * @return Whether the buffer contains a tape segment header.
+ * @param offset
+ * @return the 2-byte entry as an int
*/
- public static final boolean verify(final byte[] buffer) {
- // verify magic. for now only accept NFS_MAGIC.
- final int magic = convert32(buffer, 24);
-
- if (magic != DumpArchiveConstants.NFS_MAGIC) {
- return false;
- }
-
- //verify checksum...
- final int checksum = convert32(buffer, 28);
-
- return checksum == calculateChecksum(buffer);
+ public static final int convert16(final byte[] buffer, final int offset) {
+ return (int) ByteUtils.fromLittleEndian(buffer, offset, 2);
}
/**
- * Gets the ino associated with this buffer.
+ * Read 4-byte integer from buffer.
*
* @param buffer
- * @return the ino associated with this buffer.
+ * @param offset
+ * @return the 4-byte entry as an int
*/
- public static final int getIno(final byte[] buffer) {
- return convert32(buffer, 20);
+ public static final int convert32(final byte[] buffer, final int offset) {
+ return (int) ByteUtils.fromLittleEndian(buffer, offset, 4);
}
/**
@@ -93,32 +79,46 @@ class DumpArchiveUtil {
}
/**
- * Read 4-byte integer from buffer.
+ * Decodes a byte array to a string.
+ */
+ static String decode(final ZipEncoding encoding, final byte[] b, final int offset, final int len)
+ throws IOException {
+ return encoding.decode(Arrays.copyOfRange(b, offset, offset + len));
+ }
+
+ /**
+ * Gets the ino associated with this buffer.
*
* @param buffer
- * @param offset
- * @return the 4-byte entry as an int
+ * @return the ino associated with this buffer.
*/
- public static final int convert32(final byte[] buffer, final int offset) {
- return (int) ByteUtils.fromLittleEndian(buffer, offset, 4);
+ public static final int getIno(final byte[] buffer) {
+ return convert32(buffer, 20);
}
/**
- * Read 2-byte integer from buffer.
+ * Verifies that the buffer contains a tape segment header.
*
* @param buffer
- * @param offset
- * @return the 2-byte entry as an int
+ * @return Whether the buffer contains a tape segment header.
*/
- public static final int convert16(final byte[] buffer, final int offset) {
- return (int) ByteUtils.fromLittleEndian(buffer, offset, 2);
+ public static final boolean verify(final byte[] buffer) {
+ // verify magic. for now only accept NFS_MAGIC.
+ final int magic = convert32(buffer, 24);
+
+ if (magic != DumpArchiveConstants.NFS_MAGIC) {
+ return false;
+ }
+
+ //verify checksum...
+ final int checksum = convert32(buffer, 28);
+
+ return checksum == calculateChecksum(buffer);
}
/**
- * Decodes a byte array to a string.
+ * Private constructor to prevent instantiation.
*/
- static String decode(final ZipEncoding encoding, final byte[] b, final int offset, final int len)
- throws IOException {
- return encoding.decode(Arrays.copyOfRange(b, offset, offset + len));
+ private DumpArchiveUtil() {
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/dump/TapeInputStream.java b/src/main/java/org/apache/commons/compress/archivers/dump/TapeInputStream.java
index 6e00db70..08d23f7f 100644
--- a/src/main/java/org/apache/commons/compress/archivers/dump/TapeInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/dump/TapeInputStream.java
@@ -35,10 +35,10 @@ import org.apache.commons.compress.utils.IOUtils;
* @NotThreadSafe
*/
class TapeInputStream extends FilterInputStream {
+ private static final int RECORD_SIZE = DumpArchiveConstants.TP_SIZE;
private byte[] blockBuffer = new byte[DumpArchiveConstants.TP_SIZE];
private int currBlkIdx = -1;
private int blockSize = DumpArchiveConstants.TP_SIZE;
- private static final int RECORD_SIZE = DumpArchiveConstants.TP_SIZE;
private int readOffset = DumpArchiveConstants.TP_SIZE;
private boolean isCompressed;
private long bytesRead;
@@ -53,53 +53,61 @@ class TapeInputStream extends FilterInputStream {
}
/**
- * Set the DumpArchive Buffer's block size. We need to sync the block size with the
- * dump archive's actual block size since compression is handled at the
- * block level.
- *
- * @param recsPerBlock
- * records per block
- * @param isCompressed
- * true if the archive is compressed
- * @throws IOException
- * more than one block has been read
- * @throws IOException
- * there was an error reading additional blocks.
- * @throws IOException
- * recsPerBlock is smaller than 1
+ * @see java.io.InputStream#available
*/
- public void resetBlockSize(final int recsPerBlock, final boolean isCompressed)
- throws IOException {
- this.isCompressed = isCompressed;
-
- if (recsPerBlock < 1) {
- throw new IOException("Block with " + recsPerBlock
- + " records found, must be at least 1");
+ @Override
+ public int available() throws IOException {
+ if (readOffset < blockSize) {
+ return blockSize - readOffset;
}
- blockSize = RECORD_SIZE * recsPerBlock;
- // save first block in case we need it again
- final byte[] oldBuffer = blockBuffer;
+ return in.available();
+ }
- // read rest of new block
- blockBuffer = new byte[blockSize];
- System.arraycopy(oldBuffer, 0, blockBuffer, 0, RECORD_SIZE);
- readFully(blockBuffer, RECORD_SIZE, blockSize - RECORD_SIZE);
+ /**
+ * Close the input stream.
+ *
+ * @throws IOException on error
+ */
+ @Override
+ public void close() throws IOException {
+ if (in != null && in != System.in) {
+ in.close();
+ }
+ }
- this.currBlkIdx = 0;
- this.readOffset = RECORD_SIZE;
+ /**
+ * Gets number of bytes read.
+ *
+ * @return number of bytes read.
+ */
+ public long getBytesRead() {
+ return bytesRead;
}
/**
- * @see java.io.InputStream#available
+ * Peek at the next record from the input stream and return the data.
+ *
+ * @return The record data.
+ * @throws IOException on error
*/
- @Override
- public int available() throws IOException {
- if (readOffset < blockSize) {
- return blockSize - readOffset;
+ public byte[] peek() throws IOException {
+ // we need to read from the underlying stream. This
+ // isn't a problem since it would be the first step in
+ // any subsequent read() anyway.
+ if (readOffset == blockSize) {
+ try {
+ readBlock(true);
+ } catch (final ShortFileException sfe) { // NOSONAR
+ return null;
+ }
}
- return in.available();
+ // copy data, increment counters.
+ final byte[] b = new byte[RECORD_SIZE];
+ System.arraycopy(blockBuffer, readOffset, b, 0, b.length);
+
+ return b;
}
/**
@@ -165,110 +173,6 @@ class TapeInputStream extends FilterInputStream {
return bytes;
}
- /**
- * Skip bytes. Same as read but without the arraycopy.
- *
- * <p>skips the full given length unless EOF is reached.</p>
- *
- * @param len length to read, must be a multiple of the stream's
- * record size
- */
- @Override
- public long skip(final long len) throws IOException {
- if ((len % RECORD_SIZE) != 0) {
- throw new IllegalArgumentException(
- "All reads must be multiple of record size (" + RECORD_SIZE +
- " bytes.");
- }
-
- long bytes = 0;
-
- while (bytes < len) {
- // we need to read from the underlying stream.
- // this will reset readOffset value. We do not perform
- // any decompression if we won't eventually read the data.
- // return -1 if there's a problem.
- if (readOffset == blockSize) {
- try {
- readBlock((len - bytes) < blockSize);
- } catch (final ShortFileException sfe) { // NOSONAR
- return -1;
- }
- }
-
- long n = 0;
-
- if ((readOffset + (len - bytes)) <= blockSize) {
- // we can read entirely from the buffer.
- n = len - bytes;
- } else {
- // copy what we can from the buffer.
- n = (long) blockSize - readOffset;
- }
-
- // do not copy data but still increment counters.
- readOffset = ExactMath.add(readOffset, n);
- bytes += n;
- }
-
- return bytes;
- }
-
- /**
- * Close the input stream.
- *
- * @throws IOException on error
- */
- @Override
- public void close() throws IOException {
- if (in != null && in != System.in) {
- in.close();
- }
- }
-
- /**
- * Peek at the next record from the input stream and return the data.
- *
- * @return The record data.
- * @throws IOException on error
- */
- public byte[] peek() throws IOException {
- // we need to read from the underlying stream. This
- // isn't a problem since it would be the first step in
- // any subsequent read() anyway.
- if (readOffset == blockSize) {
- try {
- readBlock(true);
- } catch (final ShortFileException sfe) { // NOSONAR
- return null;
- }
- }
-
- // copy data, increment counters.
- final byte[] b = new byte[RECORD_SIZE];
- System.arraycopy(blockBuffer, readOffset, b, 0, b.length);
-
- return b;
- }
-
- /**
- * Read a record from the input stream and return the data.
- *
- * @return The record data.
- * @throws IOException on error
- */
- public byte[] readRecord() throws IOException {
- final byte[] result = new byte[RECORD_SIZE];
-
- // the read implementation will loop internally as long as
- // input is available
- if (-1 == read(result, 0, result.length)) {
- throw new ShortFileException();
- }
-
- return result;
- }
-
/**
* Read next block. All decompression is handled here.
*
@@ -365,11 +269,107 @@ class TapeInputStream extends FilterInputStream {
}
/**
- * Gets number of bytes read.
+ * Read a record from the input stream and return the data.
*
- * @return number of bytes read.
+ * @return The record data.
+ * @throws IOException on error
*/
- public long getBytesRead() {
- return bytesRead;
+ public byte[] readRecord() throws IOException {
+ final byte[] result = new byte[RECORD_SIZE];
+
+ // the read implementation will loop internally as long as
+ // input is available
+ if (-1 == read(result, 0, result.length)) {
+ throw new ShortFileException();
+ }
+
+ return result;
+ }
+
+ /**
+ * Set the DumpArchive Buffer's block size. We need to sync the block size with the
+ * dump archive's actual block size since compression is handled at the
+ * block level.
+ *
+ * @param recsPerBlock
+ * records per block
+ * @param isCompressed
+ * true if the archive is compressed
+ * @throws IOException
+ * more than one block has been read
+ * @throws IOException
+ * there was an error reading additional blocks.
+ * @throws IOException
+ * recsPerBlock is smaller than 1
+ */
+ public void resetBlockSize(final int recsPerBlock, final boolean isCompressed)
+ throws IOException {
+ this.isCompressed = isCompressed;
+
+ if (recsPerBlock < 1) {
+ throw new IOException("Block with " + recsPerBlock
+ + " records found, must be at least 1");
+ }
+ blockSize = RECORD_SIZE * recsPerBlock;
+
+ // save first block in case we need it again
+ final byte[] oldBuffer = blockBuffer;
+
+ // read rest of new block
+ blockBuffer = new byte[blockSize];
+ System.arraycopy(oldBuffer, 0, blockBuffer, 0, RECORD_SIZE);
+ readFully(blockBuffer, RECORD_SIZE, blockSize - RECORD_SIZE);
+
+ this.currBlkIdx = 0;
+ this.readOffset = RECORD_SIZE;
+ }
+
+ /**
+ * Skip bytes. Same as read but without the arraycopy.
+ *
+ * <p>skips the full given length unless EOF is reached.</p>
+ *
+ * @param len length to read, must be a multiple of the stream's
+ * record size
+ */
+ @Override
+ public long skip(final long len) throws IOException {
+ if ((len % RECORD_SIZE) != 0) {
+ throw new IllegalArgumentException(
+ "All reads must be multiple of record size (" + RECORD_SIZE +
+ " bytes.");
+ }
+
+ long bytes = 0;
+
+ while (bytes < len) {
+ // we need to read from the underlying stream.
+ // this will reset readOffset value. We do not perform
+ // any decompression if we won't eventually read the data.
+ // return -1 if there's a problem.
+ if (readOffset == blockSize) {
+ try {
+ readBlock((len - bytes) < blockSize);
+ } catch (final ShortFileException sfe) { // NOSONAR
+ return -1;
+ }
+ }
+
+ long n = 0;
+
+ if ((readOffset + (len - bytes)) <= blockSize) {
+ // we can read entirely from the buffer.
+ n = len - bytes;
+ } else {
+ // copy what we can from the buffer.
+ n = (long) blockSize - readOffset;
+ }
+
+ // do not copy data but still increment counters.
+ readOffset = ExactMath.add(readOffset, n);
+ bytes += n;
+ }
+
+ return bytes;
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/Archiver.java b/src/main/java/org/apache/commons/compress/archivers/examples/Archiver.java
index cdbe7b71..cd6f137a 100644
--- a/src/main/java/org/apache/commons/compress/archivers/examples/Archiver.java
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/Archiver.java
@@ -114,16 +114,11 @@ public class Archiver {
*
* @param target the stream to write the new archive to.
* @param directory the directory that contains the files to archive.
- * @param fileVisitOptions linkOptions to configure the traversal of the source {@code directory}.
- * @param linkOptions indicating how symbolic links are handled.
* @throws IOException if an I/O error occurs or the archive cannot be created for other reasons.
* @since 1.21
*/
- public void create(final ArchiveOutputStream target, final Path directory,
- final EnumSet<FileVisitOption> fileVisitOptions, final LinkOption... linkOptions) throws IOException {
- Files.walkFileTree(directory, fileVisitOptions, Integer.MAX_VALUE,
- new ArchiverFileVisitor(target, directory, linkOptions));
- target.finish();
+ public void create(final ArchiveOutputStream target, final Path directory) throws IOException {
+ create(target, directory, EMPTY_FileVisitOption);
}
/**
@@ -131,11 +126,16 @@ public class Archiver {
*
* @param target the stream to write the new archive to.
* @param directory the directory that contains the files to archive.
+ * @param fileVisitOptions linkOptions to configure the traversal of the source {@code directory}.
+ * @param linkOptions indicating how symbolic links are handled.
* @throws IOException if an I/O error occurs or the archive cannot be created for other reasons.
* @since 1.21
*/
- public void create(final ArchiveOutputStream target, final Path directory) throws IOException {
- create(target, directory, EMPTY_FileVisitOption);
+ public void create(final ArchiveOutputStream target, final Path directory,
+ final EnumSet<FileVisitOption> fileVisitOptions, final LinkOption... linkOptions) throws IOException {
+ Files.walkFileTree(directory, fileVisitOptions, Integer.MAX_VALUE,
+ new ArchiverFileVisitor(target, directory, linkOptions));
+ target.finish();
}
/**
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/CloseableConsumerAdapter.java b/src/main/java/org/apache/commons/compress/archivers/examples/CloseableConsumerAdapter.java
index 832410e2..7afea60e 100644
--- a/src/main/java/org/apache/commons/compress/archivers/examples/CloseableConsumerAdapter.java
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/CloseableConsumerAdapter.java
@@ -30,15 +30,15 @@ final class CloseableConsumerAdapter implements Closeable {
this.consumer = Objects.requireNonNull(consumer, "consumer");
}
- <C extends Closeable> C track(final C closeable) {
- this.closeable = closeable;
- return closeable;
- }
-
@Override
public void close() throws IOException {
if (closeable != null) {
consumer.accept(closeable);
}
}
+
+ <C extends Closeable> C track(final C closeable) {
+ this.closeable = closeable;
+ return closeable;
+ }
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/jar/JarArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/jar/JarArchiveEntry.java
index f0c05f04..d89f68d5 100644
--- a/src/main/java/org/apache/commons/compress/archivers/jar/JarArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/jar/JarArchiveEntry.java
@@ -36,8 +36,9 @@ public class JarArchiveEntry extends ZipArchiveEntry {
private final Attributes manifestAttributes = null;
private final Certificate[] certificates = null;
- public JarArchiveEntry(final ZipEntry entry) throws ZipException {
+ public JarArchiveEntry(final JarEntry entry) throws ZipException {
super(entry);
+
}
public JarArchiveEntry(final String name) {
@@ -48,21 +49,8 @@ public class JarArchiveEntry extends ZipArchiveEntry {
super(entry);
}
- public JarArchiveEntry(final JarEntry entry) throws ZipException {
+ public JarArchiveEntry(final ZipEntry entry) throws ZipException {
super(entry);
-
- }
-
- /**
- * This method is not implemented and won't ever be.
- * The JVM equivalent has a different name {@link java.util.jar.JarEntry#getAttributes()}
- *
- * @deprecated since 1.5, do not use; always returns null
- * @return Always returns null.
- */
- @Deprecated
- public Attributes getManifestAttributes() {
- return manifestAttributes;
}
/**
@@ -87,4 +75,16 @@ public class JarArchiveEntry extends ZipArchiveEntry {
return null;
}
+ /**
+ * This method is not implemented and won't ever be.
+ * The JVM equivalent has a different name {@link java.util.jar.JarEntry#getAttributes()}
+ *
+ * @deprecated since 1.5, do not use; always returns null
+ * @return Always returns null.
+ */
+ @Deprecated
+ public Attributes getManifestAttributes() {
+ return manifestAttributes;
+ }
+
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/jar/JarArchiveInputStream.java b/src/main/java/org/apache/commons/compress/archivers/jar/JarArchiveInputStream.java
index 47b1583c..494f03c6 100644
--- a/src/main/java/org/apache/commons/compress/archivers/jar/JarArchiveInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/jar/JarArchiveInputStream.java
@@ -32,6 +32,20 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
*/
public class JarArchiveInputStream extends ZipArchiveInputStream {
+ /**
+ * Checks if the signature matches what is expected for a jar file
+ * (in this case it is the same as for a zip file).
+ *
+ * @param signature
+ * the bytes to check
+ * @param length
+ * the number of bytes to check
+ * @return true, if this stream is a jar archive stream, false otherwise
+ */
+ public static boolean matches(final byte[] signature, final int length ) {
+ return ZipArchiveInputStream.matches(signature, length);
+ }
+
/**
* Creates an instance from the input stream using the default encoding.
*
@@ -52,27 +66,13 @@ public class JarArchiveInputStream extends ZipArchiveInputStream {
super(inputStream, encoding);
}
- public JarArchiveEntry getNextJarEntry() throws IOException {
- final ZipArchiveEntry entry = getNextZipEntry();
- return entry == null ? null : new JarArchiveEntry(entry);
- }
-
@Override
public ArchiveEntry getNextEntry() throws IOException {
return getNextJarEntry();
}
- /**
- * Checks if the signature matches what is expected for a jar file
- * (in this case it is the same as for a zip file).
- *
- * @param signature
- * the bytes to check
- * @param length
- * the number of bytes to check
- * @return true, if this stream is a jar archive stream, false otherwise
- */
- public static boolean matches(final byte[] signature, final int length ) {
- return ZipArchiveInputStream.matches(signature, length);
+ public JarArchiveEntry getNextJarEntry() throws IOException {
+ final ZipArchiveEntry entry = getNextZipEntry();
+ return entry == null ? null : new JarArchiveEntry(entry);
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/AES256SHA256Decoder.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/AES256SHA256Decoder.java
index 4353bf52..0a77fa6e 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/AES256SHA256Decoder.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/AES256SHA256Decoder.java
@@ -39,6 +39,52 @@ import org.apache.commons.compress.PasswordRequiredException;
class AES256SHA256Decoder extends AbstractCoder {
+ static byte[] sha256Password(final byte[] password, final int numCyclesPower, final byte[] salt) {
+ final MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("SHA-256");
+ } catch (final NoSuchAlgorithmException noSuchAlgorithmException) {
+ throw new IllegalStateException("SHA-256 is unsupported by your Java implementation", noSuchAlgorithmException);
+ }
+ final byte[] extra = new byte[8];
+ for (long j = 0; j < (1L << numCyclesPower); j++) {
+ digest.update(salt);
+ digest.update(password);
+ digest.update(extra);
+ for (int k = 0; k < extra.length; k++) {
+ ++extra[k];
+ if (extra[k] != 0) {
+ break;
+ }
+ }
+ }
+ return digest.digest();
+ }
+
+ static byte[] sha256Password(final char[] password, final int numCyclesPower, final byte[] salt) {
+ return sha256Password(utf16Decode(password), numCyclesPower, salt);
+ }
+
+ /**
+ * Convenience method that encodes Unicode characters into bytes in UTF-16 (ittle-endian byte order) charset
+ *
+ * @param chars characters to encode
+ * @return encoded characters
+ * @since 1.23
+ */
+ static byte[] utf16Decode(final char[] chars) {
+ if (chars == null) {
+ return null;
+ }
+ final ByteBuffer encoded = UTF_16LE.encode(CharBuffer.wrap(chars));
+ if (encoded.hasArray()) {
+ return encoded.array();
+ }
+ final byte[] e = new byte[encoded.remaining()];
+ encoded.get(e);
+ return e;
+ }
+
AES256SHA256Decoder() {
super(AES256Options.class);
}
@@ -50,6 +96,13 @@ class AES256SHA256Decoder extends AbstractCoder {
private boolean isInitialized;
private CipherInputStream cipherInputStream;
+ @Override
+ public void close() throws IOException {
+ if (cipherInputStream != null) {
+ cipherInputStream.close();
+ }
+ }
+
private CipherInputStream init() throws IOException {
if (isInitialized) {
return cipherInputStream;
@@ -109,13 +162,6 @@ class AES256SHA256Decoder extends AbstractCoder {
public int read(final byte[] b, final int off, final int len) throws IOException {
return init().read(b, off, len);
}
-
- @Override
- public void close() throws IOException {
- if (cipherInputStream != null) {
- cipherInputStream.close();
- }
- }
};
}
@@ -134,11 +180,22 @@ class AES256SHA256Decoder extends AbstractCoder {
private int count = 0;
@Override
- public void write(int b) throws IOException {
- cipherBlockBuffer[count++] = (byte) b;
- if (count == cipherBlockSize) {
- flushBuffer();
+ public void close() throws IOException {
+ if (count > 0) {
+ cipherOutputStream.write(cipherBlockBuffer);
}
+ cipherOutputStream.close();
+ }
+
+ @Override
+ public void flush() throws IOException {
+ cipherOutputStream.flush();
+ }
+
+ private void flushBuffer() throws IOException {
+ cipherOutputStream.write(cipherBlockBuffer);
+ count = 0;
+ Arrays.fill(cipherBlockBuffer, (byte) 0);
}
@Override
@@ -161,23 +218,12 @@ class AES256SHA256Decoder extends AbstractCoder {
}
}
- private void flushBuffer() throws IOException {
- cipherOutputStream.write(cipherBlockBuffer);
- count = 0;
- Arrays.fill(cipherBlockBuffer, (byte) 0);
- }
-
- @Override
- public void flush() throws IOException {
- cipherOutputStream.flush();
- }
-
@Override
- public void close() throws IOException {
- if (count > 0) {
- cipherOutputStream.write(cipherBlockBuffer);
+ public void write(int b) throws IOException {
+ cipherBlockBuffer[count++] = (byte) b;
+ if (count == cipherBlockSize) {
+ flushBuffer();
}
- cipherOutputStream.close();
}
};
}
@@ -201,50 +247,4 @@ class AES256SHA256Decoder extends AbstractCoder {
return props;
}
-
- static byte[] sha256Password(final char[] password, final int numCyclesPower, final byte[] salt) {
- return sha256Password(utf16Decode(password), numCyclesPower, salt);
- }
-
- static byte[] sha256Password(final byte[] password, final int numCyclesPower, final byte[] salt) {
- final MessageDigest digest;
- try {
- digest = MessageDigest.getInstance("SHA-256");
- } catch (final NoSuchAlgorithmException noSuchAlgorithmException) {
- throw new IllegalStateException("SHA-256 is unsupported by your Java implementation", noSuchAlgorithmException);
- }
- final byte[] extra = new byte[8];
- for (long j = 0; j < (1L << numCyclesPower); j++) {
- digest.update(salt);
- digest.update(password);
- digest.update(extra);
- for (int k = 0; k < extra.length; k++) {
- ++extra[k];
- if (extra[k] != 0) {
- break;
- }
- }
- }
- return digest.digest();
- }
-
- /**
- * Convenience method that encodes Unicode characters into bytes in UTF-16 (ittle-endian byte order) charset
- *
- * @param chars characters to encode
- * @return encoded characters
- * @since 1.23
- */
- static byte[] utf16Decode(final char[] chars) {
- if (chars == null) {
- return null;
- }
- final ByteBuffer encoded = UTF_16LE.encode(CharBuffer.wrap(chars));
- if (encoded.hasArray()) {
- return encoded.array();
- }
- final byte[] e = new byte[encoded.remaining()];
- encoded.get(e);
- return e;
- }
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/Archive.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/Archive.java
index 429ca86c..fec68fe7 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/Archive.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/Archive.java
@@ -20,6 +20,12 @@ package org.apache.commons.compress.archivers.sevenz;
import java.util.BitSet;
class Archive {
+ private static String lengthOf(final long[] a) {
+ return a == null ? "(null)" : String.valueOf(a.length);
+ }
+ private static String lengthOf(final Object[] a) {
+ return a == null ? "(null)" : String.valueOf(a.length);
+ }
/// Offset from beginning of file + SIGNATURE_HEADER_SIZE to packed streams.
long packPos;
/// Size of each packed stream.
@@ -32,8 +38,10 @@ class Archive {
Folder[] folders = Folder.EMPTY_FOLDER_ARRAY;
/// Temporary properties for non-empty files (subsumed into the files array later).
SubStreamsInfo subStreamsInfo;
+
/// The files and directories in the archive.
SevenZArchiveEntry[] files = SevenZArchiveEntry.EMPTY_SEVEN_Z_ARCHIVE_ENTRY_ARRAY;
+
/// Mapping between folders, files and streams.
StreamMap streamMap;
@@ -44,12 +52,4 @@ class Archive {
+ " CRCs, " + lengthOf(folders) + " folders, " + lengthOf(files)
+ " files and " + streamMap;
}
-
- private static String lengthOf(final long[] a) {
- return a == null ? "(null)" : String.valueOf(a.length);
- }
-
- private static String lengthOf(final Object[] a) {
- return a == null ? "(null)" : String.valueOf(a.length);
- }
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/BoundedSeekableByteChannelInputStream.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/BoundedSeekableByteChannelInputStream.java
index ca8b754f..309a3e3a 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/BoundedSeekableByteChannelInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/BoundedSeekableByteChannelInputStream.java
@@ -39,6 +39,11 @@ class BoundedSeekableByteChannelInputStream extends InputStream {
}
}
+ @Override
+ public void close() {
+ // the nested channel is controlled externally
+ }
+
@Override
public int read() throws IOException {
if (bytesRemaining > 0) {
@@ -98,9 +103,4 @@ class BoundedSeekableByteChannelInputStream extends InputStream {
buffer.flip();
return read;
}
-
- @Override
- public void close() {
- // the nested channel is controlled externally
- }
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/CLI.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/CLI.java
index dfa1c581..83cdc5e0 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/CLI.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/CLI.java
@@ -25,6 +25,22 @@ public class CLI {
private enum Mode {
LIST("Analysing") {
+ private String getContentMethods(final SevenZArchiveEntry entry) {
+ final StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (final SevenZMethodConfiguration m : entry.getContentMethods()) {
+ if (!first) {
+ sb.append(", ");
+ }
+ first = false;
+ sb.append(m.getMethod());
+ if (m.getOptions() != null) {
+ sb.append("(").append(m.getOptions()).append(")");
+ }
+ }
+ return sb.toString();
+ }
+
@Override
public void takeAction(final SevenZFile archive, final SevenZArchiveEntry entry) {
System.out.print(entry.getName());
@@ -45,22 +61,6 @@ public class CLI {
System.out.println();
}
}
-
- private String getContentMethods(final SevenZArchiveEntry entry) {
- final StringBuilder sb = new StringBuilder();
- boolean first = true;
- for (final SevenZMethodConfiguration m : entry.getContentMethods()) {
- if (!first) {
- sb.append(", ");
- }
- first = false;
- sb.append(m.getMethod());
- if (m.getOptions() != null) {
- sb.append("(").append(m.getOptions()).append(")");
- }
- }
- return sb.toString();
- }
};
private final String message;
@@ -74,6 +74,13 @@ public class CLI {
throws IOException;
}
+ private static Mode grabMode(final String[] args) {
+ if (args.length < 2) {
+ return Mode.LIST;
+ }
+ return Enum.valueOf(Mode.class, args[1].toUpperCase());
+ }
+
public static void main(final String[] args) throws Exception {
if (args.length == 0) {
usage();
@@ -97,11 +104,4 @@ public class CLI {
System.out.println("Parameters: archive-name [list]");
}
- private static Mode grabMode(final String[] args) {
- if (args.length < 2) {
- return Mode.LIST;
- }
- return Enum.valueOf(Mode.class, args[1].toUpperCase());
- }
-
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java
index 4dbeeae9..4e03caae 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java
@@ -44,64 +44,6 @@ import org.tukaani.xz.SPARCOptions;
import org.tukaani.xz.X86Options;
class Coders {
- private static final Map<SevenZMethod, AbstractCoder> CODER_MAP = new HashMap<SevenZMethod, AbstractCoder>() {
-
- private static final long serialVersionUID = 1664829131806520867L;
-
- {
- put(SevenZMethod.COPY, new CopyDecoder());
- put(SevenZMethod.LZMA, new LZMADecoder());
- put(SevenZMethod.LZMA2, new LZMA2Decoder());
- put(SevenZMethod.DEFLATE, new DeflateDecoder());
- put(SevenZMethod.DEFLATE64, new Deflate64Decoder());
- put(SevenZMethod.BZIP2, new BZIP2Decoder());
- put(SevenZMethod.AES256SHA256, new AES256SHA256Decoder());
- put(SevenZMethod.BCJ_X86_FILTER, new BCJDecoder(new X86Options()));
- put(SevenZMethod.BCJ_PPC_FILTER, new BCJDecoder(new PowerPCOptions()));
- put(SevenZMethod.BCJ_IA64_FILTER, new BCJDecoder(new IA64Options()));
- put(SevenZMethod.BCJ_ARM_FILTER, new BCJDecoder(new ARMOptions()));
- put(SevenZMethod.BCJ_ARM_THUMB_FILTER, new BCJDecoder(new ARMThumbOptions()));
- put(SevenZMethod.BCJ_SPARC_FILTER, new BCJDecoder(new SPARCOptions()));
- put(SevenZMethod.DELTA_FILTER, new DeltaDecoder());
- }
- };
-
- static AbstractCoder findByMethod(final SevenZMethod method) {
- return CODER_MAP.get(method);
- }
-
- static InputStream addDecoder(final String archiveName, final InputStream is, final long uncompressedLength,
- final Coder coder, final byte[] password, final int maxMemoryLimitInKb) throws IOException {
- final AbstractCoder cb = findByMethod(SevenZMethod.byId(coder.decompressionMethodId));
- if (cb == null) {
- throw new IOException("Unsupported compression method " +
- Arrays.toString(coder.decompressionMethodId)
- + " used in " + archiveName);
- }
- return cb.decode(archiveName, is, uncompressedLength, coder, password, maxMemoryLimitInKb);
- }
-
- static OutputStream addEncoder(final OutputStream out, final SevenZMethod method,
- final Object options) throws IOException {
- final AbstractCoder cb = findByMethod(method);
- if (cb == null) {
- throw new IOException("Unsupported compression method " + method);
- }
- return cb.encode(out, options);
- }
-
- static class CopyDecoder extends AbstractCoder {
- @Override
- InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength,
- final Coder coder, final byte[] password, final int maxMemoryLimitInKb) throws IOException {
- return in;
- }
- @Override
- OutputStream encode(final OutputStream out, final Object options) {
- return out;
- }
- }
-
static class BCJDecoder extends AbstractCoder {
private final FilterOptions opts;
BCJDecoder(final FilterOptions opts) {
@@ -128,36 +70,52 @@ class Coders {
}
}
- static class DeflateDecoder extends AbstractCoder {
- private static final byte[] ONE_ZERO_BYTE = new byte[1];
- DeflateDecoder() {
+ static class BZIP2Decoder extends AbstractCoder {
+ BZIP2Decoder() {
super(Number.class);
}
@Override
InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength,
final Coder coder, final byte[] password, final int maxMemoryLimitInKb)
- throws IOException {
- final Inflater inflater = new Inflater(true);
- // Inflater with nowrap=true has this odd contract for a zero padding
- // byte following the data stream; this used to be zlib's requirement
- // and has been fixed a long time ago, but the contract persists so
- // we comply.
- // https://docs.oracle.com/javase/7/docs/api/java/util/zip/Inflater.html#Inflater(boolean)
- final InflaterInputStream inflaterInputStream = new InflaterInputStream(new SequenceInputStream(in,
- new ByteArrayInputStream(ONE_ZERO_BYTE)), inflater);
- return new DeflateDecoderInputStream(inflaterInputStream, inflater);
+ throws IOException {
+ return new BZip2CompressorInputStream(in);
+ }
+ @Override
+ OutputStream encode(final OutputStream out, final Object options)
+ throws IOException {
+ final int blockSize = toInt(options, BZip2CompressorOutputStream.MAX_BLOCKSIZE);
+ return new BZip2CompressorOutputStream(out, blockSize);
}
+ }
+ static class CopyDecoder extends AbstractCoder {
+ @Override
+ InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength,
+ final Coder coder, final byte[] password, final int maxMemoryLimitInKb) throws IOException {
+ return in;
+ }
@Override
OutputStream encode(final OutputStream out, final Object options) {
- final int level = toInt(options, 9);
- final Deflater deflater = new Deflater(level, true);
- final DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(out, deflater);
- return new DeflateDecoderOutputStream(deflaterOutputStream, deflater);
+ return out;
}
+ }
- static class DeflateDecoderInputStream extends InputStream {
+ static class Deflate64Decoder extends AbstractCoder {
+ Deflate64Decoder() {
+ super(Number.class);
+ }
+
+ @Override
+ InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength,
+ final Coder coder, final byte[] password, final int maxMemoryLimitInKb)
+ throws IOException {
+ return new Deflate64CompressorInputStream(in);
+ }
+ }
+
+ static class DeflateDecoder extends AbstractCoder {
+ static class DeflateDecoderInputStream extends InputStream {
final InflaterInputStream inflaterInputStream;
Inflater inflater;
@@ -169,13 +127,17 @@ class Coders {
}
@Override
- public int read() throws IOException {
- return inflaterInputStream.read();
+ public void close() throws IOException {
+ try {
+ inflaterInputStream.close();
+ } finally {
+ inflater.end();
+ }
}
@Override
- public int read(final byte[] b, final int off, final int len) throws IOException {
- return inflaterInputStream.read(b, off, len);
+ public int read() throws IOException {
+ return inflaterInputStream.read();
}
@Override
@@ -184,16 +146,11 @@ class Coders {
}
@Override
- public void close() throws IOException {
- try {
- inflaterInputStream.close();
- } finally {
- inflater.end();
- }
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ return inflaterInputStream.read(b, off, len);
}
}
-
- static class DeflateDecoderOutputStream extends OutputStream {
+ static class DeflateDecoderOutputStream extends OutputStream {
final DeflaterOutputStream deflaterOutputStream;
Deflater deflater;
@@ -205,8 +162,12 @@ class Coders {
}
@Override
- public void write(final int b) throws IOException {
- deflaterOutputStream.write(b);
+ public void close() throws IOException {
+ try {
+ deflaterOutputStream.close();
+ } finally {
+ deflater.end();
+ }
}
@Override
@@ -220,46 +181,85 @@ class Coders {
}
@Override
- public void close() throws IOException {
- try {
- deflaterOutputStream.close();
- } finally {
- deflater.end();
- }
+ public void write(final int b) throws IOException {
+ deflaterOutputStream.write(b);
}
}
- }
- static class Deflate64Decoder extends AbstractCoder {
- Deflate64Decoder() {
+ private static final byte[] ONE_ZERO_BYTE = new byte[1];
+
+ DeflateDecoder() {
super(Number.class);
}
- @Override
+ @Override
InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength,
final Coder coder, final byte[] password, final int maxMemoryLimitInKb)
throws IOException {
- return new Deflate64CompressorInputStream(in);
+ final Inflater inflater = new Inflater(true);
+ // Inflater with nowrap=true has this odd contract for a zero padding
+ // byte following the data stream; this used to be zlib's requirement
+ // and has been fixed a long time ago, but the contract persists so
+ // we comply.
+ // https://docs.oracle.com/javase/7/docs/api/java/util/zip/Inflater.html#Inflater(boolean)
+ final InflaterInputStream inflaterInputStream = new InflaterInputStream(new SequenceInputStream(in,
+ new ByteArrayInputStream(ONE_ZERO_BYTE)), inflater);
+ return new DeflateDecoderInputStream(inflaterInputStream, inflater);
+ }
+
+ @Override
+ OutputStream encode(final OutputStream out, final Object options) {
+ final int level = toInt(options, 9);
+ final Deflater deflater = new Deflater(level, true);
+ final DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(out, deflater);
+ return new DeflateDecoderOutputStream(deflaterOutputStream, deflater);
}
}
- static class BZIP2Decoder extends AbstractCoder {
- BZIP2Decoder() {
- super(Number.class);
+ private static final Map<SevenZMethod, AbstractCoder> CODER_MAP = new HashMap<SevenZMethod, AbstractCoder>() {
+
+ private static final long serialVersionUID = 1664829131806520867L;
+
+ {
+ put(SevenZMethod.COPY, new CopyDecoder());
+ put(SevenZMethod.LZMA, new LZMADecoder());
+ put(SevenZMethod.LZMA2, new LZMA2Decoder());
+ put(SevenZMethod.DEFLATE, new DeflateDecoder());
+ put(SevenZMethod.DEFLATE64, new Deflate64Decoder());
+ put(SevenZMethod.BZIP2, new BZIP2Decoder());
+ put(SevenZMethod.AES256SHA256, new AES256SHA256Decoder());
+ put(SevenZMethod.BCJ_X86_FILTER, new BCJDecoder(new X86Options()));
+ put(SevenZMethod.BCJ_PPC_FILTER, new BCJDecoder(new PowerPCOptions()));
+ put(SevenZMethod.BCJ_IA64_FILTER, new BCJDecoder(new IA64Options()));
+ put(SevenZMethod.BCJ_ARM_FILTER, new BCJDecoder(new ARMOptions()));
+ put(SevenZMethod.BCJ_ARM_THUMB_FILTER, new BCJDecoder(new ARMThumbOptions()));
+ put(SevenZMethod.BCJ_SPARC_FILTER, new BCJDecoder(new SPARCOptions()));
+ put(SevenZMethod.DELTA_FILTER, new DeltaDecoder());
}
+ };
- @Override
- InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength,
- final Coder coder, final byte[] password, final int maxMemoryLimitInKb)
- throws IOException {
- return new BZip2CompressorInputStream(in);
+ static InputStream addDecoder(final String archiveName, final InputStream is, final long uncompressedLength,
+ final Coder coder, final byte[] password, final int maxMemoryLimitInKb) throws IOException {
+ final AbstractCoder cb = findByMethod(SevenZMethod.byId(coder.decompressionMethodId));
+ if (cb == null) {
+ throw new IOException("Unsupported compression method " +
+ Arrays.toString(coder.decompressionMethodId)
+ + " used in " + archiveName);
}
- @Override
- OutputStream encode(final OutputStream out, final Object options)
- throws IOException {
- final int blockSize = toInt(options, BZip2CompressorOutputStream.MAX_BLOCKSIZE);
- return new BZip2CompressorOutputStream(out, blockSize);
+ return cb.decode(archiveName, is, uncompressedLength, coder, password, maxMemoryLimitInKb);
+ }
+
+ static OutputStream addEncoder(final OutputStream out, final SevenZMethod method,
+ final Object options) throws IOException {
+ final AbstractCoder cb = findByMethod(method);
+ if (cb == null) {
+ throw new IOException("Unsupported compression method " + method);
}
+ return cb.encode(out, options);
+ }
+
+ static AbstractCoder findByMethod(final SevenZMethod method) {
+ return CODER_MAP.get(method);
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/DeltaDecoder.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/DeltaDecoder.java
index 826a6939..f34c4cb0 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/DeltaDecoder.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/DeltaDecoder.java
@@ -54,15 +54,15 @@ class DeltaDecoder extends AbstractCoder {
};
}
- @Override
- Object getOptionsFromCoder(final Coder coder, final InputStream in) {
- return getOptionsFromCoder(coder);
- }
-
private int getOptionsFromCoder(final Coder coder) {
if (coder.properties == null || coder.properties.length == 0) {
return 1;
}
return (0xff & coder.properties[0]) + 1;
}
+
+ @Override
+ Object getOptionsFromCoder(final Coder coder, final InputStream in) {
+ return getOptionsFromCoder(coder);
+ }
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/Folder.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/Folder.java
index cfa7e4b3..1124fe71 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/Folder.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/Folder.java
@@ -25,6 +25,7 @@ import java.util.LinkedList;
* The unit of solid compression.
*/
class Folder {
+ static final Folder[] EMPTY_FOLDER_ARRAY = {};
/// List of coders used in this folder, eg. one for compression, one for encryption.
Coder[] coders;
/// Total number of input streams across all coders.
@@ -46,7 +47,28 @@ class Folder {
/// output streams and the number of non-empty files in this
/// folder.
int numUnpackSubStreams;
- static final Folder[] EMPTY_FOLDER_ARRAY = {};
+
+ int findBindPairForInStream(final int index) {
+ if (bindPairs != null) {
+ for (int i = 0; i < bindPairs.length; i++) {
+ if (bindPairs[i].inIndex == index) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ int findBindPairForOutStream(final int index) {
+ if (bindPairs != null) {
+ for (int i = 0; i < bindPairs.length; i++) {
+ if (bindPairs[i].outIndex == index) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
/**
* Sorts Coders using bind pairs.
@@ -71,28 +93,6 @@ class Folder {
return l;
}
- int findBindPairForInStream(final int index) {
- if (bindPairs != null) {
- for (int i = 0; i < bindPairs.length; i++) {
- if (bindPairs[i].inIndex == index) {
- return i;
- }
- }
- }
- return -1;
- }
-
- int findBindPairForOutStream(final int index) {
- if (bindPairs != null) {
- for (int i = 0; i < bindPairs.length; i++) {
- if (bindPairs[i].outIndex == index) {
- return i;
- }
- }
- }
- return -1;
- }
-
long getUnpackSize() {
if (totalOutputStreams == 0) {
return 0;
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/LZMA2Decoder.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/LZMA2Decoder.java
index 289e8067..ba84194d 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/LZMA2Decoder.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/LZMA2Decoder.java
@@ -53,29 +53,6 @@ class LZMA2Decoder extends AbstractCoder {
return getOptions(opts).getOutputStream(new FinishableWrapperOutputStream(out));
}
- @Override
- byte[] getOptionsAsProperties(final Object opts) {
- final int dictSize = getDictSize(opts);
- final int lead = Integer.numberOfLeadingZeros(dictSize);
- final int secondBit = (dictSize >>> (30 - lead)) - 2;
- return new byte[] {
- (byte) ((19 - lead) * 2 + secondBit)
- };
- }
-
- @Override
- Object getOptionsFromCoder(final Coder coder, final InputStream in)
- throws IOException {
- return getDictionarySize(coder);
- }
-
- private int getDictSize(final Object opts) {
- if (opts instanceof LZMA2Options) {
- return ((LZMA2Options) opts).getDictSize();
- }
- return numberOptionOrDefault(opts);
- }
-
private int getDictionarySize(final Coder coder) throws IOException {
if (coder.properties == null) {
throw new IOException("Missing LZMA2 properties");
@@ -96,6 +73,13 @@ class LZMA2Decoder extends AbstractCoder {
return (2 | (dictionarySizeBits & 0x1)) << (dictionarySizeBits / 2 + 11);
}
+ private int getDictSize(final Object opts) {
+ if (opts instanceof LZMA2Options) {
+ return ((LZMA2Options) opts).getDictSize();
+ }
+ return numberOptionOrDefault(opts);
+ }
+
private LZMA2Options getOptions(final Object opts) throws IOException {
if (opts instanceof LZMA2Options) {
return (LZMA2Options) opts;
@@ -105,6 +89,22 @@ class LZMA2Decoder extends AbstractCoder {
return options;
}
+ @Override
+ byte[] getOptionsAsProperties(final Object opts) {
+ final int dictSize = getDictSize(opts);
+ final int lead = Integer.numberOfLeadingZeros(dictSize);
+ final int secondBit = (dictSize >>> (30 - lead)) - 2;
+ return new byte[] {
+ (byte) ((19 - lead) * 2 + secondBit)
+ };
+ }
+
+ @Override
+ Object getOptionsFromCoder(final Coder coder, final InputStream in)
+ throws IOException {
+ return getDictionarySize(coder);
+ }
+
private int numberOptionOrDefault(final Object opts) {
return toInt(opts, LZMA2Options.DICT_SIZE_DEFAULT);
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/LZMADecoder.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/LZMADecoder.java
index 82626e8f..9ef390da 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/LZMADecoder.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/LZMADecoder.java
@@ -63,6 +63,19 @@ class LZMADecoder extends AbstractCoder {
return new FlushShieldFilterOutputStream(new LZMAOutputStream(out, getOptions(opts), false));
}
+ private int getDictionarySize(final Coder coder) throws IllegalArgumentException {
+ return (int) ByteUtils.fromLittleEndian(coder.properties, 1, 4);
+ }
+
+ private LZMA2Options getOptions(final Object opts) throws IOException {
+ if (opts instanceof LZMA2Options) {
+ return (LZMA2Options) opts;
+ }
+ final LZMA2Options options = new LZMA2Options();
+ options.setDictSize(numberOptionOrDefault(opts));
+ return options;
+ }
+
@Override
byte[] getOptionsAsProperties(final Object opts) throws IOException {
final LZMA2Options options = getOptions(opts);
@@ -95,19 +108,6 @@ class LZMADecoder extends AbstractCoder {
return opts;
}
- private int getDictionarySize(final Coder coder) throws IllegalArgumentException {
- return (int) ByteUtils.fromLittleEndian(coder.properties, 1, 4);
- }
-
- private LZMA2Options getOptions(final Object opts) throws IOException {
- if (opts instanceof LZMA2Options) {
- return (LZMA2Options) opts;
- }
- final LZMA2Options options = new LZMA2Options();
- options.setDictSize(numberOptionOrDefault(opts));
- return options;
- }
-
private int numberOptionOrDefault(final Object opts) {
return toInt(opts, LZMA2Options.DICT_SIZE_DEFAULT);
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java
index a7d938d5..4ec6b000 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java
@@ -90,28 +90,220 @@ import org.apache.commons.compress.utils.InputStreamStatistics;
* @since 1.6
*/
public class SevenZFile implements Closeable {
+ private static class ArchiveStatistics {
+ private int numberOfPackedStreams;
+ private long numberOfCoders;
+ private long numberOfOutStreams;
+ private long numberOfInStreams;
+ private long numberOfUnpackSubStreams;
+ private int numberOfFolders;
+ private BitSet folderHasCrc;
+ private int numberOfEntries;
+ private int numberOfEntriesWithStream;
+
+ void assertValidity(final int maxMemoryLimitInKb) throws IOException {
+ if (numberOfEntriesWithStream > 0 && numberOfFolders == 0) {
+ throw new IOException("archive with entries but no folders");
+ }
+ if (numberOfEntriesWithStream > numberOfUnpackSubStreams) {
+ throw new IOException("archive doesn't contain enough substreams for entries");
+ }
+
+ final long memoryNeededInKb = estimateSize() / 1024;
+ if (maxMemoryLimitInKb < memoryNeededInKb) {
+ throw new MemoryLimitException(memoryNeededInKb, maxMemoryLimitInKb);
+ }
+ }
+
+ private long bindPairSize() {
+ return 16;
+ }
+
+ private long coderSize() {
+ return 2 /* methodId is between 1 and four bytes currently, COPY and LZMA2 are the most common with 1 */
+ + 16
+ + 4 /* properties, guess */
+ ;
+ }
+
+ private long entrySize() {
+ return 100; /* real size depends on name length, everything without name is about 70 bytes */
+ }
+
+ long estimateSize() {
+ final long lowerBound = 16L * numberOfPackedStreams /* packSizes, packCrcs in Archive */
+ + numberOfPackedStreams / 8 /* packCrcsDefined in Archive */
+ + numberOfFolders * folderSize() /* folders in Archive */
+ + numberOfCoders * coderSize() /* coders in Folder */
+ + (numberOfOutStreams - numberOfFolders) * bindPairSize() /* bindPairs in Folder */
+ + 8L * (numberOfInStreams - numberOfOutStreams + numberOfFolders) /* packedStreams in Folder */
+ + 8L * numberOfOutStreams /* unpackSizes in Folder */
+ + numberOfEntries * entrySize() /* files in Archive */
+ + streamMapSize()
+ ;
+ return 2 * lowerBound /* conservative guess */;
+ }
+
+ private long folderSize() {
+ return 30; /* nested arrays are accounted for separately */
+ }
+
+ private long streamMapSize() {
+ return 8 * numberOfFolders /* folderFirstPackStreamIndex, folderFirstFileIndex */
+ + 8 * numberOfPackedStreams /* packStreamOffsets */
+ + 4 * numberOfEntries /* fileFolderIndex */
+ ;
+ }
+
+ @Override
+ public String toString() {
+ return "Archive with " + numberOfEntries + " entries in " + numberOfFolders
+ + " folders. Estimated size " + estimateSize()/ 1024L + " kB.";
+ }
+ }
+
static final int SIGNATURE_HEADER_SIZE = 32;
private static final String DEFAULT_FILE_NAME = "unknown archive";
+ /** Shared with SevenZOutputFile and tests, neither mutates it. */
+ static final byte[] sevenZSignature = { //NOSONAR
+ (byte)'7', (byte)'z', (byte)0xBC, (byte)0xAF, (byte)0x27, (byte)0x1C
+ };
+ private static int assertFitsIntoNonNegativeInt(final String what, final long value) throws IOException {
+ if (value > Integer.MAX_VALUE || value < 0) {
+ throw new IOException("Cannot handle " + what + " " + value);
+ }
+ return (int) value;
+ }
+ private static void get(final ByteBuffer buf, final byte[] to) throws IOException {
+ if (buf.remaining() < to.length) {
+ throw new EOFException();
+ }
+ buf.get(to);
+ }
+ private static char getChar(final ByteBuffer buf) throws IOException {
+ if (buf.remaining() < 2) {
+ throw new EOFException();
+ }
+ return buf.getChar();
+ }
+ private static int getInt(final ByteBuffer buf) throws IOException {
+ if (buf.remaining() < 4) {
+ throw new EOFException();
+ }
+ return buf.getInt();
+ }
+ private static long getLong(final ByteBuffer buf) throws IOException {
+ if (buf.remaining() < 8) {
+ throw new EOFException();
+ }
+ return buf.getLong();
+ }
+ private static int getUnsignedByte(final ByteBuffer buf) throws IOException {
+ if (!buf.hasRemaining()) {
+ throw new EOFException();
+ }
+ return buf.get() & 0xff;
+ }
+
+ /**
+ * Checks if the signature matches what is expected for a 7z file.
+ *
+ * @param signature
+ * the bytes to check
+ * @param length
+ * the number of bytes to check
+ * @return true, if this is the signature of a 7z archive.
+ * @since 1.8
+ */
+ public static boolean matches(final byte[] signature, final int length) {
+ if (length < sevenZSignature.length) {
+ return false;
+ }
+
+ for (int i = 0; i < sevenZSignature.length; i++) {
+ if (signature[i] != sevenZSignature[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ private static long readUint64(final ByteBuffer in) throws IOException {
+ // long rather than int as it might get shifted beyond the range of an int
+ final long firstByte = getUnsignedByte(in);
+ int mask = 0x80;
+ long value = 0;
+ for (int i = 0; i < 8; i++) {
+ if ((firstByte & mask) == 0) {
+ return value | (firstByte & mask - 1) << 8 * i;
+ }
+ final long nextByte = getUnsignedByte(in);
+ value |= nextByte << 8 * i;
+ mask >>>= 1;
+ }
+ return value;
+ }
+
+ private static long skipBytesFully(final ByteBuffer input, long bytesToSkip) {
+ if (bytesToSkip < 1) {
+ return 0;
+ }
+ final int current = input.position();
+ final int maxSkip = input.remaining();
+ if (maxSkip < bytesToSkip) {
+ bytesToSkip = maxSkip;
+ }
+ input.position(current + (int) bytesToSkip);
+ return bytesToSkip;
+ }
private final String fileName;
+
private SeekableByteChannel channel;
+
private final Archive archive;
+
private int currentEntryIndex = -1;
+
private int currentFolderIndex = -1;
+
private InputStream currentFolderInputStream;
+
private byte[] password;
+
private final SevenZFileOptions options;
private long compressedBytesReadFromCurrentEntry;
+
private long uncompressedBytesReadFromCurrentEntry;
private final ArrayList<InputStream> deferredBlockStreams = new ArrayList<>();
- /** Shared with SevenZOutputFile and tests, neither mutates it. */
- static final byte[] sevenZSignature = { //NOSONAR
- (byte)'7', (byte)'z', (byte)0xBC, (byte)0xAF, (byte)0x27, (byte)0x1C
- };
+ /**
+ * Reads a file as unencrypted 7z archive
+ *
+ * @param fileName the file to read
+ * @throws IOException if reading the archive fails
+ */
+ public SevenZFile(final File fileName) throws IOException {
+ this(fileName, SevenZFileOptions.DEFAULT);
+ }
+
+ /**
+ * Reads a file as 7z archive
+ *
+ * @param fileName the file to read
+ * @param password optional password if the archive is encrypted -
+ * the byte array is supposed to be the UTF16-LE encoded
+ * representation of the password.
+ * @throws IOException if reading the archive fails
+ * @deprecated use the char[]-arg version for the password instead
+ */
+ @Deprecated
+ public SevenZFile(final File fileName, final byte[] password) throws IOException {
+ this(Files.newByteChannel(fileName.toPath(), EnumSet.of(StandardOpenOption.READ)),
+ fileName.getAbsolutePath(), password, true, SevenZFileOptions.DEFAULT);
+ }
/**
* Reads a file as 7z archive
@@ -140,19 +332,15 @@ public class SevenZFile implements Closeable {
}
/**
- * Reads a file as 7z archive
+ * Reads a file as unencrypted 7z archive
*
* @param fileName the file to read
- * @param password optional password if the archive is encrypted -
- * the byte array is supposed to be the UTF16-LE encoded
- * representation of the password.
- * @throws IOException if reading the archive fails
- * @deprecated use the char[]-arg version for the password instead
+ * @param options the options to apply
+ * @throws IOException if reading the archive fails or the memory limit (if set) is too small
+ * @since 1.19
*/
- @Deprecated
- public SevenZFile(final File fileName, final byte[] password) throws IOException {
- this(Files.newByteChannel(fileName.toPath(), EnumSet.of(StandardOpenOption.READ)),
- fileName.getAbsolutePath(), password, true, SevenZFileOptions.DEFAULT);
+ public SevenZFile(final File fileName, final SevenZFileOptions options) throws IOException {
+ this(fileName, null, options);
}
/**
@@ -171,19 +359,24 @@ public class SevenZFile implements Closeable {
}
/**
- * Reads a SeekableByteChannel as 7z archive with addtional options.
+ * Reads a SeekableByteChannel as 7z archive
*
* <p>{@link
* org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
* allows you to read from an in-memory archive.</p>
*
* @param channel the channel to read
- * @param options the options to apply
- * @throws IOException if reading the archive fails or the memory limit (if set) is too small
- * @since 1.19
+ * @param password optional password if the archive is encrypted -
+ * the byte array is supposed to be the UTF16-LE encoded
+ * representation of the password.
+ * @throws IOException if reading the archive fails
+ * @since 1.13
+ * @deprecated use the char[]-arg version for the password instead
*/
- public SevenZFile(final SeekableByteChannel channel, final SevenZFileOptions options) throws IOException {
- this(channel, DEFAULT_FILE_NAME, null, options);
+ @Deprecated
+ public SevenZFile(final SeekableByteChannel channel,
+ final byte[] password) throws IOException {
+ this(channel, DEFAULT_FILE_NAME, password);
}
/**
@@ -222,25 +415,23 @@ public class SevenZFile implements Closeable {
}
/**
- * Reads a SeekableByteChannel as 7z archive
+ * Reads a SeekableByteChannel as 7z archive with addtional options.
*
* <p>{@link
* org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
* allows you to read from an in-memory archive.</p>
*
* @param channel the channel to read
- * @param fileName name of the archive - only used for error reporting
- * @param password optional password if the archive is encrypted
- * @throws IOException if reading the archive fails
- * @since 1.17
+ * @param options the options to apply
+ * @throws IOException if reading the archive fails or the memory limit (if set) is too small
+ * @since 1.19
*/
- public SevenZFile(final SeekableByteChannel channel, final String fileName,
- final char[] password) throws IOException {
- this(channel, fileName, password, SevenZFileOptions.DEFAULT);
+ public SevenZFile(final SeekableByteChannel channel, final SevenZFileOptions options) throws IOException {
+ this(channel, DEFAULT_FILE_NAME, null, options);
}
/**
- * Reads a SeekableByteChannel as 7z archive with addtional options.
+ * Reads a SeekableByteChannel as 7z archive
*
* <p>{@link
* org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
@@ -248,72 +439,14 @@ public class SevenZFile implements Closeable {
*
* @param channel the channel to read
* @param fileName name of the archive - only used for error reporting
- * @param password optional password if the archive is encrypted
- * @param options the options to apply
- * @throws IOException if reading the archive fails or the memory limit (if set) is too small
- * @since 1.19
- */
- public SevenZFile(final SeekableByteChannel channel, final String fileName, final char[] password,
- final SevenZFileOptions options) throws IOException {
- this(channel, fileName, AES256SHA256Decoder.utf16Decode(password), false, options);
- }
-
- /**
- * Reads a SeekableByteChannel as 7z archive
- *
- * <p>{@link
- * org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
- * allows you to read from an in-memory archive.</p>
- *
- * @param channel the channel to read
- * @param fileName name of the archive - only used for error reporting
- * @throws IOException if reading the archive fails
- * @since 1.17
+ * @throws IOException if reading the archive fails
+ * @since 1.17
*/
public SevenZFile(final SeekableByteChannel channel, final String fileName)
throws IOException {
this(channel, fileName, SevenZFileOptions.DEFAULT);
}
- /**
- * Reads a SeekableByteChannel as 7z archive with additional options.
- *
- * <p>{@link
- * org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
- * allows you to read from an in-memory archive.</p>
- *
- * @param channel the channel to read
- * @param fileName name of the archive - only used for error reporting
- * @param options the options to apply
- * @throws IOException if reading the archive fails or the memory limit (if set) is too small
- * @since 1.19
- */
- public SevenZFile(final SeekableByteChannel channel, final String fileName, final SevenZFileOptions options)
- throws IOException {
- this(channel, fileName, null, false, options);
- }
-
- /**
- * Reads a SeekableByteChannel as 7z archive
- *
- * <p>{@link
- * org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
- * allows you to read from an in-memory archive.</p>
- *
- * @param channel the channel to read
- * @param password optional password if the archive is encrypted -
- * the byte array is supposed to be the UTF16-LE encoded
- * representation of the password.
- * @throws IOException if reading the archive fails
- * @since 1.13
- * @deprecated use the char[]-arg version for the password instead
- */
- @Deprecated
- public SevenZFile(final SeekableByteChannel channel,
- final byte[] password) throws IOException {
- this(channel, DEFAULT_FILE_NAME, password);
- }
-
/**
* Reads a SeekableByteChannel as 7z archive
*
@@ -358,819 +491,765 @@ public class SevenZFile implements Closeable {
}
/**
- * Reads a file as unencrypted 7z archive
+ * Reads a SeekableByteChannel as 7z archive
*
- * @param fileName the file to read
+ * <p>{@link
+ * org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
+ * allows you to read from an in-memory archive.</p>
+ *
+ * @param channel the channel to read
+ * @param fileName name of the archive - only used for error reporting
+ * @param password optional password if the archive is encrypted
* @throws IOException if reading the archive fails
+ * @since 1.17
*/
- public SevenZFile(final File fileName) throws IOException {
- this(fileName, SevenZFileOptions.DEFAULT);
+ public SevenZFile(final SeekableByteChannel channel, final String fileName,
+ final char[] password) throws IOException {
+ this(channel, fileName, password, SevenZFileOptions.DEFAULT);
}
/**
- * Reads a file as unencrypted 7z archive
+ * Reads a SeekableByteChannel as 7z archive with addtional options.
*
- * @param fileName the file to read
+ * <p>{@link
+ * org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
+ * allows you to read from an in-memory archive.</p>
+ *
+ * @param channel the channel to read
+ * @param fileName name of the archive - only used for error reporting
+ * @param password optional password if the archive is encrypted
* @param options the options to apply
* @throws IOException if reading the archive fails or the memory limit (if set) is too small
* @since 1.19
*/
- public SevenZFile(final File fileName, final SevenZFileOptions options) throws IOException {
- this(fileName, null, options);
+ public SevenZFile(final SeekableByteChannel channel, final String fileName, final char[] password,
+ final SevenZFileOptions options) throws IOException {
+ this(channel, fileName, AES256SHA256Decoder.utf16Decode(password), false, options);
}
/**
- * Closes the archive.
- * @throws IOException if closing the file fails
+ * Reads a SeekableByteChannel as 7z archive with additional options.
+ *
+ * <p>{@link
+ * org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
+ * allows you to read from an in-memory archive.</p>
+ *
+ * @param channel the channel to read
+ * @param fileName name of the archive - only used for error reporting
+ * @param options the options to apply
+ * @throws IOException if reading the archive fails or the memory limit (if set) is too small
+ * @since 1.19
*/
- @Override
- public void close() throws IOException {
- if (channel != null) {
- try {
- channel.close();
- } finally {
- channel = null;
- if (password != null) {
- Arrays.fill(password, (byte) 0);
+ public SevenZFile(final SeekableByteChannel channel, final String fileName, final SevenZFileOptions options)
+ throws IOException {
+ this(channel, fileName, null, false, options);
+ }
+
+ private InputStream buildDecoderStack(final Folder folder, final long folderOffset,
+ final int firstPackStreamIndex, final SevenZArchiveEntry entry) throws IOException {
+ channel.position(folderOffset);
+ InputStream inputStreamStack = new FilterInputStream(new BufferedInputStream(
+ new BoundedSeekableByteChannelInputStream(channel,
+ archive.packSizes[firstPackStreamIndex]))) {
+ private void count(final int c) {
+ compressedBytesReadFromCurrentEntry += c;
+ }
+ @Override
+ public int read() throws IOException {
+ final int r = in.read();
+ if (r >= 0) {
+ count(1);
}
- password = null;
+ return r;
+ }
+ @Override
+ public int read(final byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+ @Override
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ if (len == 0) {
+ return 0;
+ }
+ final int r = in.read(b, off, len);
+ if (r >= 0) {
+ count(r);
+ }
+ return r;
}
+ };
+ final LinkedList<SevenZMethodConfiguration> methods = new LinkedList<>();
+ for (final Coder coder : folder.getOrderedCoders()) {
+ if (coder.numInStreams != 1 || coder.numOutStreams != 1) {
+ throw new IOException("Multi input/output stream coders are not yet supported");
+ }
+ final SevenZMethod method = SevenZMethod.byId(coder.decompressionMethodId);
+ inputStreamStack = Coders.addDecoder(fileName, inputStreamStack,
+ folder.getUnpackSizeForCoder(coder), coder, password, options.getMaxMemoryLimitInKb());
+ methods.addFirst(new SevenZMethodConfiguration(method,
+ Coders.findByMethod(method).getOptionsFromCoder(coder, inputStreamStack)));
+ }
+ entry.setContentMethods(methods);
+ if (folder.hasCrc) {
+ return new CRC32VerifyingInputStream(inputStreamStack,
+ folder.getUnpackSize(), folder.crc);
}
+ return inputStreamStack;
}
/**
- * Returns the next Archive Entry in this archive.
+ * Build the decoding stream for the entry to be read.
+ * This method may be called from a random access(getInputStream) or
+ * sequential access(getNextEntry).
+ * If this method is called from a random access, some entries may
+ * need to be skipped(we put them to the deferredBlockStreams and
+ * skip them when actually needed to improve the performance)
*
- * @return the next entry,
- * or {@code null} if there are no more entries
- * @throws IOException if the next entry could not be read
+ * @param entryIndex the index of the entry to be read
+ * @param isRandomAccess is this called in a random access
+ * @throws IOException if there are exceptions when reading the file
*/
- public SevenZArchiveEntry getNextEntry() throws IOException {
- if (currentEntryIndex >= archive.files.length - 1) {
- return null;
+ private void buildDecodingStream(final int entryIndex, final boolean isRandomAccess) throws IOException {
+ if (archive.streamMap == null) {
+ throw new IOException("Archive doesn't contain stream information to read entries");
}
- ++currentEntryIndex;
- final SevenZArchiveEntry entry = archive.files[currentEntryIndex];
- if (entry.getName() == null && options.getUseDefaultNameForUnnamedEntries()) {
- entry.setName(getDefaultName());
+ final int folderIndex = archive.streamMap.fileFolderIndex[entryIndex];
+ if (folderIndex < 0) {
+ deferredBlockStreams.clear();
+ // TODO: previously it'd return an empty stream?
+ // new BoundedInputStream(new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY), 0);
+ return;
}
- buildDecodingStream(currentEntryIndex, false);
- uncompressedBytesReadFromCurrentEntry = compressedBytesReadFromCurrentEntry = 0;
- return entry;
- }
-
- /**
- * Returns a copy of meta-data of all archive entries.
- *
- * <p>This method only provides meta-data, the entries can not be
- * used to read the contents, you still need to process all
- * entries in order using {@link #getNextEntry} for that.</p>
- *
- * <p>The content methods are only available for entries that have
- * already been reached via {@link #getNextEntry}.</p>
- *
- * @return a copy of meta-data of all archive entries.
- * @since 1.11
- */
- public Iterable<SevenZArchiveEntry> getEntries() {
- return new ArrayList<>(Arrays.asList(archive.files));
- }
+ final SevenZArchiveEntry file = archive.files[entryIndex];
+ boolean isInSameFolder = false;
+ if (currentFolderIndex == folderIndex) {
+ // (COMPRESS-320).
+ // The current entry is within the same (potentially opened) folder. The
+ // previous stream has to be fully decoded before we can start reading
+ // but don't do it eagerly -- if the user skips over the entire folder nothing
+ // is effectively decompressed.
+ if (entryIndex > 0) {
+ file.setContentMethods(archive.files[entryIndex - 1].getContentMethods());
+ }
- private Archive readHeaders(final byte[] password) throws IOException {
- final ByteBuffer buf = ByteBuffer.allocate(12 /* signature + 2 bytes version + 4 bytes CRC */)
- .order(ByteOrder.LITTLE_ENDIAN);
- readFully(buf);
- final byte[] signature = new byte[6];
- buf.get(signature);
- if (!Arrays.equals(signature, sevenZSignature)) {
- throw new IOException("Bad 7z signature");
+ // if this is called in a random access, then the content methods of previous entry may be null
+ // the content methods should be set to methods of the first entry as it must not be null,
+ // and the content methods would only be set if the content methods was not set
+ if(isRandomAccess && file.getContentMethods() == null) {
+ final int folderFirstFileIndex = archive.streamMap.folderFirstFileIndex[folderIndex];
+ final SevenZArchiveEntry folderFirstFile = archive.files[folderFirstFileIndex];
+ file.setContentMethods(folderFirstFile.getContentMethods());
+ }
+ isInSameFolder = true;
+ } else {
+ currentFolderIndex = folderIndex;
+ // We're opening a new folder. Discard any queued streams/ folder stream.
+ reopenFolderInputStream(folderIndex, file);
}
- // 7zFormat.txt has it wrong - it's first major then minor
- final byte archiveVersionMajor = buf.get();
- final byte archiveVersionMinor = buf.get();
- if (archiveVersionMajor != 0) {
- throw new IOException(String.format("Unsupported 7z version (%d,%d)",
- archiveVersionMajor, archiveVersionMinor));
+
+ boolean haveSkippedEntries = false;
+ if (isRandomAccess) {
+ // entries will only need to be skipped if it's a random access
+ haveSkippedEntries = skipEntriesWhenNeeded(entryIndex, isInSameFolder, folderIndex);
}
- boolean headerLooksValid = false; // See https://www.7-zip.org/recover.html - "There is no correct End Header at the end of archive"
- final long startHeaderCrc = 0xffffFFFFL & buf.getInt();
- if (startHeaderCrc == 0) {
- // This is an indication of a corrupt header - peek the next 20 bytes
- final long currentPosition = channel.position();
- final ByteBuffer peekBuf = ByteBuffer.allocate(20);
- readFully(peekBuf);
- channel.position(currentPosition);
- // Header invalid if all data is 0
- while (peekBuf.hasRemaining()) {
- if (peekBuf.get()!=0) {
- headerLooksValid = true;
- break;
- }
- }
- } else {
- headerLooksValid = true;
+ if (isRandomAccess && currentEntryIndex == entryIndex && !haveSkippedEntries) {
+ // we don't need to add another entry to the deferredBlockStreams when :
+ // 1. If this method is called in a random access and the entry index
+ // to be read equals to the current entry index, the input stream
+ // has already been put in the deferredBlockStreams
+ // 2. If this entry has not been read(which means no entries are skipped)
+ return;
}
- if (headerLooksValid) {
- return initializeArchive(readStartHeader(startHeaderCrc), password, true);
- }
- // No valid header found - probably first file of multipart archive was removed too early. Scan for end header.
- if (options.getTryToRecoverBrokenArchives()) {
- return tryToLocateEndHeader(password);
+ InputStream fileStream = new BoundedInputStream(currentFolderInputStream, file.getSize());
+ if (file.getHasCrc()) {
+ fileStream = new CRC32VerifyingInputStream(fileStream, file.getSize(), file.getCrcValue());
}
- throw new IOException("archive seems to be invalid.\nYou may want to retry and enable the"
- + " tryToRecoverBrokenArchives if the archive could be a multi volume archive that has been closed"
- + " prematurely.");
+
+ deferredBlockStreams.add(fileStream);
}
- private Archive tryToLocateEndHeader(final byte[] password) throws IOException {
- final ByteBuffer nidBuf = ByteBuffer.allocate(1);
- final long searchLimit = 1024L * 1024 * 1;
- // Main header, plus bytes that readStartHeader would read
- final long previousDataSize = channel.position() + 20;
- final long minPos;
- // Determine minimal position - can't start before current position
- if (channel.position() + searchLimit > channel.size()) {
- minPos = channel.position();
- } else {
- minPos = channel.size() - searchLimit;
+ private void calculateStreamMap(final Archive archive) throws IOException {
+ final StreamMap streamMap = new StreamMap();
+
+ int nextFolderPackStreamIndex = 0;
+ final int numFolders = archive.folders != null ? archive.folders.length : 0;
+ streamMap.folderFirstPackStreamIndex = new int[numFolders];
+ for (int i = 0; i < numFolders; i++) {
+ streamMap.folderFirstPackStreamIndex[i] = nextFolderPackStreamIndex;
+ nextFolderPackStreamIndex += archive.folders[i].packedStreams.length;
}
- long pos = channel.size() - 1;
- // Loop: Try from end of archive
- while (pos > minPos) {
- pos--;
- channel.position(pos);
- nidBuf.rewind();
- if (channel.read(nidBuf) < 1) {
- throw new EOFException();
+
+ long nextPackStreamOffset = 0;
+ final int numPackSizes = archive.packSizes.length;
+ streamMap.packStreamOffsets = new long[numPackSizes];
+ for (int i = 0; i < numPackSizes; i++) {
+ streamMap.packStreamOffsets[i] = nextPackStreamOffset;
+ nextPackStreamOffset += archive.packSizes[i];
+ }
+
+ streamMap.folderFirstFileIndex = new int[numFolders];
+ streamMap.fileFolderIndex = new int[archive.files.length];
+ int nextFolderIndex = 0;
+ int nextFolderUnpackStreamIndex = 0;
+ for (int i = 0; i < archive.files.length; i++) {
+ if (!archive.files[i].hasStream() && nextFolderUnpackStreamIndex == 0) {
+ streamMap.fileFolderIndex[i] = -1;
+ continue;
}
- final int nid = nidBuf.array()[0];
- // First indicator: Byte equals one of these header identifiers
- if (nid == NID.kEncodedHeader || nid == NID.kHeader) {
- try {
- // Try to initialize Archive structure from here
- final StartHeader startHeader = new StartHeader();
- startHeader.nextHeaderOffset = pos - previousDataSize;
- startHeader.nextHeaderSize = channel.size() - pos;
- final Archive result = initializeArchive(startHeader, password, false);
- // Sanity check: There must be some data...
- if (result.packSizes.length > 0 && result.files.length > 0) {
- return result;
+ if (nextFolderUnpackStreamIndex == 0) {
+ for (; nextFolderIndex < archive.folders.length; ++nextFolderIndex) {
+ streamMap.folderFirstFileIndex[nextFolderIndex] = i;
+ if (archive.folders[nextFolderIndex].numUnpackSubStreams > 0) {
+ break;
}
- } catch (final Exception ignore) {
- // Wrong guess...
+ }
+ if (nextFolderIndex >= archive.folders.length) {
+ throw new IOException("Too few folders in archive");
}
}
- }
- throw new IOException("Start header corrupt and unable to guess end header");
- }
-
- private Archive initializeArchive(final StartHeader startHeader, final byte[] password, final boolean verifyCrc) throws IOException {
- assertFitsIntoNonNegativeInt("nextHeaderSize", startHeader.nextHeaderSize);
- final int nextHeaderSizeInt = (int) startHeader.nextHeaderSize;
- channel.position(SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset);
- if (verifyCrc) {
- final long position = channel.position();
- final CheckedInputStream cis = new CheckedInputStream(Channels.newInputStream(channel), new CRC32());
- if (cis.skip(nextHeaderSizeInt) != nextHeaderSizeInt) {
- throw new IOException("Problem computing NextHeader CRC-32");
+ streamMap.fileFolderIndex[i] = nextFolderIndex;
+ if (!archive.files[i].hasStream()) {
+ continue;
}
- if (startHeader.nextHeaderCrc != cis.getChecksum().getValue()) {
- throw new IOException("NextHeader CRC-32 mismatch");
+ ++nextFolderUnpackStreamIndex;
+ if (nextFolderUnpackStreamIndex >= archive.folders[nextFolderIndex].numUnpackSubStreams) {
+ ++nextFolderIndex;
+ nextFolderUnpackStreamIndex = 0;
}
- channel.position(position);
- }
- Archive archive = new Archive();
- ByteBuffer buf = ByteBuffer.allocate(nextHeaderSizeInt).order(ByteOrder.LITTLE_ENDIAN);
- readFully(buf);
- int nid = getUnsignedByte(buf);
- if (nid == NID.kEncodedHeader) {
- buf = readEncodedHeader(buf, archive, password);
- // Archive gets rebuilt with the new header
- archive = new Archive();
- nid = getUnsignedByte(buf);
}
- if (nid != NID.kHeader) {
- throw new IOException("Broken or unsupported archive: no Header");
- }
- readHeader(buf, archive);
- archive.subStreamsInfo = null;
- return archive;
- }
-
- private StartHeader readStartHeader(final long startHeaderCrc) throws IOException {
- final StartHeader startHeader = new StartHeader();
- // using Stream rather than ByteBuffer for the benefit of the
- // built-in CRC check
- try (DataInputStream dataInputStream = new DataInputStream(new CRC32VerifyingInputStream(
- new BoundedSeekableByteChannelInputStream(channel, 20), 20, startHeaderCrc))) {
- startHeader.nextHeaderOffset = Long.reverseBytes(dataInputStream.readLong());
- if (startHeader.nextHeaderOffset < 0
- || startHeader.nextHeaderOffset + SIGNATURE_HEADER_SIZE > channel.size()) {
- throw new IOException("nextHeaderOffset is out of bounds");
- }
-
- startHeader.nextHeaderSize = Long.reverseBytes(dataInputStream.readLong());
- final long nextHeaderEnd = startHeader.nextHeaderOffset + startHeader.nextHeaderSize;
- if (nextHeaderEnd < startHeader.nextHeaderOffset
- || nextHeaderEnd + SIGNATURE_HEADER_SIZE > channel.size()) {
- throw new IOException("nextHeaderSize is out of bounds");
- }
-
- startHeader.nextHeaderCrc = 0xffffFFFFL & Integer.reverseBytes(dataInputStream.readInt());
- return startHeader;
- }
+ archive.streamMap = streamMap;
}
- private void readHeader(final ByteBuffer header, final Archive archive) throws IOException {
- final int pos = header.position();
- final ArchiveStatistics stats = sanityCheckAndCollectStatistics(header);
- stats.assertValidity(options.getMaxMemoryLimitInKb());
- header.position(pos);
-
- int nid = getUnsignedByte(header);
-
- if (nid == NID.kArchiveProperties) {
- readArchiveProperties(header);
- nid = getUnsignedByte(header);
- }
-
- if (nid == NID.kAdditionalStreamsInfo) {
- throw new IOException("Additional streams unsupported");
- //nid = getUnsignedByte(header);
- }
-
- if (nid == NID.kMainStreamsInfo) {
- readStreamsInfo(header, archive);
- nid = getUnsignedByte(header);
- }
-
- if (nid == NID.kFilesInfo) {
- readFilesInfo(header, archive);
- nid = getUnsignedByte(header);
+ private void checkEntryIsInitialized(final Map<Integer, SevenZArchiveEntry> archiveEntries, final int index) {
+ if (archiveEntries.get(index) == null) {
+ archiveEntries.put(index, new SevenZArchiveEntry());
}
}
- private ArchiveStatistics sanityCheckAndCollectStatistics(final ByteBuffer header)
- throws IOException {
- final ArchiveStatistics stats = new ArchiveStatistics();
-
- int nid = getUnsignedByte(header);
-
- if (nid == NID.kArchiveProperties) {
- sanityCheckArchiveProperties(header);
- nid = getUnsignedByte(header);
- }
-
- if (nid == NID.kAdditionalStreamsInfo) {
- throw new IOException("Additional streams unsupported");
- //nid = getUnsignedByte(header);
+ /**
+ * Closes the archive.
+ * @throws IOException if closing the file fails
+ */
+ @Override
+ public void close() throws IOException {
+ if (channel != null) {
+ try {
+ channel.close();
+ } finally {
+ channel = null;
+ if (password != null) {
+ Arrays.fill(password, (byte) 0);
+ }
+ password = null;
+ }
}
+ }
- if (nid == NID.kMainStreamsInfo) {
- sanityCheckStreamsInfo(header, stats);
- nid = getUnsignedByte(header);
+ private InputStream getCurrentStream() throws IOException {
+ if (archive.files[currentEntryIndex].getSize() == 0) {
+ return new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY);
}
-
- if (nid == NID.kFilesInfo) {
- sanityCheckFilesInfo(header, stats);
- nid = getUnsignedByte(header);
+ if (deferredBlockStreams.isEmpty()) {
+ throw new IllegalStateException("No current 7z entry (call getNextEntry() first).");
}
- if (nid != NID.kEnd) {
- throw new IOException("Badly terminated header, found " + nid);
+ while (deferredBlockStreams.size() > 1) {
+ // In solid compression mode we need to decompress all leading folder'
+ // streams to get access to an entry. We defer this until really needed
+ // so that entire blocks can be skipped without wasting time for decompression.
+ try (final InputStream stream = deferredBlockStreams.remove(0)) {
+ IOUtils.skip(stream, Long.MAX_VALUE);
+ }
+ compressedBytesReadFromCurrentEntry = 0;
}
- return stats;
+ return deferredBlockStreams.get(0);
}
- private void readArchiveProperties(final ByteBuffer input) throws IOException {
- // FIXME: the reference implementation just throws them away?
- int nid = getUnsignedByte(input);
- while (nid != NID.kEnd) {
- final long propertySize = readUint64(input);
- final byte[] property = new byte[(int)propertySize];
- get(input, property);
- nid = getUnsignedByte(input);
+ /**
+ * Derives a default file name from the archive name - if known.
+ *
+ * <p>This implements the same heuristics the 7z tools use. In
+ * 7z's case if an archive contains entries without a name -
+ * i.e. {@link SevenZArchiveEntry#getName} returns {@code null} -
+ * then its command line and GUI tools will use this default name
+ * when extracting the entries.</p>
+ *
+ * @return null if the name of the archive is unknown. Otherwise
+ * if the name of the archive has got any extension, it is
+ * stripped and the remainder returned. Finally if the name of the
+ * archive hasn't got any extension then a {@code ~} character is
+ * appended to the archive name.
+ *
+ * @since 1.19
+ */
+ public String getDefaultName() {
+ if (DEFAULT_FILE_NAME.equals(fileName) || fileName == null) {
+ return null;
}
- }
- private void sanityCheckArchiveProperties(final ByteBuffer header)
- throws IOException {
- int nid = getUnsignedByte(header);
- while (nid != NID.kEnd) {
- final int propertySize =
- assertFitsIntoNonNegativeInt("propertySize", readUint64(header));
- if (skipBytesFully(header, propertySize) < propertySize) {
- throw new IOException("invalid property size");
- }
- nid = getUnsignedByte(header);
+ final String lastSegment = new File(fileName).getName();
+ final int dotPos = lastSegment.lastIndexOf(".");
+ if (dotPos > 0) { // if the file starts with a dot then this is not an extension
+ return lastSegment.substring(0, dotPos);
}
+ return lastSegment + "~";
}
- private ByteBuffer readEncodedHeader(final ByteBuffer header, final Archive archive,
- final byte[] password) throws IOException {
- final int pos = header.position();
- final ArchiveStatistics stats = new ArchiveStatistics();
- sanityCheckStreamsInfo(header, stats);
- stats.assertValidity(options.getMaxMemoryLimitInKb());
- header.position(pos);
-
- readStreamsInfo(header, archive);
+ /**
+ * Returns a copy of meta-data of all archive entries.
+ *
+ * <p>This method only provides meta-data, the entries can not be
+ * used to read the contents, you still need to process all
+ * entries in order using {@link #getNextEntry} for that.</p>
+ *
+ * <p>The content methods are only available for entries that have
+ * already been reached via {@link #getNextEntry}.</p>
+ *
+ * @return a copy of meta-data of all archive entries.
+ * @since 1.11
+ */
+ public Iterable<SevenZArchiveEntry> getEntries() {
+ return new ArrayList<>(Arrays.asList(archive.files));
+ }
- if (archive.folders == null || archive.folders.length == 0) {
- throw new IOException("no folders, can't read encoded header");
+ /**
+ * Returns an InputStream for reading the contents of the given entry.
+ *
+ * <p>For archives using solid compression randomly accessing
+ * entries will be significantly slower than reading the archive
+ * sequentially.</p>
+ *
+ * @param entry the entry to get the stream for.
+ * @return a stream to read the entry from.
+ * @throws IOException if unable to create an input stream from the zipentry
+ * @since 1.20
+ */
+ public InputStream getInputStream(final SevenZArchiveEntry entry) throws IOException {
+ int entryIndex = -1;
+ for (int i = 0; i < this.archive.files.length;i++) {
+ if (entry == this.archive.files[i]) {
+ entryIndex = i;
+ break;
+ }
}
- if (archive.packSizes == null || archive.packSizes.length == 0) {
- throw new IOException("no packed streams, can't read encoded header");
+
+ if (entryIndex < 0) {
+ throw new IllegalArgumentException("Can not find " + entry.getName() + " in " + this.fileName);
}
- // FIXME: merge with buildDecodingStream()/buildDecoderStack() at some stage?
- final Folder folder = archive.folders[0];
- final int firstPackStreamIndex = 0;
- final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos +
- 0;
+ buildDecodingStream(entryIndex, true);
+ currentEntryIndex = entryIndex;
+ currentFolderIndex = archive.streamMap.fileFolderIndex[entryIndex];
+ return getCurrentStream();
+ }
- channel.position(folderOffset);
- InputStream inputStreamStack = new BoundedSeekableByteChannelInputStream(channel,
- archive.packSizes[firstPackStreamIndex]);
- for (final Coder coder : folder.getOrderedCoders()) {
- if (coder.numInStreams != 1 || coder.numOutStreams != 1) {
- throw new IOException("Multi input/output stream coders are not yet supported");
- }
- inputStreamStack = Coders.addDecoder(fileName, inputStreamStack, //NOSONAR
- folder.getUnpackSizeForCoder(coder), coder, password, options.getMaxMemoryLimitInKb());
- }
- if (folder.hasCrc) {
- inputStreamStack = new CRC32VerifyingInputStream(inputStreamStack,
- folder.getUnpackSize(), folder.crc);
+ /**
+ * Returns the next Archive Entry in this archive.
+ *
+ * @return the next entry,
+ * or {@code null} if there are no more entries
+ * @throws IOException if the next entry could not be read
+ */
+ public SevenZArchiveEntry getNextEntry() throws IOException {
+ if (currentEntryIndex >= archive.files.length - 1) {
+ return null;
}
- final int unpackSize = assertFitsIntoNonNegativeInt("unpackSize", folder.getUnpackSize());
- final byte[] nextHeader = IOUtils.readRange(inputStreamStack, unpackSize);
- if (nextHeader.length < unpackSize) {
- throw new IOException("premature end of stream");
+ ++currentEntryIndex;
+ final SevenZArchiveEntry entry = archive.files[currentEntryIndex];
+ if (entry.getName() == null && options.getUseDefaultNameForUnnamedEntries()) {
+ entry.setName(getDefaultName());
}
- inputStreamStack.close();
- return ByteBuffer.wrap(nextHeader).order(ByteOrder.LITTLE_ENDIAN);
+ buildDecodingStream(currentEntryIndex, false);
+ uncompressedBytesReadFromCurrentEntry = compressedBytesReadFromCurrentEntry = 0;
+ return entry;
}
- private void sanityCheckStreamsInfo(final ByteBuffer header,
- final ArchiveStatistics stats) throws IOException {
- int nid = getUnsignedByte(header);
-
- if (nid == NID.kPackInfo) {
- sanityCheckPackInfo(header, stats);
- nid = getUnsignedByte(header);
- }
-
- if (nid == NID.kUnpackInfo) {
- sanityCheckUnpackInfo(header, stats);
- nid = getUnsignedByte(header);
- }
+ /**
+ * Provides statistics for bytes read from the current entry.
+ *
+ * @return statistics for bytes read from the current entry
+ * @since 1.17
+ */
+ public InputStreamStatistics getStatisticsForCurrentEntry() {
+ return new InputStreamStatistics() {
+ @Override
+ public long getCompressedCount() {
+ return compressedBytesReadFromCurrentEntry;
+ }
+ @Override
+ public long getUncompressedCount() {
+ return uncompressedBytesReadFromCurrentEntry;
+ }
+ };
+ }
- if (nid == NID.kSubStreamsInfo) {
- sanityCheckSubStreamsInfo(header, stats);
- nid = getUnsignedByte(header);
- }
+ /**
+ * Find out if any data of current entry has been read or not.
+ * This is achieved by comparing the bytes remaining to read
+ * and the size of the file.
+ *
+ * @return true if any data of current entry has been read
+ * @since 1.21
+ */
+ private boolean hasCurrentEntryBeenRead() {
+ boolean hasCurrentEntryBeenRead = false;
+ if (!deferredBlockStreams.isEmpty()) {
+ final InputStream currentEntryInputStream = deferredBlockStreams.get(deferredBlockStreams.size() - 1);
+ // get the bytes remaining to read, and compare it with the size of
+ // the file to figure out if the file has been read
+ if (currentEntryInputStream instanceof CRC32VerifyingInputStream) {
+ hasCurrentEntryBeenRead = ((CRC32VerifyingInputStream) currentEntryInputStream).getBytesRemaining() != archive.files[currentEntryIndex].getSize();
+ }
- if (nid != NID.kEnd) {
- throw new IOException("Badly terminated StreamsInfo");
+ if (currentEntryInputStream instanceof BoundedInputStream) {
+ hasCurrentEntryBeenRead = ((BoundedInputStream) currentEntryInputStream).getBytesRemaining() != archive.files[currentEntryIndex].getSize();
+ }
}
+ return hasCurrentEntryBeenRead;
}
- private void readStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException {
- int nid = getUnsignedByte(header);
-
- if (nid == NID.kPackInfo) {
- readPackInfo(header, archive);
- nid = getUnsignedByte(header);
+ private Archive initializeArchive(final StartHeader startHeader, final byte[] password, final boolean verifyCrc) throws IOException {
+ assertFitsIntoNonNegativeInt("nextHeaderSize", startHeader.nextHeaderSize);
+ final int nextHeaderSizeInt = (int) startHeader.nextHeaderSize;
+ channel.position(SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset);
+ if (verifyCrc) {
+ final long position = channel.position();
+ final CheckedInputStream cis = new CheckedInputStream(Channels.newInputStream(channel), new CRC32());
+ if (cis.skip(nextHeaderSizeInt) != nextHeaderSizeInt) {
+ throw new IOException("Problem computing NextHeader CRC-32");
+ }
+ if (startHeader.nextHeaderCrc != cis.getChecksum().getValue()) {
+ throw new IOException("NextHeader CRC-32 mismatch");
+ }
+ channel.position(position);
}
-
- if (nid == NID.kUnpackInfo) {
- readUnpackInfo(header, archive);
- nid = getUnsignedByte(header);
- } else {
- // archive without unpack/coders info
- archive.folders = Folder.EMPTY_FOLDER_ARRAY;
+ Archive archive = new Archive();
+ ByteBuffer buf = ByteBuffer.allocate(nextHeaderSizeInt).order(ByteOrder.LITTLE_ENDIAN);
+ readFully(buf);
+ int nid = getUnsignedByte(buf);
+ if (nid == NID.kEncodedHeader) {
+ buf = readEncodedHeader(buf, archive, password);
+ // Archive gets rebuilt with the new header
+ archive = new Archive();
+ nid = getUnsignedByte(buf);
}
-
- if (nid == NID.kSubStreamsInfo) {
- readSubStreamsInfo(header, archive);
- nid = getUnsignedByte(header);
+ if (nid != NID.kHeader) {
+ throw new IOException("Broken or unsupported archive: no Header");
}
+ readHeader(buf, archive);
+ archive.subStreamsInfo = null;
+ return archive;
}
- private void sanityCheckPackInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
- final long packPos = readUint64(header);
- if (packPos < 0 || SIGNATURE_HEADER_SIZE + packPos > channel.size()
- || SIGNATURE_HEADER_SIZE + packPos < 0) {
- throw new IOException("packPos (" + packPos + ") is out of range");
- }
- final long numPackStreams = readUint64(header);
- stats.numberOfPackedStreams = assertFitsIntoNonNegativeInt("numPackStreams", numPackStreams);
- int nid = getUnsignedByte(header);
- if (nid == NID.kSize) {
- long totalPackSizes = 0;
- for (int i = 0; i < stats.numberOfPackedStreams; i++) {
- final long packSize = readUint64(header);
- totalPackSizes += packSize;
- final long endOfPackStreams = SIGNATURE_HEADER_SIZE + packPos + totalPackSizes;
- if (packSize < 0
- || endOfPackStreams > channel.size()
- || endOfPackStreams < packPos) {
- throw new IOException("packSize (" + packSize + ") is out of range");
- }
- }
- nid = getUnsignedByte(header);
- }
-
- if (nid == NID.kCRC) {
- final int crcsDefined = readAllOrBits(header, stats.numberOfPackedStreams)
- .cardinality();
- if (skipBytesFully(header, 4 * crcsDefined) < 4 * crcsDefined) {
- throw new IOException("invalid number of CRCs in PackInfo");
- }
- nid = getUnsignedByte(header);
+ /**
+ * Reads a byte of data.
+ *
+ * @return the byte read, or -1 if end of input is reached
+ * @throws IOException
+ * if an I/O error has occurred
+ */
+ public int read() throws IOException {
+ final int b = getCurrentStream().read();
+ if (b >= 0) {
+ uncompressedBytesReadFromCurrentEntry++;
}
+ return b;
+ }
- if (nid != NID.kEnd) {
- throw new IOException("Badly terminated PackInfo (" + nid + ")");
- }
+ /**
+ * Reads data into an array of bytes.
+ *
+ * @param b the array to write data to
+ * @return the number of bytes read, or -1 if end of input is reached
+ * @throws IOException
+ * if an I/O error has occurred
+ */
+ public int read(final byte[] b) throws IOException {
+ return read(b, 0, b.length);
}
- private void readPackInfo(final ByteBuffer header, final Archive archive) throws IOException {
- archive.packPos = readUint64(header);
- final int numPackStreamsInt = (int) readUint64(header);
- int nid = getUnsignedByte(header);
- if (nid == NID.kSize) {
- archive.packSizes = new long[numPackStreamsInt];
- for (int i = 0; i < archive.packSizes.length; i++) {
- archive.packSizes[i] = readUint64(header);
- }
- nid = getUnsignedByte(header);
+ /**
+ * Reads data into an array of bytes.
+ *
+ * @param b the array to write data to
+ * @param off offset into the buffer to start filling at
+ * @param len of bytes to read
+ * @return the number of bytes read, or -1 if end of input is reached
+ * @throws IOException
+ * if an I/O error has occurred
+ */
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ if (len == 0) {
+ return 0;
}
-
- if (nid == NID.kCRC) {
- archive.packCrcsDefined = readAllOrBits(header, numPackStreamsInt);
- archive.packCrcs = new long[numPackStreamsInt];
- for (int i = 0; i < numPackStreamsInt; i++) {
- if (archive.packCrcsDefined.get(i)) {
- archive.packCrcs[i] = 0xffffFFFFL & getInt(header);
- }
- }
-
- nid = getUnsignedByte(header);
+ final int cnt = getCurrentStream().read(b, off, len);
+ if (cnt > 0) {
+ uncompressedBytesReadFromCurrentEntry += cnt;
}
+ return cnt;
}
- private void sanityCheckUnpackInfo(final ByteBuffer header, final ArchiveStatistics stats)
- throws IOException {
- int nid = getUnsignedByte(header);
- if (nid != NID.kFolder) {
- throw new IOException("Expected kFolder, got " + nid);
- }
- final long numFolders = readUint64(header);
- stats.numberOfFolders = assertFitsIntoNonNegativeInt("numFolders", numFolders);
- final int external = getUnsignedByte(header);
- if (external != 0) {
- throw new IOException("External unsupported");
+ private BitSet readAllOrBits(final ByteBuffer header, final int size) throws IOException {
+ final int areAllDefined = getUnsignedByte(header);
+ final BitSet bits;
+ if (areAllDefined != 0) {
+ bits = new BitSet(size);
+ for (int i = 0; i < size; i++) {
+ bits.set(i, true);
+ }
+ } else {
+ bits = readBits(header, size);
}
+ return bits;
+ }
- final List<Integer> numberOfOutputStreamsPerFolder = new LinkedList<>();
- for (int i = 0; i < stats.numberOfFolders; i++) {
- numberOfOutputStreamsPerFolder.add(sanityCheckFolder(header, stats));
+ private void readArchiveProperties(final ByteBuffer input) throws IOException {
+ // FIXME: the reference implementation just throws them away?
+ int nid = getUnsignedByte(input);
+ while (nid != NID.kEnd) {
+ final long propertySize = readUint64(input);
+ final byte[] property = new byte[(int)propertySize];
+ get(input, property);
+ nid = getUnsignedByte(input);
}
+ }
- final long totalNumberOfBindPairs = stats.numberOfOutStreams - stats.numberOfFolders;
- final long packedStreamsRequiredByFolders = stats.numberOfInStreams - totalNumberOfBindPairs;
- if (packedStreamsRequiredByFolders < stats.numberOfPackedStreams) {
- throw new IOException("archive doesn't contain enough packed streams");
+ private BitSet readBits(final ByteBuffer header, final int size) throws IOException {
+ final BitSet bits = new BitSet(size);
+ int mask = 0;
+ int cache = 0;
+ for (int i = 0; i < size; i++) {
+ if (mask == 0) {
+ mask = 0x80;
+ cache = getUnsignedByte(header);
+ }
+ bits.set(i, (cache & mask) != 0);
+ mask >>>= 1;
}
+ return bits;
+ }
- nid = getUnsignedByte(header);
- if (nid != NID.kCodersUnpackSize) {
- throw new IOException("Expected kCodersUnpackSize, got " + nid);
- }
+ private ByteBuffer readEncodedHeader(final ByteBuffer header, final Archive archive,
+ final byte[] password) throws IOException {
+ final int pos = header.position();
+ final ArchiveStatistics stats = new ArchiveStatistics();
+ sanityCheckStreamsInfo(header, stats);
+ stats.assertValidity(options.getMaxMemoryLimitInKb());
+ header.position(pos);
- for (final int numberOfOutputStreams : numberOfOutputStreamsPerFolder) {
- for (int i = 0; i < numberOfOutputStreams; i++) {
- final long unpackSize = readUint64(header);
- if (unpackSize < 0) {
- throw new IllegalArgumentException("negative unpackSize");
- }
- }
- }
+ readStreamsInfo(header, archive);
- nid = getUnsignedByte(header);
- if (nid == NID.kCRC) {
- stats.folderHasCrc = readAllOrBits(header, stats.numberOfFolders);
- final int crcsDefined = stats.folderHasCrc.cardinality();
- if (skipBytesFully(header, 4 * crcsDefined) < 4 * crcsDefined) {
- throw new IOException("invalid number of CRCs in UnpackInfo");
- }
- nid = getUnsignedByte(header);
+ if (archive.folders == null || archive.folders.length == 0) {
+ throw new IOException("no folders, can't read encoded header");
}
-
- if (nid != NID.kEnd) {
- throw new IOException("Badly terminated UnpackInfo");
+ if (archive.packSizes == null || archive.packSizes.length == 0) {
+ throw new IOException("no packed streams, can't read encoded header");
}
- }
- private void readUnpackInfo(final ByteBuffer header, final Archive archive) throws IOException {
- int nid = getUnsignedByte(header);
- final int numFoldersInt = (int) readUint64(header);
- final Folder[] folders = new Folder[numFoldersInt];
- archive.folders = folders;
- /* final int external = */ getUnsignedByte(header);
- for (int i = 0; i < numFoldersInt; i++) {
- folders[i] = readFolder(header);
- }
+ // FIXME: merge with buildDecodingStream()/buildDecoderStack() at some stage?
+ final Folder folder = archive.folders[0];
+ final int firstPackStreamIndex = 0;
+ final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos +
+ 0;
- nid = getUnsignedByte(header);
- for (final Folder folder : folders) {
- assertFitsIntoNonNegativeInt("totalOutputStreams", folder.totalOutputStreams);
- folder.unpackSizes = new long[(int)folder.totalOutputStreams];
- for (int i = 0; i < folder.totalOutputStreams; i++) {
- folder.unpackSizes[i] = readUint64(header);
+ channel.position(folderOffset);
+ InputStream inputStreamStack = new BoundedSeekableByteChannelInputStream(channel,
+ archive.packSizes[firstPackStreamIndex]);
+ for (final Coder coder : folder.getOrderedCoders()) {
+ if (coder.numInStreams != 1 || coder.numOutStreams != 1) {
+ throw new IOException("Multi input/output stream coders are not yet supported");
}
+ inputStreamStack = Coders.addDecoder(fileName, inputStreamStack, //NOSONAR
+ folder.getUnpackSizeForCoder(coder), coder, password, options.getMaxMemoryLimitInKb());
}
-
- nid = getUnsignedByte(header);
- if (nid == NID.kCRC) {
- final BitSet crcsDefined = readAllOrBits(header, numFoldersInt);
- for (int i = 0; i < numFoldersInt; i++) {
- if (crcsDefined.get(i)) {
- folders[i].hasCrc = true;
- folders[i].crc = 0xffffFFFFL & getInt(header);
- } else {
- folders[i].hasCrc = false;
- }
- }
-
- nid = getUnsignedByte(header);
+ if (folder.hasCrc) {
+ inputStreamStack = new CRC32VerifyingInputStream(inputStreamStack,
+ folder.getUnpackSize(), folder.crc);
+ }
+ final int unpackSize = assertFitsIntoNonNegativeInt("unpackSize", folder.getUnpackSize());
+ final byte[] nextHeader = IOUtils.readRange(inputStreamStack, unpackSize);
+ if (nextHeader.length < unpackSize) {
+ throw new IOException("premature end of stream");
}
+ inputStreamStack.close();
+ return ByteBuffer.wrap(nextHeader).order(ByteOrder.LITTLE_ENDIAN);
}
- private void sanityCheckSubStreamsInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
-
- int nid = getUnsignedByte(header);
- final List<Integer> numUnpackSubStreamsPerFolder = new LinkedList<>();
- if (nid == NID.kNumUnpackStream) {
- for (int i = 0; i < stats.numberOfFolders; i++) {
- numUnpackSubStreamsPerFolder.add(assertFitsIntoNonNegativeInt("numStreams", readUint64(header)));
+ private void readFilesInfo(final ByteBuffer header, final Archive archive) throws IOException {
+ final int numFilesInt = (int) readUint64(header);
+ final Map<Integer, SevenZArchiveEntry> fileMap = new LinkedHashMap<>();
+ BitSet isEmptyStream = null;
+ BitSet isEmptyFile = null;
+ BitSet isAnti = null;
+ while (true) {
+ final int propertyType = getUnsignedByte(header);
+ if (propertyType == 0) {
+ break;
}
- stats.numberOfUnpackSubStreams = numUnpackSubStreamsPerFolder.stream().mapToLong(Integer::longValue).sum();
- nid = getUnsignedByte(header);
- } else {
- stats.numberOfUnpackSubStreams = stats.numberOfFolders;
- }
-
- assertFitsIntoNonNegativeInt("totalUnpackStreams", stats.numberOfUnpackSubStreams);
-
- if (nid == NID.kSize) {
- for (final int numUnpackSubStreams : numUnpackSubStreamsPerFolder) {
- if (numUnpackSubStreams == 0) {
- continue;
+ final long size = readUint64(header);
+ switch (propertyType) {
+ case NID.kEmptyStream: {
+ isEmptyStream = readBits(header, numFilesInt);
+ break;
}
- for (int i = 0; i < numUnpackSubStreams - 1; i++) {
- final long size = readUint64(header);
- if (size < 0) {
- throw new IOException("negative unpackSize");
+ case NID.kEmptyFile: {
+ isEmptyFile = readBits(header, isEmptyStream.cardinality());
+ break;
+ }
+ case NID.kAnti: {
+ isAnti = readBits(header, isEmptyStream.cardinality());
+ break;
+ }
+ case NID.kName: {
+ /* final int external = */ getUnsignedByte(header);
+ final byte[] names = new byte[(int) (size - 1)];
+ final int namesLength = names.length;
+ get(header, names);
+ int nextFile = 0;
+ int nextName = 0;
+ for (int i = 0; i < namesLength; i += 2) {
+ if (names[i] == 0 && names[i + 1] == 0) {
+ checkEntryIsInitialized(fileMap, nextFile);
+ fileMap.get(nextFile).setName(new String(names, nextName, i - nextName, UTF_16LE));
+ nextName = i + 2;
+ nextFile++;
+ }
+ }
+ if (nextName != namesLength || nextFile != numFilesInt) {
+ throw new IOException("Error parsing file names");
}
+ break;
}
- }
- nid = getUnsignedByte(header);
- }
-
- int numDigests = 0;
- if (numUnpackSubStreamsPerFolder.isEmpty()) {
- numDigests = stats.folderHasCrc == null ? stats.numberOfFolders
- : stats.numberOfFolders - stats.folderHasCrc.cardinality();
- } else {
- int folderIdx = 0;
- for (final int numUnpackSubStreams : numUnpackSubStreamsPerFolder) {
- if (numUnpackSubStreams != 1 || stats.folderHasCrc == null
- || !stats.folderHasCrc.get(folderIdx++)) {
- numDigests += numUnpackSubStreams;
+ case NID.kCTime: {
+ final BitSet timesDefined = readAllOrBits(header, numFilesInt);
+ /* final int external = */ getUnsignedByte(header);
+ for (int i = 0; i < numFilesInt; i++) {
+ checkEntryIsInitialized(fileMap, i);
+ final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
+ entryAtIndex.setHasCreationDate(timesDefined.get(i));
+ if (entryAtIndex.getHasCreationDate()) {
+ entryAtIndex.setCreationDate(getLong(header));
+ }
+ }
+ break;
}
- }
- }
-
- if (nid == NID.kCRC) {
- assertFitsIntoNonNegativeInt("numDigests", numDigests);
- final int missingCrcs = readAllOrBits(header, numDigests)
- .cardinality();
- if (skipBytesFully(header, 4 * missingCrcs) < 4 * missingCrcs) {
- throw new IOException("invalid number of missing CRCs in SubStreamInfo");
- }
- nid = getUnsignedByte(header);
- }
-
- if (nid != NID.kEnd) {
- throw new IOException("Badly terminated SubStreamsInfo");
- }
- }
-
- private void readSubStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException {
- for (final Folder folder : archive.folders) {
- folder.numUnpackSubStreams = 1;
- }
- long unpackStreamsCount = archive.folders.length;
-
- int nid = getUnsignedByte(header);
- if (nid == NID.kNumUnpackStream) {
- unpackStreamsCount = 0;
- for (final Folder folder : archive.folders) {
- final long numStreams = readUint64(header);
- folder.numUnpackSubStreams = (int)numStreams;
- unpackStreamsCount += numStreams;
- }
- nid = getUnsignedByte(header);
- }
+ case NID.kATime: {
+ final BitSet timesDefined = readAllOrBits(header, numFilesInt);
+ /* final int external = */ getUnsignedByte(header);
+ for (int i = 0; i < numFilesInt; i++) {
+ checkEntryIsInitialized(fileMap, i);
+ final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
+ entryAtIndex.setHasAccessDate(timesDefined.get(i));
+ if (entryAtIndex.getHasAccessDate()) {
+ entryAtIndex.setAccessDate(getLong(header));
+ }
+ }
+ break;
+ }
+ case NID.kMTime: {
+ final BitSet timesDefined = readAllOrBits(header, numFilesInt);
+ /* final int external = */ getUnsignedByte(header);
+ for (int i = 0; i < numFilesInt; i++) {
+ checkEntryIsInitialized(fileMap, i);
+ final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
+ entryAtIndex.setHasLastModifiedDate(timesDefined.get(i));
+ if (entryAtIndex.getHasLastModifiedDate()) {
+ entryAtIndex.setLastModifiedDate(getLong(header));
+ }
+ }
+ break;
+ }
+ case NID.kWinAttributes: {
+ final BitSet attributesDefined = readAllOrBits(header, numFilesInt);
+ /* final int external = */ getUnsignedByte(header);
+ for (int i = 0; i < numFilesInt; i++) {
+ checkEntryIsInitialized(fileMap, i);
+ final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
+ entryAtIndex.setHasWindowsAttributes(attributesDefined.get(i));
+ if (entryAtIndex.getHasWindowsAttributes()) {
+ entryAtIndex.setWindowsAttributes(getInt(header));
+ }
+ }
+ break;
+ }
+ case NID.kDummy: {
+ // 7z 9.20 asserts the content is all zeros and ignores the property
+ // Compress up to 1.8.1 would throw an exception, now we ignore it (see COMPRESS-287
- final int totalUnpackStreams = (int) unpackStreamsCount;
- final SubStreamsInfo subStreamsInfo = new SubStreamsInfo();
- subStreamsInfo.unpackSizes = new long[totalUnpackStreams];
- subStreamsInfo.hasCrc = new BitSet(totalUnpackStreams);
- subStreamsInfo.crcs = new long[totalUnpackStreams];
+ skipBytesFully(header, size);
+ break;
+ }
- int nextUnpackStream = 0;
- for (final Folder folder : archive.folders) {
- if (folder.numUnpackSubStreams == 0) {
- continue;
- }
- long sum = 0;
- if (nid == NID.kSize) {
- for (int i = 0; i < folder.numUnpackSubStreams - 1; i++) {
- final long size = readUint64(header);
- subStreamsInfo.unpackSizes[nextUnpackStream++] = size;
- sum += size;
+ default: {
+ // Compress up to 1.8.1 would throw an exception, now we ignore it (see COMPRESS-287
+ skipBytesFully(header, size);
+ break;
}
}
- if (sum > folder.getUnpackSize()) {
- throw new IOException("sum of unpack sizes of folder exceeds total unpack size");
- }
- subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum;
- }
- if (nid == NID.kSize) {
- nid = getUnsignedByte(header);
}
-
- int numDigests = 0;
- for (final Folder folder : archive.folders) {
- if (folder.numUnpackSubStreams != 1 || !folder.hasCrc) {
- numDigests += folder.numUnpackSubStreams;
+ int nonEmptyFileCounter = 0;
+ int emptyFileCounter = 0;
+ for (int i = 0; i < numFilesInt; i++) {
+ final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
+ if (entryAtIndex == null) {
+ continue;
}
- }
-
- if (nid == NID.kCRC) {
- final BitSet hasMissingCrc = readAllOrBits(header, numDigests);
- final long[] missingCrcs = new long[numDigests];
- for (int i = 0; i < numDigests; i++) {
- if (hasMissingCrc.get(i)) {
- missingCrcs[i] = 0xffffFFFFL & getInt(header);
+ entryAtIndex.setHasStream(isEmptyStream == null || !isEmptyStream.get(i));
+ if (entryAtIndex.hasStream()) {
+ if (archive.subStreamsInfo == null) {
+ throw new IOException("Archive contains file with streams but no subStreamsInfo");
}
- }
- int nextCrc = 0;
- int nextMissingCrc = 0;
- for (final Folder folder: archive.folders) {
- if (folder.numUnpackSubStreams == 1 && folder.hasCrc) {
- subStreamsInfo.hasCrc.set(nextCrc, true);
- subStreamsInfo.crcs[nextCrc] = folder.crc;
- ++nextCrc;
- } else {
- for (int i = 0; i < folder.numUnpackSubStreams; i++) {
- subStreamsInfo.hasCrc.set(nextCrc, hasMissingCrc.get(nextMissingCrc));
- subStreamsInfo.crcs[nextCrc] = missingCrcs[nextMissingCrc];
- ++nextCrc;
- ++nextMissingCrc;
- }
+ entryAtIndex.setDirectory(false);
+ entryAtIndex.setAntiItem(false);
+ entryAtIndex.setHasCrc(archive.subStreamsInfo.hasCrc.get(nonEmptyFileCounter));
+ entryAtIndex.setCrcValue(archive.subStreamsInfo.crcs[nonEmptyFileCounter]);
+ entryAtIndex.setSize(archive.subStreamsInfo.unpackSizes[nonEmptyFileCounter]);
+ if (entryAtIndex.getSize() < 0) {
+ throw new IOException("broken archive, entry with negative size");
}
+ ++nonEmptyFileCounter;
+ } else {
+ entryAtIndex.setDirectory(isEmptyFile == null || !isEmptyFile.get(emptyFileCounter));
+ entryAtIndex.setAntiItem(isAnti != null && isAnti.get(emptyFileCounter));
+ entryAtIndex.setHasCrc(false);
+ entryAtIndex.setSize(0);
+ ++emptyFileCounter;
}
-
- nid = getUnsignedByte(header);
}
-
- archive.subStreamsInfo = subStreamsInfo;
+ archive.files = fileMap.values().stream().filter(Objects::nonNull).toArray(SevenZArchiveEntry[]::new);
+ calculateStreamMap(archive);
}
- private int sanityCheckFolder(final ByteBuffer header, final ArchiveStatistics stats)
- throws IOException {
-
- final int numCoders = assertFitsIntoNonNegativeInt("numCoders", readUint64(header));
- if (numCoders == 0) {
- throw new IOException("Folder without coders");
- }
- stats.numberOfCoders += numCoders;
+ private Folder readFolder(final ByteBuffer header) throws IOException {
+ final Folder folder = new Folder();
- long totalOutStreams = 0;
+ final long numCoders = readUint64(header);
+ final Coder[] coders = new Coder[(int)numCoders];
long totalInStreams = 0;
- for (int i = 0; i < numCoders; i++) {
+ long totalOutStreams = 0;
+ for (int i = 0; i < coders.length; i++) {
+ coders[i] = new Coder();
final int bits = getUnsignedByte(header);
final int idSize = bits & 0xf;
- get(header, new byte[idSize]);
-
final boolean isSimple = (bits & 0x10) == 0;
final boolean hasAttributes = (bits & 0x20) != 0;
final boolean moreAlternativeMethods = (bits & 0x80) != 0;
- if (moreAlternativeMethods) {
- throw new IOException("Alternative methods are unsupported, please report. " + // NOSONAR
- "The reference implementation doesn't support them either.");
- }
+ coders[i].decompressionMethodId = new byte[idSize];
+ get(header, coders[i].decompressionMethodId);
if (isSimple) {
- totalInStreams++;
- totalOutStreams++;
+ coders[i].numInStreams = 1;
+ coders[i].numOutStreams = 1;
} else {
- totalInStreams +=
- assertFitsIntoNonNegativeInt("numInStreams", readUint64(header));
- totalOutStreams +=
- assertFitsIntoNonNegativeInt("numOutStreams", readUint64(header));
+ coders[i].numInStreams = readUint64(header);
+ coders[i].numOutStreams = readUint64(header);
}
-
- if (hasAttributes) {
- final int propertiesSize =
- assertFitsIntoNonNegativeInt("propertiesSize", readUint64(header));
- if (skipBytesFully(header, propertiesSize) < propertiesSize) {
- throw new IOException("invalid propertiesSize in folder");
- }
- }
- }
- assertFitsIntoNonNegativeInt("totalInStreams", totalInStreams);
- assertFitsIntoNonNegativeInt("totalOutStreams", totalOutStreams);
- stats.numberOfOutStreams += totalOutStreams;
- stats.numberOfInStreams += totalInStreams;
-
- if (totalOutStreams == 0) {
- throw new IOException("Total output streams can't be 0");
- }
-
- final int numBindPairs =
- assertFitsIntoNonNegativeInt("numBindPairs", totalOutStreams - 1);
- if (totalInStreams < numBindPairs) {
- throw new IOException("Total input streams can't be less than the number of bind pairs");
- }
- final BitSet inStreamsBound = new BitSet((int) totalInStreams);
- for (int i = 0; i < numBindPairs; i++) {
- final int inIndex = assertFitsIntoNonNegativeInt("inIndex", readUint64(header));
- if (totalInStreams <= inIndex) {
- throw new IOException("inIndex is bigger than number of inStreams");
- }
- inStreamsBound.set(inIndex);
- final int outIndex = assertFitsIntoNonNegativeInt("outIndex", readUint64(header));
- if (totalOutStreams <= outIndex) {
- throw new IOException("outIndex is bigger than number of outStreams");
- }
- }
-
- final int numPackedStreams =
- assertFitsIntoNonNegativeInt("numPackedStreams", totalInStreams - numBindPairs);
-
- if (numPackedStreams == 1) {
- if (inStreamsBound.nextClearBit(0) == -1) {
- throw new IOException("Couldn't find stream's bind pair index");
- }
- } else {
- for (int i = 0; i < numPackedStreams; i++) {
- final int packedStreamIndex =
- assertFitsIntoNonNegativeInt("packedStreamIndex", readUint64(header));
- if (packedStreamIndex >= totalInStreams) {
- throw new IOException("packedStreamIndex is bigger than number of totalInStreams");
- }
- }
- }
-
- return (int) totalOutStreams;
- }
-
- private Folder readFolder(final ByteBuffer header) throws IOException {
- final Folder folder = new Folder();
-
- final long numCoders = readUint64(header);
- final Coder[] coders = new Coder[(int)numCoders];
- long totalInStreams = 0;
- long totalOutStreams = 0;
- for (int i = 0; i < coders.length; i++) {
- coders[i] = new Coder();
- final int bits = getUnsignedByte(header);
- final int idSize = bits & 0xf;
- final boolean isSimple = (bits & 0x10) == 0;
- final boolean hasAttributes = (bits & 0x20) != 0;
- final boolean moreAlternativeMethods = (bits & 0x80) != 0;
-
- coders[i].decompressionMethodId = new byte[idSize];
- get(header, coders[i].decompressionMethodId);
- if (isSimple) {
- coders[i].numInStreams = 1;
- coders[i].numOutStreams = 1;
- } else {
- coders[i].numInStreams = readUint64(header);
- coders[i].numOutStreams = readUint64(header);
- }
- totalInStreams += coders[i].numInStreams;
- totalOutStreams += coders[i].numOutStreams;
+ totalInStreams += coders[i].numInStreams;
+ totalOutStreams += coders[i].numOutStreams;
if (hasAttributes) {
final long propertiesSize = readUint64(header);
coders[i].properties = new byte[(int)propertiesSize];
@@ -1215,78 +1294,389 @@ public class SevenZFile implements Closeable {
return folder;
}
- private BitSet readAllOrBits(final ByteBuffer header, final int size) throws IOException {
- final int areAllDefined = getUnsignedByte(header);
- final BitSet bits;
- if (areAllDefined != 0) {
- bits = new BitSet(size);
- for (int i = 0; i < size; i++) {
- bits.set(i, true);
+ private void readFully(final ByteBuffer buf) throws IOException {
+ buf.rewind();
+ IOUtils.readFully(channel, buf);
+ buf.flip();
+ }
+
+ private void readHeader(final ByteBuffer header, final Archive archive) throws IOException {
+ final int pos = header.position();
+ final ArchiveStatistics stats = sanityCheckAndCollectStatistics(header);
+ stats.assertValidity(options.getMaxMemoryLimitInKb());
+ header.position(pos);
+
+ int nid = getUnsignedByte(header);
+
+ if (nid == NID.kArchiveProperties) {
+ readArchiveProperties(header);
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kAdditionalStreamsInfo) {
+ throw new IOException("Additional streams unsupported");
+ //nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kMainStreamsInfo) {
+ readStreamsInfo(header, archive);
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kFilesInfo) {
+ readFilesInfo(header, archive);
+ nid = getUnsignedByte(header);
+ }
+ }
+
+ private Archive readHeaders(final byte[] password) throws IOException {
+ final ByteBuffer buf = ByteBuffer.allocate(12 /* signature + 2 bytes version + 4 bytes CRC */)
+ .order(ByteOrder.LITTLE_ENDIAN);
+ readFully(buf);
+ final byte[] signature = new byte[6];
+ buf.get(signature);
+ if (!Arrays.equals(signature, sevenZSignature)) {
+ throw new IOException("Bad 7z signature");
+ }
+ // 7zFormat.txt has it wrong - it's first major then minor
+ final byte archiveVersionMajor = buf.get();
+ final byte archiveVersionMinor = buf.get();
+ if (archiveVersionMajor != 0) {
+ throw new IOException(String.format("Unsupported 7z version (%d,%d)",
+ archiveVersionMajor, archiveVersionMinor));
+ }
+
+ boolean headerLooksValid = false; // See https://www.7-zip.org/recover.html - "There is no correct End Header at the end of archive"
+ final long startHeaderCrc = 0xffffFFFFL & buf.getInt();
+ if (startHeaderCrc == 0) {
+ // This is an indication of a corrupt header - peek the next 20 bytes
+ final long currentPosition = channel.position();
+ final ByteBuffer peekBuf = ByteBuffer.allocate(20);
+ readFully(peekBuf);
+ channel.position(currentPosition);
+ // Header invalid if all data is 0
+ while (peekBuf.hasRemaining()) {
+ if (peekBuf.get()!=0) {
+ headerLooksValid = true;
+ break;
+ }
}
} else {
- bits = readBits(header, size);
+ headerLooksValid = true;
}
- return bits;
+
+ if (headerLooksValid) {
+ return initializeArchive(readStartHeader(startHeaderCrc), password, true);
+ }
+ // No valid header found - probably first file of multipart archive was removed too early. Scan for end header.
+ if (options.getTryToRecoverBrokenArchives()) {
+ return tryToLocateEndHeader(password);
+ }
+ throw new IOException("archive seems to be invalid.\nYou may want to retry and enable the"
+ + " tryToRecoverBrokenArchives if the archive could be a multi volume archive that has been closed"
+ + " prematurely.");
}
- private BitSet readBits(final ByteBuffer header, final int size) throws IOException {
- final BitSet bits = new BitSet(size);
- int mask = 0;
- int cache = 0;
- for (int i = 0; i < size; i++) {
- if (mask == 0) {
- mask = 0x80;
- cache = getUnsignedByte(header);
+ private void readPackInfo(final ByteBuffer header, final Archive archive) throws IOException {
+ archive.packPos = readUint64(header);
+ final int numPackStreamsInt = (int) readUint64(header);
+ int nid = getUnsignedByte(header);
+ if (nid == NID.kSize) {
+ archive.packSizes = new long[numPackStreamsInt];
+ for (int i = 0; i < archive.packSizes.length; i++) {
+ archive.packSizes[i] = readUint64(header);
}
- bits.set(i, (cache & mask) != 0);
- mask >>>= 1;
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kCRC) {
+ archive.packCrcsDefined = readAllOrBits(header, numPackStreamsInt);
+ archive.packCrcs = new long[numPackStreamsInt];
+ for (int i = 0; i < numPackStreamsInt; i++) {
+ if (archive.packCrcsDefined.get(i)) {
+ archive.packCrcs[i] = 0xffffFFFFL & getInt(header);
+ }
+ }
+
+ nid = getUnsignedByte(header);
}
- return bits;
}
- private void sanityCheckFilesInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
- stats.numberOfEntries = assertFitsIntoNonNegativeInt("numFiles", readUint64(header));
+ private StartHeader readStartHeader(final long startHeaderCrc) throws IOException {
+ final StartHeader startHeader = new StartHeader();
+ // using Stream rather than ByteBuffer for the benefit of the
+ // built-in CRC check
+ try (DataInputStream dataInputStream = new DataInputStream(new CRC32VerifyingInputStream(
+ new BoundedSeekableByteChannelInputStream(channel, 20), 20, startHeaderCrc))) {
+ startHeader.nextHeaderOffset = Long.reverseBytes(dataInputStream.readLong());
+ if (startHeader.nextHeaderOffset < 0
+ || startHeader.nextHeaderOffset + SIGNATURE_HEADER_SIZE > channel.size()) {
+ throw new IOException("nextHeaderOffset is out of bounds");
+ }
- int emptyStreams = -1;
- while (true) {
- final int propertyType = getUnsignedByte(header);
- if (propertyType == 0) {
- break;
- }
- final long size = readUint64(header);
- switch (propertyType) {
- case NID.kEmptyStream: {
- emptyStreams = readBits(header, stats.numberOfEntries).cardinality();
- break;
- }
- case NID.kEmptyFile: {
- if (emptyStreams == -1) {
- throw new IOException("Header format error: kEmptyStream must appear before kEmptyFile");
- }
- readBits(header, emptyStreams);
- break;
- }
- case NID.kAnti: {
- if (emptyStreams == -1) {
- throw new IOException("Header format error: kEmptyStream must appear before kAnti");
- }
- readBits(header, emptyStreams);
- break;
- }
- case NID.kName: {
- final int external = getUnsignedByte(header);
- if (external != 0) {
- throw new IOException("Not implemented");
- }
- final int namesLength =
- assertFitsIntoNonNegativeInt("file names length", size - 1);
- if ((namesLength & 1) != 0) {
- throw new IOException("File names length invalid");
- }
+ startHeader.nextHeaderSize = Long.reverseBytes(dataInputStream.readLong());
+ final long nextHeaderEnd = startHeader.nextHeaderOffset + startHeader.nextHeaderSize;
+ if (nextHeaderEnd < startHeader.nextHeaderOffset
+ || nextHeaderEnd + SIGNATURE_HEADER_SIZE > channel.size()) {
+ throw new IOException("nextHeaderSize is out of bounds");
+ }
- int filesSeen = 0;
- for (int i = 0; i < namesLength; i += 2) {
- final char c = getChar(header);
+ startHeader.nextHeaderCrc = 0xffffFFFFL & Integer.reverseBytes(dataInputStream.readInt());
+
+ return startHeader;
+ }
+ }
+
+ private void readStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException {
+ int nid = getUnsignedByte(header);
+
+ if (nid == NID.kPackInfo) {
+ readPackInfo(header, archive);
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kUnpackInfo) {
+ readUnpackInfo(header, archive);
+ nid = getUnsignedByte(header);
+ } else {
+ // archive without unpack/coders info
+ archive.folders = Folder.EMPTY_FOLDER_ARRAY;
+ }
+
+ if (nid == NID.kSubStreamsInfo) {
+ readSubStreamsInfo(header, archive);
+ nid = getUnsignedByte(header);
+ }
+ }
+
+ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException {
+ for (final Folder folder : archive.folders) {
+ folder.numUnpackSubStreams = 1;
+ }
+ long unpackStreamsCount = archive.folders.length;
+
+ int nid = getUnsignedByte(header);
+ if (nid == NID.kNumUnpackStream) {
+ unpackStreamsCount = 0;
+ for (final Folder folder : archive.folders) {
+ final long numStreams = readUint64(header);
+ folder.numUnpackSubStreams = (int)numStreams;
+ unpackStreamsCount += numStreams;
+ }
+ nid = getUnsignedByte(header);
+ }
+
+ final int totalUnpackStreams = (int) unpackStreamsCount;
+ final SubStreamsInfo subStreamsInfo = new SubStreamsInfo();
+ subStreamsInfo.unpackSizes = new long[totalUnpackStreams];
+ subStreamsInfo.hasCrc = new BitSet(totalUnpackStreams);
+ subStreamsInfo.crcs = new long[totalUnpackStreams];
+
+ int nextUnpackStream = 0;
+ for (final Folder folder : archive.folders) {
+ if (folder.numUnpackSubStreams == 0) {
+ continue;
+ }
+ long sum = 0;
+ if (nid == NID.kSize) {
+ for (int i = 0; i < folder.numUnpackSubStreams - 1; i++) {
+ final long size = readUint64(header);
+ subStreamsInfo.unpackSizes[nextUnpackStream++] = size;
+ sum += size;
+ }
+ }
+ if (sum > folder.getUnpackSize()) {
+ throw new IOException("sum of unpack sizes of folder exceeds total unpack size");
+ }
+ subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum;
+ }
+ if (nid == NID.kSize) {
+ nid = getUnsignedByte(header);
+ }
+
+ int numDigests = 0;
+ for (final Folder folder : archive.folders) {
+ if (folder.numUnpackSubStreams != 1 || !folder.hasCrc) {
+ numDigests += folder.numUnpackSubStreams;
+ }
+ }
+
+ if (nid == NID.kCRC) {
+ final BitSet hasMissingCrc = readAllOrBits(header, numDigests);
+ final long[] missingCrcs = new long[numDigests];
+ for (int i = 0; i < numDigests; i++) {
+ if (hasMissingCrc.get(i)) {
+ missingCrcs[i] = 0xffffFFFFL & getInt(header);
+ }
+ }
+ int nextCrc = 0;
+ int nextMissingCrc = 0;
+ for (final Folder folder: archive.folders) {
+ if (folder.numUnpackSubStreams == 1 && folder.hasCrc) {
+ subStreamsInfo.hasCrc.set(nextCrc, true);
+ subStreamsInfo.crcs[nextCrc] = folder.crc;
+ ++nextCrc;
+ } else {
+ for (int i = 0; i < folder.numUnpackSubStreams; i++) {
+ subStreamsInfo.hasCrc.set(nextCrc, hasMissingCrc.get(nextMissingCrc));
+ subStreamsInfo.crcs[nextCrc] = missingCrcs[nextMissingCrc];
+ ++nextCrc;
+ ++nextMissingCrc;
+ }
+ }
+ }
+
+ nid = getUnsignedByte(header);
+ }
+
+ archive.subStreamsInfo = subStreamsInfo;
+ }
+
+ private void readUnpackInfo(final ByteBuffer header, final Archive archive) throws IOException {
+ int nid = getUnsignedByte(header);
+ final int numFoldersInt = (int) readUint64(header);
+ final Folder[] folders = new Folder[numFoldersInt];
+ archive.folders = folders;
+ /* final int external = */ getUnsignedByte(header);
+ for (int i = 0; i < numFoldersInt; i++) {
+ folders[i] = readFolder(header);
+ }
+
+ nid = getUnsignedByte(header);
+ for (final Folder folder : folders) {
+ assertFitsIntoNonNegativeInt("totalOutputStreams", folder.totalOutputStreams);
+ folder.unpackSizes = new long[(int)folder.totalOutputStreams];
+ for (int i = 0; i < folder.totalOutputStreams; i++) {
+ folder.unpackSizes[i] = readUint64(header);
+ }
+ }
+
+ nid = getUnsignedByte(header);
+ if (nid == NID.kCRC) {
+ final BitSet crcsDefined = readAllOrBits(header, numFoldersInt);
+ for (int i = 0; i < numFoldersInt; i++) {
+ if (crcsDefined.get(i)) {
+ folders[i].hasCrc = true;
+ folders[i].crc = 0xffffFFFFL & getInt(header);
+ } else {
+ folders[i].hasCrc = false;
+ }
+ }
+
+ nid = getUnsignedByte(header);
+ }
+ }
+
+ /**
+ * Discard any queued streams/ folder stream, and reopen the current folder input stream.
+ *
+ * @param folderIndex the index of the folder to reopen
+ * @param file the 7z entry to read
+ * @throws IOException if exceptions occur when reading the 7z file
+ */
+ private void reopenFolderInputStream(final int folderIndex, final SevenZArchiveEntry file) throws IOException {
+ deferredBlockStreams.clear();
+ if (currentFolderInputStream != null) {
+ currentFolderInputStream.close();
+ currentFolderInputStream = null;
+ }
+ final Folder folder = archive.folders[folderIndex];
+ final int firstPackStreamIndex = archive.streamMap.folderFirstPackStreamIndex[folderIndex];
+ final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos +
+ archive.streamMap.packStreamOffsets[firstPackStreamIndex];
+
+ currentFolderInputStream = buildDecoderStack(folder, folderOffset, firstPackStreamIndex, file);
+ }
+
+ private ArchiveStatistics sanityCheckAndCollectStatistics(final ByteBuffer header)
+ throws IOException {
+ final ArchiveStatistics stats = new ArchiveStatistics();
+
+ int nid = getUnsignedByte(header);
+
+ if (nid == NID.kArchiveProperties) {
+ sanityCheckArchiveProperties(header);
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kAdditionalStreamsInfo) {
+ throw new IOException("Additional streams unsupported");
+ //nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kMainStreamsInfo) {
+ sanityCheckStreamsInfo(header, stats);
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kFilesInfo) {
+ sanityCheckFilesInfo(header, stats);
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid != NID.kEnd) {
+ throw new IOException("Badly terminated header, found " + nid);
+ }
+
+ return stats;
+ }
+
+ private void sanityCheckArchiveProperties(final ByteBuffer header)
+ throws IOException {
+ int nid = getUnsignedByte(header);
+ while (nid != NID.kEnd) {
+ final int propertySize =
+ assertFitsIntoNonNegativeInt("propertySize", readUint64(header));
+ if (skipBytesFully(header, propertySize) < propertySize) {
+ throw new IOException("invalid property size");
+ }
+ nid = getUnsignedByte(header);
+ }
+ }
+
+ private void sanityCheckFilesInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
+ stats.numberOfEntries = assertFitsIntoNonNegativeInt("numFiles", readUint64(header));
+
+ int emptyStreams = -1;
+ while (true) {
+ final int propertyType = getUnsignedByte(header);
+ if (propertyType == 0) {
+ break;
+ }
+ final long size = readUint64(header);
+ switch (propertyType) {
+ case NID.kEmptyStream: {
+ emptyStreams = readBits(header, stats.numberOfEntries).cardinality();
+ break;
+ }
+ case NID.kEmptyFile: {
+ if (emptyStreams == -1) {
+ throw new IOException("Header format error: kEmptyStream must appear before kEmptyFile");
+ }
+ readBits(header, emptyStreams);
+ break;
+ }
+ case NID.kAnti: {
+ if (emptyStreams == -1) {
+ throw new IOException("Header format error: kEmptyStream must appear before kAnti");
+ }
+ readBits(header, emptyStreams);
+ break;
+ }
+ case NID.kName: {
+ final int external = getUnsignedByte(header);
+ if (external != 0) {
+ throw new IOException("Not implemented");
+ }
+ final int namesLength =
+ assertFitsIntoNonNegativeInt("file names length", size - 1);
+ if ((namesLength & 1) != 0) {
+ throw new IOException("File names length invalid");
+ }
+
+ int filesSeen = 0;
+ for (int i = 0; i < namesLength; i += 2) {
+ final char c = getChar(header);
if (c == 0) {
filesSeen++;
}
@@ -1370,767 +1760,377 @@ public class SevenZFile implements Closeable {
stats.numberOfEntriesWithStream = stats.numberOfEntries - Math.max(emptyStreams, 0);
}
- private void readFilesInfo(final ByteBuffer header, final Archive archive) throws IOException {
- final int numFilesInt = (int) readUint64(header);
- final Map<Integer, SevenZArchiveEntry> fileMap = new LinkedHashMap<>();
- BitSet isEmptyStream = null;
- BitSet isEmptyFile = null;
- BitSet isAnti = null;
- while (true) {
- final int propertyType = getUnsignedByte(header);
- if (propertyType == 0) {
- break;
- }
- final long size = readUint64(header);
- switch (propertyType) {
- case NID.kEmptyStream: {
- isEmptyStream = readBits(header, numFilesInt);
- break;
- }
- case NID.kEmptyFile: {
- isEmptyFile = readBits(header, isEmptyStream.cardinality());
- break;
- }
- case NID.kAnti: {
- isAnti = readBits(header, isEmptyStream.cardinality());
- break;
- }
- case NID.kName: {
- /* final int external = */ getUnsignedByte(header);
- final byte[] names = new byte[(int) (size - 1)];
- final int namesLength = names.length;
- get(header, names);
- int nextFile = 0;
- int nextName = 0;
- for (int i = 0; i < namesLength; i += 2) {
- if (names[i] == 0 && names[i + 1] == 0) {
- checkEntryIsInitialized(fileMap, nextFile);
- fileMap.get(nextFile).setName(new String(names, nextName, i - nextName, UTF_16LE));
- nextName = i + 2;
- nextFile++;
- }
- }
- if (nextName != namesLength || nextFile != numFilesInt) {
- throw new IOException("Error parsing file names");
- }
- break;
- }
- case NID.kCTime: {
- final BitSet timesDefined = readAllOrBits(header, numFilesInt);
- /* final int external = */ getUnsignedByte(header);
- for (int i = 0; i < numFilesInt; i++) {
- checkEntryIsInitialized(fileMap, i);
- final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
- entryAtIndex.setHasCreationDate(timesDefined.get(i));
- if (entryAtIndex.getHasCreationDate()) {
- entryAtIndex.setCreationDate(getLong(header));
- }
- }
- break;
- }
- case NID.kATime: {
- final BitSet timesDefined = readAllOrBits(header, numFilesInt);
- /* final int external = */ getUnsignedByte(header);
... 89000 lines suppressed ...