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