diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/HeaderBuffer.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/HeaderBuffer.java new file mode 100644 index 00000000000..837e627cc7b --- /dev/null +++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/HeaderBuffer.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + * + */ +package org.apache.commons.compress.archivers.sevenz; + +import java.io.IOException; +import java.util.zip.CRC32; + +/** + * Represents a buffer for a {@link SevenZFile} header. + * + * @since 1.21 + */ +interface HeaderBuffer { + void get(byte[] dst) throws IOException; + + int getInt() throws IOException; + + long getLong() throws IOException; + + int getUnsignedByte() throws IOException; + + boolean hasCRC(); + + CRC32 getCRC() throws IOException; + + long skipBytesFully(long bytesToSkip) throws IOException; +} diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/HeaderChannelBuffer.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/HeaderChannelBuffer.java new file mode 100644 index 00000000000..e29b45c5c54 --- /dev/null +++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/HeaderChannelBuffer.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.commons.compress.archivers.sevenz; + +import org.apache.commons.compress.utils.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.zip.CRC32; + +/** + * Enables little-endian primitive type reads from a {@link ReadableByteChannel} + * or {@link InputStream}, internally using a paged-in {@link ByteBuffer}. + *
+ * Access is serial only but does allow a + * virtual buffer capacity of {@code Long.MAX_VALUE}. + * If the requested capacity is within the maximum page size (default 16MiB) + * the buffer will be fully read and held in a {@link HeaderInMemoryBuffer}. + * + * @NotThreadSafe + * @since 1.21 + */ +class HeaderChannelBuffer implements HeaderBuffer { + private static final int DEFAULT_PAGE_MAX = 16_777_216; + // This must match the largest get (currently getLong) + private static final int MAX_GET_ELEMENT_SIZE = 8; + private final ReadableByteChannel channel; + private final ByteBuffer buffer; + private long remaining; + + private HeaderChannelBuffer(final ReadableByteChannel channel, final long capacity, final int maxPageBytes) { + this.channel = channel; + int limit = (int) Math.min(maxPageBytes, capacity); + this.buffer = ByteBuffer.allocate(limit).order(ByteOrder.LITTLE_ENDIAN); + this.remaining = capacity; + } + + public static HeaderBuffer create(final ReadableByteChannel channel, final long capacity, final int maxPageBytes) + throws IOException { + if (maxPageBytes < MAX_GET_ELEMENT_SIZE) { + throw new IllegalArgumentException("Page size must be at least " + MAX_GET_ELEMENT_SIZE); + } + if (capacity <= maxPageBytes) { + ByteBuffer buf = ByteBuffer.allocate((int) capacity).order(ByteOrder.LITTLE_ENDIAN); + IOUtils.readFully(channel, buf); + buf.flip(); + return new HeaderInMemoryBuffer(buf); + } + HeaderChannelBuffer channelBuffer = new HeaderChannelBuffer(channel, capacity, maxPageBytes); + channelBuffer.fill(); + return channelBuffer; + } + + public static HeaderBuffer create(final ReadableByteChannel channel, final long capacity) throws IOException { + return HeaderChannelBuffer.create(channel, capacity, DEFAULT_PAGE_MAX); + } + + public static HeaderBuffer create(final InputStream inputStream, final long capacity, final int maxPageBytes) + throws IOException { + return create(Channels.newChannel(inputStream), capacity, maxPageBytes); + } + + public static HeaderBuffer create(final InputStream inputStream, final long capacity) throws IOException { + return create(Channels.newChannel(inputStream), capacity, DEFAULT_PAGE_MAX); + } + + @Override + public boolean hasCRC() { + return false; + } + + @Override + public CRC32 getCRC() throws IOException { + throw new IOException("CRC is not implemented for this header type"); + } + + @Override + public void get(byte[] dst) throws IOException { + int remainingBytes = dst.length; + do { + int length = Math.min(buffer.remaining(), remainingBytes); + buffer.get(dst, dst.length - remainingBytes, length); + remainingBytes -= length; + } while (refilled(remainingBytes)); + } + + private boolean refilled(final int remainingBytes) throws IOException { + if (remainingBytes <= 0) { + return false; + } + if (remainingBytes > this.remaining) { + throw new BufferUnderflowException(); + } + buffer.clear(); + this.fill(); + return true; + } + + @Override + public int getInt() throws IOException { + compactAndFill(); + return buffer.getInt(); + } + + @Override + public long getLong() throws IOException { + compactAndFill(); + return buffer.getLong(); + } + + @Override + public int getUnsignedByte() throws IOException { + compactAndFill(); + return buffer.get() & 0xff; + } + + @Override + public long skipBytesFully(long bytesToSkip) throws IOException { + if (bytesToSkip <= 0) { + return 0; + } + int current = buffer.position(); + long length = buffer.remaining(); + if (bytesToSkip <= length) { + buffer.position(current + (int) bytesToSkip); + } else { + long maxSkip = remaining + length; + bytesToSkip = Math.min(bytesToSkip, maxSkip); + while (length < bytesToSkip) { + buffer.clear(); + fill(); + length += buffer.limit(); + } + buffer.position(buffer.limit() - (int) (length - bytesToSkip)); + } + return bytesToSkip; + } + + private void compactAndFill() throws IOException { + if (buffer.remaining() <= MAX_GET_ELEMENT_SIZE) { + buffer.compact(); + this.fill(); + } + } + + private void fill() throws IOException { + if (buffer.remaining() > remaining) { + buffer.limit(buffer.position() + (int) remaining); + } + remaining -= buffer.remaining(); + IOUtils.readFully(channel, buffer); + buffer.flip(); + } +} diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/HeaderInMemoryBuffer.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/HeaderInMemoryBuffer.java new file mode 100644 index 00000000000..2a9c219dc0e --- /dev/null +++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/HeaderInMemoryBuffer.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.commons.compress.archivers.sevenz; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.zip.CRC32; + +/** + * A thin and limited wrapper around a {@link ByteBuffer} with serial access only. + * + * @NotThreadSafe + * @since 1.21 + */ +class HeaderInMemoryBuffer implements HeaderBuffer { + private final ByteBuffer buffer; + + public HeaderInMemoryBuffer(ByteBuffer buf) { + this.buffer = buf; + } + + @Override + public boolean hasCRC() { + return true; + } + + @Override + public CRC32 getCRC() { + final CRC32 crc = new CRC32(); + crc.update(buffer.array()); + return crc; + } + + @Override + public void get(byte[] dst) { + buffer.get(dst); + } + + @Override + public int getInt() { + return buffer.getInt(); + } + + @Override + public long getLong() { + return buffer.getLong(); + } + + @Override + public int getUnsignedByte() { + return buffer.get() & 0xff; + } + + @Override + public long skipBytesFully(long bytesToSkip) throws IOException { + if (bytesToSkip <= 0) { + return 0; + } + int current = buffer.position(); + int maxSkip = buffer.remaining(); + if (maxSkip < bytesToSkip) { + bytesToSkip = maxSkip; + } + buffer.position(current + (int) bytesToSkip); + return bytesToSkip; + } +} 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 5de96670e40..76b59fc0736 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 @@ -516,23 +516,21 @@ private Archive initializeArchive(StartHeader startHeader, final byte[] password assertFitsIntoInt("nextHeaderSize", startHeader.nextHeaderSize); final int nextHeaderSizeInt = (int) startHeader.nextHeaderSize; channel.position(SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset); - ByteBuffer buf = ByteBuffer.allocate(nextHeaderSizeInt).order(ByteOrder.LITTLE_ENDIAN); - readFully(buf); + HeaderBuffer buf = HeaderChannelBuffer.create(channel, nextHeaderSizeInt, Integer.MAX_VALUE); if (verifyCrc) { - final CRC32 crc = new CRC32(); - crc.update(buf.array()); + final CRC32 crc = buf.getCRC(); if (startHeader.nextHeaderCrc != crc.getValue()) { throw new IOException("NextHeader CRC mismatch"); } } Archive archive = new Archive(); - int nid = getUnsignedByte(buf); + int nid = buf.getUnsignedByte(); if (nid == NID.kEncodedHeader) { buf = readEncodedHeader(buf, archive, password); // Archive gets rebuilt with the new header archive = new Archive(); - nid = getUnsignedByte(buf); + nid = buf.getUnsignedByte(); } if (nid == NID.kHeader) { readHeader(buf, archive); @@ -555,12 +553,12 @@ private StartHeader readStartHeader(final long startHeaderCrc) throws IOExceptio } } - private void readHeader(final ByteBuffer header, final Archive archive) throws IOException { - int nid = getUnsignedByte(header); + private void readHeader(final HeaderBuffer header, final Archive archive) throws IOException { + int nid = header.getUnsignedByte(); if (nid == NID.kArchiveProperties) { readArchiveProperties(header); - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); } if (nid == NID.kAdditionalStreamsInfo) { @@ -570,12 +568,12 @@ private void readHeader(final ByteBuffer header, final Archive archive) throws I if (nid == NID.kMainStreamsInfo) { readStreamsInfo(header, archive); - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); } if (nid == NID.kFilesInfo) { readFilesInfo(header, archive); - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); } if (nid != NID.kEnd) { @@ -583,20 +581,20 @@ private void readHeader(final ByteBuffer header, final Archive archive) throws I } } - private void readArchiveProperties(final ByteBuffer input) throws IOException { + private void readArchiveProperties(final HeaderBuffer input) throws IOException { // FIXME: the reference implementation just throws them away? - int nid = getUnsignedByte(input); + int nid = input.getUnsignedByte(); while (nid != NID.kEnd) { final long propertySize = readUint64(input); assertFitsIntoInt("propertySize", propertySize); final byte[] property = new byte[(int)propertySize]; input.get(property); - nid = getUnsignedByte(input); + nid = input.getUnsignedByte(); } } - private ByteBuffer readEncodedHeader(final ByteBuffer header, final Archive archive, - final byte[] password) throws IOException { + private HeaderBuffer readEncodedHeader(final HeaderBuffer header, final Archive archive, + final byte[] password) throws IOException { readStreamsInfo(header, archive); // FIXME: merge with buildDecodingStream()/buildDecoderStack() at some stage? @@ -619,25 +617,20 @@ private ByteBuffer readEncodedHeader(final ByteBuffer header, final Archive arch inputStreamStack = new CRC32VerifyingInputStream(inputStreamStack, folder.getUnpackSize(), folder.crc); } - assertFitsIntoInt("unpackSize", folder.getUnpackSize()); - final byte[] nextHeader = new byte[(int)folder.getUnpackSize()]; - try (DataInputStream nextHeaderInputStream = new DataInputStream(inputStreamStack)) { - nextHeaderInputStream.readFully(nextHeader); - } - return ByteBuffer.wrap(nextHeader).order(ByteOrder.LITTLE_ENDIAN); + return HeaderChannelBuffer.create(inputStreamStack, folder.getUnpackSize()); } - private void readStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException { - int nid = getUnsignedByte(header); + private void readStreamsInfo(final HeaderBuffer header, final Archive archive) throws IOException { + int nid = header.getUnsignedByte(); if (nid == NID.kPackInfo) { readPackInfo(header, archive); - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); } if (nid == NID.kUnpackInfo) { readUnpackInfo(header, archive); - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); } else { // archive without unpack/coders info archive.folders = new Folder[0]; @@ -645,7 +638,7 @@ private void readStreamsInfo(final ByteBuffer header, final Archive archive) thr if (nid == NID.kSubStreamsInfo) { readSubStreamsInfo(header, archive); - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); } if (nid != NID.kEnd) { @@ -653,18 +646,18 @@ private void readStreamsInfo(final ByteBuffer header, final Archive archive) thr } } - private void readPackInfo(final ByteBuffer header, final Archive archive) throws IOException { + private void readPackInfo(final HeaderBuffer header, final Archive archive) throws IOException { archive.packPos = readUint64(header); final long numPackStreams = readUint64(header); assertFitsIntoInt("numPackStreams", numPackStreams); final int numPackStreamsInt = (int) numPackStreams; - int nid = getUnsignedByte(header); + int nid = header.getUnsignedByte(); 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); + nid = header.getUnsignedByte(); } if (nid == NID.kCRC) { @@ -676,7 +669,7 @@ private void readPackInfo(final ByteBuffer header, final Archive archive) throws } } - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); } if (nid != NID.kEnd) { @@ -684,8 +677,8 @@ private void readPackInfo(final ByteBuffer header, final Archive archive) throws } } - private void readUnpackInfo(final ByteBuffer header, final Archive archive) throws IOException { - int nid = getUnsignedByte(header); + private void readUnpackInfo(final HeaderBuffer header, final Archive archive) throws IOException { + int nid = header.getUnsignedByte(); if (nid != NID.kFolder) { throw new IOException("Expected kFolder, got " + nid); } @@ -694,7 +687,7 @@ private void readUnpackInfo(final ByteBuffer header, final Archive archive) thro final int numFoldersInt = (int) numFolders; final Folder[] folders = new Folder[numFoldersInt]; archive.folders = folders; - final int external = getUnsignedByte(header); + final int external = header.getUnsignedByte(); if (external != 0) { throw new IOException("External unsupported"); } @@ -702,7 +695,7 @@ private void readUnpackInfo(final ByteBuffer header, final Archive archive) thro folders[i] = readFolder(header); } - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); if (nid != NID.kCodersUnpackSize) { throw new IOException("Expected kCodersUnpackSize, got " + nid); } @@ -714,7 +707,7 @@ private void readUnpackInfo(final ByteBuffer header, final Archive archive) thro } } - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); if (nid == NID.kCRC) { final BitSet crcsDefined = readAllOrBits(header, numFoldersInt); for (int i = 0; i < numFoldersInt; i++) { @@ -726,7 +719,7 @@ private void readUnpackInfo(final ByteBuffer header, final Archive archive) thro } } - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); } if (nid != NID.kEnd) { @@ -734,13 +727,13 @@ private void readUnpackInfo(final ByteBuffer header, final Archive archive) thro } } - private void readSubStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException { + private void readSubStreamsInfo(final HeaderBuffer header, final Archive archive) throws IOException { for (final Folder folder : archive.folders) { folder.numUnpackSubStreams = 1; } int totalUnpackStreams = archive.folders.length; - int nid = getUnsignedByte(header); + int nid = header.getUnsignedByte(); if (nid == NID.kNumUnpackStream) { totalUnpackStreams = 0; for (final Folder folder : archive.folders) { @@ -749,7 +742,7 @@ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive) folder.numUnpackSubStreams = (int)numStreams; totalUnpackStreams += numStreams; } - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); } final SubStreamsInfo subStreamsInfo = new SubStreamsInfo(); @@ -773,7 +766,7 @@ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive) subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum; } if (nid == NID.kSize) { - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); } int numDigests = 0; @@ -808,7 +801,7 @@ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive) } } - nid = getUnsignedByte(header); + nid = header.getUnsignedByte(); } if (nid != NID.kEnd) { @@ -818,7 +811,7 @@ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive) archive.subStreamsInfo = subStreamsInfo; } - private Folder readFolder(final ByteBuffer header) throws IOException { + private Folder readFolder(final HeaderBuffer header) throws IOException { final Folder folder = new Folder(); final long numCoders = readUint64(header); @@ -828,7 +821,7 @@ private Folder readFolder(final ByteBuffer header) throws IOException { long totalOutStreams = 0; for (int i = 0; i < coders.length; i++) { coders[i] = new Coder(); - final int bits = getUnsignedByte(header); + final int bits = header.getUnsignedByte(); final int idSize = bits & 0xf; final boolean isSimple = (bits & 0x10) == 0; final boolean hasAttributes = (bits & 0x20) != 0; @@ -903,8 +896,8 @@ private Folder readFolder(final ByteBuffer header) throws IOException { return folder; } - private BitSet readAllOrBits(final ByteBuffer header, final int size) throws IOException { - final int areAllDefined = getUnsignedByte(header); + private BitSet readAllOrBits(final HeaderBuffer header, final int size) throws IOException { + final int areAllDefined = header.getUnsignedByte(); final BitSet bits; if (areAllDefined != 0) { bits = new BitSet(size); @@ -917,14 +910,14 @@ private BitSet readAllOrBits(final ByteBuffer header, final int size) throws IOE return bits; } - private BitSet readBits(final ByteBuffer header, final int size) throws IOException { + private BitSet readBits(final HeaderBuffer 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); + cache = header.getUnsignedByte(); } bits.set(i, (cache & mask) != 0); mask >>>= 1; @@ -932,7 +925,7 @@ private BitSet readBits(final ByteBuffer header, final int size) throws IOExcept return bits; } - private void readFilesInfo(final ByteBuffer header, final Archive archive) throws IOException { + private void readFilesInfo(final HeaderBuffer header, final Archive archive) throws IOException { final long numFiles = readUint64(header); assertFitsIntoInt("numFiles", numFiles); final SevenZArchiveEntry[] files = new SevenZArchiveEntry[(int)numFiles]; @@ -943,7 +936,7 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw BitSet isEmptyFile = null; BitSet isAnti = null; while (true) { - final int propertyType = getUnsignedByte(header); + final int propertyType = header.getUnsignedByte(); if (propertyType == 0) { break; } @@ -968,7 +961,7 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw break; } case NID.kName: { - final int external = getUnsignedByte(header); + final int external = header.getUnsignedByte(); if (external != 0) { throw new IOException("Not implemented"); } @@ -993,7 +986,7 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw } case NID.kCTime: { final BitSet timesDefined = readAllOrBits(header, files.length); - final int external = getUnsignedByte(header); + final int external = header.getUnsignedByte(); if (external != 0) { throw new IOException("Unimplemented"); } @@ -1007,7 +1000,7 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw } case NID.kATime: { final BitSet timesDefined = readAllOrBits(header, files.length); - final int external = getUnsignedByte(header); + final int external = header.getUnsignedByte(); if (external != 0) { throw new IOException("Unimplemented"); } @@ -1021,7 +1014,7 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw } case NID.kMTime: { final BitSet timesDefined = readAllOrBits(header, files.length); - final int external = getUnsignedByte(header); + final int external = header.getUnsignedByte(); if (external != 0) { throw new IOException("Unimplemented"); } @@ -1035,7 +1028,7 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw } case NID.kWinAttributes: { final BitSet attributesDefined = readAllOrBits(header, files.length); - final int external = getUnsignedByte(header); + final int external = header.getUnsignedByte(); if (external != 0) { throw new IOException("Unimplemented"); } @@ -1054,7 +1047,7 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw // 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 - if (skipBytesFully(header, size) < size) { + if (header.skipBytesFully(size) < size) { throw new IOException("Incomplete kDummy property"); } break; @@ -1062,7 +1055,7 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw default: { // Compress up to 1.8.1 would throw an exception, now we ignore it (see COMPRESS-287 - if (skipBytesFully(header, size) < size) { + if (header.skipBytesFully(size) < size) { throw new IOException("Incomplete property of type " + propertyType); } break; @@ -1501,26 +1494,22 @@ public long getUncompressedCount() { }; } - private static long readUint64(final ByteBuffer in) throws IOException { + private static long readUint64(final HeaderBuffer in) throws IOException { // long rather than int as it might get shifted beyond the range of an int - final long firstByte = getUnsignedByte(in); + final long firstByte = in.getUnsignedByte(); 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); + final long nextByte = in.getUnsignedByte(); value |= nextByte << (8 * i); mask >>>= 1; } return value; } - private static int getUnsignedByte(ByteBuffer buf) { - return buf.get() & 0xff; - } - /** * Checks if the signature matches what is expected for a 7z file. * @@ -1544,19 +1533,6 @@ public static boolean matches(final byte[] signature, final int length) { return true; } - private static long skipBytesFully(final ByteBuffer input, long bytesToSkip) throws IOException { - if (bytesToSkip < 1) { - return 0; - } - int current = input.position(); - int maxSkip = input.remaining(); - if (maxSkip < bytesToSkip) { - bytesToSkip = maxSkip; - } - input.position(current + (int) bytesToSkip); - return bytesToSkip; - } - private void readFully(ByteBuffer buf) throws IOException { buf.rewind(); IOUtils.readFully(channel, buf); diff --git a/src/test/java/org/apache/commons/compress/archivers/sevenz/HeaderChannelBufferTest.java b/src/test/java/org/apache/commons/compress/archivers/sevenz/HeaderChannelBufferTest.java new file mode 100644 index 00000000000..1780582d49b --- /dev/null +++ b/src/test/java/org/apache/commons/compress/archivers/sevenz/HeaderChannelBufferTest.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.commons.compress.archivers.sevenz; + +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.BufferUnderflowException; +import java.nio.channels.ReadableByteChannel; +import java.util.Random; + +import static org.junit.Assert.*; + +public class HeaderChannelBufferTest { + @Test + public void testPagedArrayTransfer() throws Exception { + byte[] data = new byte[1_000_000]; + byte[] output = new byte[1_000_000]; + new Random(0xabadfeed).nextBytes(data); + SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(data); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(channel, data.length, 113); + headerBuffer.get(output); + assertArrayEquals(data, output); + } + + @Test + public void testNoMaxPageSize() throws Exception { + SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(new byte[50]); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(channel, 50); + assertTrue(headerBuffer.hasCRC()); + } + + @Test + public void testGetElementsFromPagedChannel() throws Exception { + byte[] data = {(byte) 255, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8}; + SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(data); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(channel, data.length, 17); + assertEquals(255, headerBuffer.getUnsignedByte()); + assertEquals(0x04030201, headerBuffer.getInt()); + assertEquals(0x0403020104030201L, headerBuffer.getLong()); + assertEquals(0x0807060504030201L, headerBuffer.getLong()); + } + + @Test + public void testGetElementsFromPagedStream() throws Exception { + byte[] data = {1, 2, 3, 4, (byte) 255, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8}; + InputStream inputStream = new ByteArrayInputStream(data); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(inputStream, data.length, 11); + assertEquals(0x04030201, headerBuffer.getInt()); + assertEquals(255, headerBuffer.getUnsignedByte()); + assertEquals(0x0403020104030201L, headerBuffer.getLong()); + assertEquals(0x0807060504030201L, headerBuffer.getLong()); + } + + @Test + public void testSkipWithinPage() throws Exception { + byte[] data = {(byte) 255, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8}; + SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(data); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(channel, data.length, 11); + long skipped = headerBuffer.skipBytesFully(7); + assertEquals(7, skipped); + assertEquals(0x0201040302010403L, headerBuffer.getLong()); + } + + @Test + public void testSkipOutsidePage() throws Exception { + byte[] data = {(byte) 255, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8}; + SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(data); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(channel, data.length, 11); + long skipped = headerBuffer.skipBytesFully(13); + assertEquals(13, skipped); + assertEquals(0x0807060504030201L, headerBuffer.getLong()); + } + + @Test + public void testSkipOutsideCapacity() throws Exception { + byte[] data = {(byte) 255, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8}; + SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(data); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(channel, data.length, 11); + long skipped = headerBuffer.skipBytesFully(100); + assertEquals(21, skipped); + } + + @Test + public void testSkipNothing() throws Exception { + SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(new byte[50]); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(channel, 50, 25); + headerBuffer.get(new byte[50]); + assertEquals(0, headerBuffer.skipBytesFully(20)); + } + + @Test + public void testSkipNegative() throws Exception { + SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(new byte[50]); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(channel, 50, 25); + assertEquals(0, headerBuffer.skipBytesFully(-1)); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidPageBytes() throws IOException { + HeaderChannelBuffer.create((ReadableByteChannel) null, 1000, 7); + } + + @Test(expected = EOFException.class) + public void testCapacityTooLarge() throws Exception { + InputStream inputStream = new ByteArrayInputStream(new byte[20]); + HeaderChannelBuffer.create(inputStream, 100, 100); + } + + @Test(expected = EOFException.class) + public void testCapacityTooLargeSmallPage() throws Exception { + InputStream inputStream = new ByteArrayInputStream(new byte[20]); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(inputStream, 100, 10); + headerBuffer.get(new byte[100]); + } + + @Test(expected = BufferUnderflowException.class) + public void testTransferTooMuch() throws Exception { + SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(new byte[100]); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(channel, 50, 20); + headerBuffer.get(new byte[100]); + } + + @Test(expected = BufferUnderflowException.class) + public void testGetTooMuch() throws Exception { + SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(new byte[100]); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(channel, 50, 20); + headerBuffer.get(new byte[50]); + headerBuffer.getInt(); + } + + @Test + public void testHasCrcInMemory() throws IOException { + byte[] data = {1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8}; + InputStream inputStream = new ByteArrayInputStream(data); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(inputStream, data.length); + assertTrue(headerBuffer.hasCRC()); + assertEquals(0x542F8E62, headerBuffer.getCRC().getValue()); + } + + @Test + public void testNoCrcInPaged() throws IOException { + InputStream inputStream = new ByteArrayInputStream(new byte[50]); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(inputStream, 50, 20); + assertFalse(headerBuffer.hasCRC()); + } + + @Test(expected = IOException.class) + public void testGetCrcInPaged() throws IOException { + InputStream inputStream = new ByteArrayInputStream(new byte[50]); + HeaderBuffer headerBuffer = HeaderChannelBuffer.create(inputStream, 50, 20); + headerBuffer.getCRC(); + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/commons/compress/archivers/sevenz/HeaderInMemoryBufferTest.java b/src/test/java/org/apache/commons/compress/archivers/sevenz/HeaderInMemoryBufferTest.java new file mode 100644 index 00000000000..0534d0fb5f1 --- /dev/null +++ b/src/test/java/org/apache/commons/compress/archivers/sevenz/HeaderInMemoryBufferTest.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.commons.compress.archivers.sevenz; + +import org.junit.Test; + +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Random; + +import static org.junit.Assert.*; + +public class HeaderInMemoryBufferTest { + @Test + public void testArrayTransfer() throws Exception { + byte[] data = new byte[1_000_000]; + byte[] output = new byte[1_000_000]; + new Random(0xabadfeed).nextBytes(data); + ByteBuffer buffer = ByteBuffer.wrap(data); + HeaderBuffer headerBuffer = new HeaderInMemoryBuffer(buffer); + headerBuffer.get(output); + assertArrayEquals(data, output); + } + + @Test + public void testGetElements() throws Exception { + byte[] data = {(byte) 255, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8}; + ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); + HeaderBuffer headerBuffer = new HeaderInMemoryBuffer(buffer); + assertEquals(255, headerBuffer.getUnsignedByte()); + assertEquals(0x04030201, headerBuffer.getInt()); + assertEquals(0x0403020104030201L, headerBuffer.getLong()); + assertEquals(0x0807060504030201L, headerBuffer.getLong()); + } + + @Test + public void testSkipWithin() throws Exception { + byte[] data = {(byte) 255, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8}; + ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); + HeaderBuffer headerBuffer = new HeaderInMemoryBuffer(buffer); + long skipped = headerBuffer.skipBytesFully(7); + assertEquals(7, skipped); + assertEquals(0x0201040302010403L, headerBuffer.getLong()); + } + + @Test + public void testSkipOutside() throws Exception { + byte[] data = {(byte) 255, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8}; + ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); + HeaderBuffer headerBuffer = new HeaderInMemoryBuffer(buffer); + long skipped = headerBuffer.skipBytesFully(100); + assertEquals(21, skipped); + } + + @Test + public void testSkipExact() throws Exception { + byte[] data = {(byte) 255, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8}; + ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); + HeaderBuffer headerBuffer = new HeaderInMemoryBuffer(buffer); + long skipped = headerBuffer.skipBytesFully(21); + assertEquals(21, skipped); + } + + @Test + public void testSkipNegative() throws Exception { + HeaderBuffer headerBuffer = new HeaderInMemoryBuffer(ByteBuffer.allocate(20)); + assertEquals(0, headerBuffer.skipBytesFully(-1)); + } + + @Test + public void testCrc() throws IOException { + byte[] data = {1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8}; + ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); + HeaderBuffer headerBuffer = new HeaderInMemoryBuffer(buffer); + assertTrue(headerBuffer.hasCRC()); + assertEquals(0x542F8E62, headerBuffer.getCRC().getValue()); + } + + @Test(expected = BufferUnderflowException.class) + public void testTransferTooMuch() throws Exception { + ByteBuffer buffer = ByteBuffer.allocate(20); + HeaderBuffer headerBuffer = new HeaderInMemoryBuffer(buffer); + headerBuffer.get(new byte[100]); + } + + @Test(expected = BufferUnderflowException.class) + public void testGetTooMuch() throws Exception { + ByteBuffer buffer = ByteBuffer.allocate(20); + HeaderBuffer headerBuffer = new HeaderInMemoryBuffer(buffer); + headerBuffer.get(new byte[20]); + headerBuffer.getInt(); + } +} \ No newline at end of file