diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 7578b4da036..4cbe168c3e8 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -25,6 +25,6 @@ Before you push a pull request, review this list:
- [ ] Read the [ASF Generative Tooling Guidance](https://www.apache.org/legal/generative-tooling.html) if you use Artificial Intelligence (AI).
- [ ] I used AI to create any part of, or all of, this pull request.
- [ ] Run a successful build using the default [Maven](https://maven.apache.org/) goal with `mvn`; that's `mvn` on the command line by itself.
-- [ ] Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible, but it is a best-practice.
+- [ ] Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible, but it is a best practice.
- [ ] Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
- [ ] Each commit in the pull request should have a meaningful subject line and body. Note that a maintainer may squash commits during the merge process.
diff --git a/pom.xml b/pom.xml
index 3c13af651db..133427c835f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -203,7 +203,7 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
commons-io
commons-io
- 2.20.0
+ 2.21.0
org.apache.commons
@@ -289,21 +289,6 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
maven-bundle-plugin
${commons.felix.version}
-
- com.github.siom79.japicmp
- japicmp-maven-plugin
-
-
-
-
- org.apache.commons.compress.harmony.pack200.Segment
- org.apache.commons.compress.harmony.pack200.SegmentMethodVisitor
- org.apache.commons.compress.harmony.pack200.SegmentAnnotationVisitor
- org.apache.commons.compress.harmony.pack200.SegmentFieldVisitor
-
-
-
-
org.apache.maven.plugins
maven-checkstyle-plugin
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index c20c82ff4ba..b36f0952285 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -56,14 +56,16 @@ The type attribute can be add,update,fix,remove.
AES256SHA256Decoder now enforces the CPP source k_NumCyclesPower_Supported_MAX = 24 limit.
Don't loose precision while reading folders from a SevenZFile.
Improve some exception messages in TarUtils and TarArchiveEntry.
-
+ SevenZFile now enforces the same folder and coder limits as the CPP implementation.
+ Refactor unsigned number parsing and header validation in SevenZFile.
+
BZip2CompressorInputStream now throw CompressorException (a subclass of IOException) for invalid or corrupted data, providing more specific error reporting.
BZip2 input streams treat Huffman codes longer than 20 bits as corrupted data, matching the behavior of the reference implementation.
Align DUMP archive block size with Linux `dump` utility.
DumpArchiveInputStream.getNextEntry() throws an ArchiveException instead of ArrayIndexOutOfBoundsException.
Fix DumpArchiveInputStream to correctly handle file names up to 255 bytes #711.
-
+
ZipArchiveInputStream.read(byte[], int, int) now throws an IOException instead of a NullPointerException.
ZipFile.createBoundedInputStream(long, long) now throws an ArchiveException instead of IllegalArgumentException.
ZipFile.getContentBeforeFirstLocalFileHeader() now throws an ArchiveException instead of IllegalArgumentException.
@@ -71,7 +73,7 @@ The type attribute can be add,update,fix,remove.
ZipArchiveInputStream.read() now throws an IOException instead of java.lang.ArrayIndexOutOfBoundsException.
ZipArchiveInputStream now throws an MemoryLimitException instead of ArchiveException, or OutOfMemoryError when running with low memory settings set on the command line.
ZstdCompressorInputStream closes the InputStream held by ZipArchiveInputStream garbage collection.
-
+
>Uniform handling of special tar records in TarFile and TarArchiveInputStream.
TarArchiveOutputStream now throws a IllegalArgumentException instead of an OutOfMemoryError.
TarUtils.verifyCheckSum() throws an Exception when checksum could not be parsed.
@@ -79,16 +81,21 @@ The type attribute can be add,update,fix,remove.
ArArchiveInputStream.readGNUStringTable(byte[], int, int) now provides a better exception message, wrapping the underlying exception.
ArArchiveInputStream.read(byte[], int, int) now throws ArchiveException instead of ArithmeticException.
Simplify handling of special AR records in ArArchiveInputStream.
-
+
+ Correct byte accounting and truncation errors in ARJ input stream.
+ Add strict header validation in ARJ input stream and `selfExtracting` option.
+
org.apache.commons.compress.harmony.unpack200 now throws Pack200Exception, IllegalArgumentException, and IllegalStateException instead of other runtime exceptions and Error.
org.apache.commons.compress.harmony.pack200 now throws Pack200Exception, IllegalArgumentException, IllegalStateException, instead of other runtime exceptions and Error.
Extract duplicate code in org.apache.commons.compress.harmony.pack200.IntList.
-
+ Simplify `PackingUtils` by leveraging Commons IO.
+
CpioArchiveEntry now throws ArchiveException instead of Arithmetic exception.
CpioArchiveInputStream.getNextEntry() now throws a MemoryLimitException instead of OutOfMemoryError when it can't process input greater than available memory.
CpioArchiveInputStream.readOldAsciiEntry(boolean) now throws ArchiveException instead of Arithmetic exception.
CpioArchiveInputStream.readOldBinaryEntry(boolean) now throws ArchiveException instead of Arithmetic exception.
+ Fix checksum calculation in CpioArchiveInputStream when reading with a non-zero offset.
GzipParameters.setOperatingSystem(int) now throws CompressorException on illegal input.
GZip IOException: Extra subfield length exceeds remaining bytes in extra field; use new option GzipCompressorInputStream.Builder.setIgnoreExtraField(boolean).
@@ -104,6 +111,8 @@ The type attribute can be add,update,fix,remove.
SeekableInMemoryByteChannel.SeekableInMemoryByteChannel() internal buffer now defaults to IOUtils.DEFAULT_BUFFER_SIZE.
SeekableInMemoryByteChannel.position(), size(), and truncate() now comply with the SeekableByteChannel contract and throw ClosedChannelException.
SeekableInMemoryByteChannel.position(long) now throws IllegalArgumentException instead of IOException when the position request is negative, complying with the SeekableByteChannel.position(long) contract.
+ Makes TarUtils final and cleans up protected methods #712.
+ All archive input stream constructors now throw IOException.
Add MemoryLimitException.MemoryLimitException(long, long).
Add CompressException.CompressException(String, Object...).
@@ -121,15 +130,19 @@ The type attribute can be add,update,fix,remove.
Add Pack200Exception.addExact(int, long).
Add GzipCompressorInputStream.Builder.setIgnoreExtraField(boolean).
Add SnappyCompressorInputStream.getUncompressedSize() and deprecate getSize().
- Add ArchiveInputStream.ArchiveInputStream(InputStream, Charset) as a public constructor, it was private.
Introduce builders for all ArchiveInputStream implementations and deprecate some constructors.
TarFile now implements IOIterable<TarArchiveEntry>.
Add a builder for the TarFile class and deprecate some constructors.
+ SevenZFile, TarFile, and ZipFile now always close underlying resources when builder or constructor fails.
+ Introduce an ArchiveFile abstraction to unify the APIs of SevenZFile, TarFile, and ZipFile.
+ Add a configurable maxEntryNameLength option to all archivers.
Bump org.apache.commons:commons-parent from 85 to 89 #707.
Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0.
Bump com.github.luben:zstd-jni from 1.5.7-4 to 1.5.7-6 #717, #740.
- Bump com.github.marschall:memoryfilesystem from 2.8.1 to 2.8.2 #727.
+ Bump com.github.marschall:memoryfilesystem from 2.8.1 to 2.8.2 #727.
+
+ Deprecate IOUtils.readFully and IOUtils.skip.
diff --git a/src/main/java/org/apache/commons/compress/archivers/AbstractArchiveBuilder.java b/src/main/java/org/apache/commons/compress/archivers/AbstractArchiveBuilder.java
new file mode 100644
index 00000000000..f5b122a3b66
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/AbstractArchiveBuilder.java
@@ -0,0 +1,80 @@
+/*
+ * 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
+ *
+ * https://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;
+
+import org.apache.commons.io.build.AbstractStreamBuilder;
+
+/**
+ * Base class for builder pattern implementations of all archive readers.
+ *
+ * Ensures that all {@code ArchiveInputStream} implementations and other
+ * archive handlers expose a consistent set of configuration options.
+ *
+ * @param The type of archive stream or file to build.
+ * @param The type of the concrete builder subclass.
+ * @since 1.29.0
+ */
+public abstract class AbstractArchiveBuilder>
+ extends AbstractStreamBuilder {
+
+ private int maxEntryNameLength = Short.MAX_VALUE;
+
+ /**
+ * Constructs a new instance.
+ */
+ protected AbstractArchiveBuilder() {
+ // empty
+ }
+
+ /**
+ * Gets the maximum length of an archive entry name.
+ *
+ * @return The maximum length of an archive entry name.
+ */
+ public int getMaxEntryNameLength() {
+ return maxEntryNameLength;
+ }
+
+ /**
+ * Sets the maximum length, in bytes, of an archive entry name.
+ *
+ * Most operating systems and file systems impose relatively small limits on
+ * file name or path length, which are sufficient for everyday use. By contrast,
+ * many archive formats permit much longer names: for example, TAR can encode
+ * names of several gigabytes, while ZIP allows up to 64 KiB.
+ *
+ * This setting applies an upper bound on entry name length after encoding
+ * with the {@link #setCharset configured charset}. If an entry name exceeds this
+ * limit, an {@link ArchiveException} will be thrown during reading.
+ *
+ * The default is {@link Short#MAX_VALUE}, which already exceeds the limits
+ * of most operating systems.
+ *
+ * @param maxEntryNameLength The maximum entry name length in bytes; must be positive
+ * @return {@code this} instance.
+ * @throws IllegalArgumentException If {@code maxEntryNameLength} is not positive.
+ */
+ public B setMaxEntryNameLength(final int maxEntryNameLength) {
+ if (maxEntryNameLength <= 0) {
+ throw new IllegalArgumentException("maxEntryNameLength must be positive");
+ }
+ this.maxEntryNameLength = maxEntryNameLength;
+ return asThis();
+ }
+}
diff --git a/src/main/java/org/apache/commons/compress/archivers/ArchiveException.java b/src/main/java/org/apache/commons/compress/archivers/ArchiveException.java
index 505183077db..a2c4c0a31c4 100644
--- a/src/main/java/org/apache/commons/compress/archivers/ArchiveException.java
+++ b/src/main/java/org/apache/commons/compress/archivers/ArchiveException.java
@@ -33,24 +33,6 @@ public class ArchiveException extends CompressException {
/** Serial. */
private static final long serialVersionUID = 2772690708123267100L;
- /**
- * Delegates to {@link Math#addExact(int, int)} wrapping its {@link ArithmeticException} in our {@link ArchiveException}.
- *
- * @param x the first value.
- * @param y the second value.
- * @return the result.
- * @throws ArchiveException if the result overflows an {@code int}.
- * @see Math#addExact(int, int)
- * @since 1.29.0
- */
- public static int addExact(final int x, final int y) throws ArchiveException {
- try {
- return Math.addExact(x, y);
- } catch (final ArithmeticException e) {
- throw new ArchiveException(e);
- }
- }
-
/**
* Delegates to {@link Math#addExact(int, int)} wrapping its {@link ArithmeticException} in our {@link ArchiveException}.
*
diff --git a/src/main/java/org/apache/commons/compress/archivers/ArchiveFile.java b/src/main/java/org/apache/commons/compress/archivers/ArchiveFile.java
new file mode 100644
index 00000000000..8fcf10387e8
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/ArchiveFile.java
@@ -0,0 +1,101 @@
+/*
+ * 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
+ *
+ * https://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;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.io.function.IOIterable;
+import org.apache.commons.io.function.IOIterator;
+import org.apache.commons.io.function.IOStream;
+
+/**
+ * A file-based representation of an archive containing multiple {@link ArchiveEntry entries}.
+ *
+ *
+ * This interface provides an abstraction over archive files, similar to {@link ZipFile}, but generalized for a variety of archive formats.
+ *
+ *
+ *
+ * Implementations are {@link Closeable} and should be closed once they are no longer needed in order to release any underlying system resources.
+ *
+ *
+ * @param the type of {@link ArchiveEntry} produced by this archive.
+ * @since 1.29.0
+ */
+public interface ArchiveFile extends Closeable, IOIterable {
+
+ /**
+ * Gets all entries contained in the archive as a list.
+ *
+ *
+ * The order of entries is format-dependent but guaranteed to be consistent across multiple invocations on the same archive.
+ *
+ *
+ * @return An immutable list of all entries in this archive.
+ */
+ default List extends T> entries() {
+ try (IOStream extends T> stream = stream()) {
+ return stream.collect(Collectors.toList());
+ }
+ }
+
+ /**
+ * Gets a new input stream for the specified entry's contents.
+ *
+ *
+ * The caller is responsible for closing the returned stream after use.
+ *
+ *
+ * @param entry The archive entry to read.
+ * @return An input stream providing the contents of the given entry.
+ * @throws IOException If an I/O error occurs while opening the entry stream.
+ */
+ InputStream getInputStream(T entry) throws IOException;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ default IOIterator iterator() {
+ return IOIterator.adapt((Iterable) entries());
+ }
+
+ /**
+ * Gets a sequential stream of archive entries.
+ *
+ *
+ * The order of entries is format-dependent but stable for a given archive.
+ *
+ *
+ * The returned stream must be closed after use to free associated resources.
+ *
+ *
+ * @return A stream of entries in this archive.
+ */
+ IOStream extends T> stream();
+
+ @Override
+ default Iterable unwrap() {
+ return asIterable();
+ }
+}
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 73c4dbefd3a..c7a832681ba 100644
--- a/src/main/java/org/apache/commons/compress/archivers/ArchiveInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/ArchiveInputStream.java
@@ -26,7 +26,6 @@
import java.util.Objects;
import org.apache.commons.io.Charsets;
-import org.apache.commons.io.build.AbstractStreamBuilder;
import org.apache.commons.io.function.IOConsumer;
import org.apache.commons.io.function.IOIterator;
import org.apache.commons.io.input.NullInputStream;
@@ -52,24 +51,6 @@
*/
public abstract class ArchiveInputStream extends FilterInputStream {
- /**
- * Generic builder for ArchiveInputStream instances.
- *
- * @param The type of {@link ArchiveInputStream} to build.
- * @param The type of the concrete AbstractBuilder.
- * @since 1.29.0
- */
- public abstract static class AbstractBuilder>, B extends AbstractBuilder>
- extends AbstractStreamBuilder {
-
- /**
- * Constructs a new instance.
- */
- protected AbstractBuilder() {
- // empty
- }
- }
-
/**
* An iterator over a collection of a specific {@link ArchiveEntry} type.
*/
@@ -114,26 +95,32 @@ public Iterator unwrap() {
/** The number of bytes read in this stream. */
private long bytesRead;
- private Charset charset;
+ private final Charset charset;
+
+ private final int maxEntryNameLength;
/**
* Constructs a new instance.
+ *
+ * @deprecated Since 1.29.0, use {@link #ArchiveInputStream(AbstractArchiveBuilder)} instead.
*/
+ @Deprecated
@SuppressWarnings("resource")
public ArchiveInputStream() {
- this(new NullInputStream(), Charset.defaultCharset());
+ this(new NullInputStream(), Charset.defaultCharset().name());
}
/**
- * Constructs a new instance.
+ * Constructs a new instance from a builder.
*
- * @param inputStream the underlying input stream, or {@code null} if this instance is to be created without an underlying stream.
- * @param charset charset.
+ * @param builder The builder.
+ * @throws IOException Thrown if an I/O error occurs.
* @since 1.29.0
*/
- protected ArchiveInputStream(final InputStream inputStream, final Charset charset) {
- super(inputStream);
- this.charset = Charsets.toCharset(charset);
+ protected ArchiveInputStream(final AbstractArchiveBuilder, ?> builder) throws IOException {
+ super(builder.getInputStream());
+ this.charset = builder.getCharset();
+ this.maxEntryNameLength = builder.getMaxEntryNameLength();
}
/**
@@ -142,9 +129,13 @@ protected ArchiveInputStream(final InputStream inputStream, final Charset charse
* @param inputStream the underlying input stream, or {@code null} if this instance is to be created without an underlying stream.
* @param charsetName charset name.
* @since 1.26.0
+ * @deprecated Since 1.29.0, use {@link #ArchiveInputStream(AbstractArchiveBuilder)} instead.
*/
+ @Deprecated
protected ArchiveInputStream(final InputStream inputStream, final String charsetName) {
- this(inputStream, Charsets.toCharset(charsetName));
+ super(inputStream == null ? new NullInputStream() : inputStream);
+ this.charset = Charsets.toCharset(charsetName);
+ this.maxEntryNameLength = Short.MAX_VALUE;
}
/**
@@ -229,6 +220,16 @@ public int getCount() {
return (int) bytesRead;
}
+ /**
+ * Gets the maximum length of an archive entry name.
+ *
+ * @return The maximum length of an archive entry name.
+ * @since 1.29.0
+ */
+ protected int getMaxEntryNameLength() {
+ return maxEntryNameLength;
+ }
+
/**
* Gets the next Archive Entry in this Stream.
*
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 2c33d769d91..4a0e35176b0 100644
--- a/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java
+++ b/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java
@@ -43,8 +43,8 @@
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.Sets;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
/**
@@ -218,7 +218,7 @@ public static String detect(final InputStream in) throws ArchiveException {
in.mark(signature.length);
int signatureLength = -1;
try {
- signatureLength = IOUtils.readFully(in, signature);
+ signatureLength = IOUtils.read(in, signature);
in.reset();
} catch (final IOException e) {
throw new ArchiveException("Failure reading signature.", (Throwable) e);
@@ -247,7 +247,7 @@ public static String detect(final InputStream in) throws ArchiveException {
final byte[] dumpsig = new byte[DUMP_SIGNATURE_SIZE];
in.mark(dumpsig.length);
try {
- signatureLength = IOUtils.readFully(in, dumpsig);
+ signatureLength = IOUtils.read(in, dumpsig);
in.reset();
} catch (final IOException e) {
throw new ArchiveException("IOException while reading dump signature", (Throwable) e);
@@ -259,7 +259,7 @@ public static String detect(final InputStream in) throws ArchiveException {
final byte[] tarHeader = new byte[TAR_HEADER_SIZE];
in.mark(tarHeader.length);
try {
- signatureLength = IOUtils.readFully(in, tarHeader);
+ signatureLength = IOUtils.read(in, tarHeader);
in.reset();
} catch (final IOException e) {
throw new ArchiveException("IOException while reading tar signature", (Throwable) e);
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 819141ddfbd..117e632ddb6 100644
--- a/src/main/java/org/apache/commons/compress/archivers/Lister.java
+++ b/src/main/java/org/apache/commons/compress/archivers/Lister.java
@@ -155,7 +155,7 @@ private void listStream(final Path file, final String[] args) throws ArchiveExc
}
private void listZipUsingTarFile(final Path file) throws IOException {
- try (TarFile tarFile = new TarFile(file)) {
+ try (TarFile tarFile = TarFile.builder().setPath(file).get()) {
println("Created " + tarFile);
tarFile.getEntries().forEach(this::println);
}
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 9423d8a6407..d8b74e639f1 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
@@ -25,11 +25,13 @@
import java.util.Arrays;
import java.util.regex.Pattern;
+import org.apache.commons.compress.MemoryLimitException;
+import org.apache.commons.compress.archivers.AbstractArchiveBuilder;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.utils.ArchiveUtils;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.ParsingUtils;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
/**
@@ -52,7 +54,7 @@ public class ArArchiveInputStream extends ArchiveInputStream {
*
* @since 1.29.0
*/
- public static final class Builder extends AbstractBuilder {
+ public static final class Builder extends AbstractArchiveBuilder {
private Builder() {
setCharset(StandardCharsets.US_ASCII);
@@ -167,20 +169,21 @@ public static boolean matches(final byte[] buffer, final int ignored) {
private final byte[] metaData = new byte[NAME_LEN + LAST_MODIFIED_LEN + USER_ID_LEN + GROUP_ID_LEN + FILE_MODE_LEN + LENGTH_LEN];
private ArArchiveInputStream(final Builder builder) throws IOException {
- this(builder.getInputStream(), builder);
+ super(builder);
+ // Fail-fast if there is no signature
+ skipGlobalSignature();
}
/**
* Constructs an Ar input stream with the referenced stream
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the ar input stream
+ * @throws IOException if an I/O error has occurred.
*/
- public ArArchiveInputStream(final InputStream inputStream) {
- this(inputStream, builder());
- }
-
- private ArArchiveInputStream(final InputStream inputStream, final Builder builder) {
- super(inputStream, builder.getCharset());
+ public ArArchiveInputStream(final InputStream inputStream) throws IOException {
+ this(builder().setInputStream(inputStream));
}
private int asInt(final byte[] byteArray, final int offset, final int len, final boolean treatBlankAsZero) throws IOException {
@@ -211,10 +214,10 @@ private long asLong(final byte[] byteArray, final int offset, final int len) thr
private void checkTrailer() throws IOException {
// Check and skip the record trailer
final byte[] expectedTrailer = ArchiveUtils.toAsciiBytes(ArArchiveEntry.TRAILER);
- final byte[] actualTrailer = IOUtils.readRange(in, expectedTrailer.length);
+ final byte[] actualTrailer = org.apache.commons.compress.utils.IOUtils.readRange(in, expectedTrailer.length);
if (actualTrailer.length < expectedTrailer.length) {
throw new EOFException(String.format(
- "Premature end of ar archive: invalid or incomplete trailer for entry '%s'.",
+ "Premature end of ar archive: Invalid or incomplete trailer for entry '%s'.",
ArchiveUtils.toAsciiString(metaData, NAME_OFFSET, NAME_LEN).trim()));
}
count(actualTrailer.length);
@@ -244,8 +247,8 @@ public void close() throws IOException {
* @since 1.3
*/
private String getBSDLongName(final String bsdLongName) throws IOException {
- final int nameLen = ParsingUtils.parseIntValue(bsdLongName.substring(BSD_LONGNAME_PREFIX_LEN));
- final byte[] name = IOUtils.readRange(in, nameLen);
+ final int nameLen = checkEntryNameLength(ParsingUtils.parseIntValue(bsdLongName.substring(BSD_LONGNAME_PREFIX_LEN)));
+ final byte[] name = org.apache.commons.compress.utils.IOUtils.readRange(in, nameLen);
final int read = name.length;
count(read);
if (read != nameLen) {
@@ -276,7 +279,7 @@ private String getExtendedName(final int offset) throws IOException {
}
// Check there is a something to return, otherwise break out of the loop
if (i > offset) {
- return ArchiveUtils.toAsciiString(namebuffer, offset, i - offset);
+ return ArchiveUtils.toAsciiString(namebuffer, offset, checkEntryNameLength(i - offset));
}
break;
}
@@ -284,6 +287,10 @@ private String getExtendedName(final int offset) throws IOException {
throw new ArchiveException("Failed to read GNU long file name at offset " + offset);
}
+ private int checkEntryNameLength(final int nameLength) throws ArchiveException, MemoryLimitException {
+ return ArchiveUtils.checkEntryNameLength(nameLength, getMaxEntryNameLength(), "AR");
+ }
+
/**
* Returns the next AR entry in this stream.
*
@@ -306,14 +313,12 @@ public ArArchiveEntry getNextArEntry() throws IOException {
*/
@Override
public ArArchiveEntry getNextEntry() throws IOException {
- skipGlobalSignature();
-
// Handle special GNU ar entries
boolean foundGNUStringTable = false;
do {
// If there is a current entry, skip any unread data and padding
if (currentEntry != null) {
- IOUtils.skip(this, Long.MAX_VALUE); // Skip to end of current entry
+ IOUtils.consume(this); // Skip to end of current entry
skipRecordPadding(); // Skip padding to align to the next record
}
@@ -328,7 +333,7 @@ public ArArchiveEntry getNextEntry() throws IOException {
//
// Reference: https://man.freebsd.org/cgi/man.cgi?query=ar&sektion=5
if (foundGNUStringTable) {
- throw new EOFException("Premature end of ar archive: no regular entry after GNU string table.");
+ throw new EOFException("Premature end of ar archive: No regular entry after GNU string table.");
}
currentEntry = null;
return null; // End of archive
@@ -363,7 +368,7 @@ public ArArchiveEntry getNextEntry() throws IOException {
final int nameLen = name.length();
if (nameLen > len) {
throw new ArchiveException(
- "Invalid BSD long name: file name length (" + nameLen + ") exceeds entry length (" + len + ")");
+ "Invalid BSD long name: File name length (" + nameLen + ") exceeds entry length (" + len + ")");
}
len -= nameLen;
entryOffset += nameLen;
@@ -384,14 +389,14 @@ public ArArchiveEntry getNextEntry() throws IOException {
* @throws IOException if an I/O error occurs while reading the stream or if the record is malformed.
*/
private byte[] getRecord() throws IOException {
- final int read = IOUtils.readFully(in, metaData);
+ final int read = IOUtils.read(in, metaData);
count(read);
if (read == 0) {
return null;
}
if (read < metaData.length) {
throw new EOFException(String.format(
- "Premature end of ar archive: incomplete entry header (expected %d bytes, got %d).",
+ "Premature end of ar archive: Incomplete entry header (expected %d bytes, got %d).",
metaData.length, read));
}
return metaData;
@@ -435,13 +440,9 @@ private ArArchiveEntry parseEntry(final byte[] headerBuf) throws IOException {
}
}
- /*
- * (non-Javadoc)
- *
- * @see InputStream#read(byte[], int, int)
- */
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
@@ -457,7 +458,7 @@ public int read(final byte[] b, final int off, final int len) throws IOException
final int ret = in.read(b, off, toRead);
if (ret < 0) {
throw new EOFException(String.format(
- "Premature end of ar archive: entry '%s' is truncated or incomplete.", currentEntry.getName()));
+ "Premature end of ar archive: Entry '%s' is truncated or incomplete.", currentEntry.getName()));
}
count(ret);
return ret;
@@ -473,10 +474,10 @@ private byte[] readGNUStringTable(final ArArchiveEntry entry) throws IOException
throw new ArchiveException("Invalid GNU string table entry size: " + entry.getLength());
}
final int size = (int) entry.getLength();
- final byte[] namebuffer = IOUtils.readRange(in, size);
+ final byte[] namebuffer = org.apache.commons.compress.utils.IOUtils.readRange(in, size);
final int read = namebuffer.length;
if (read < size) {
- throw new EOFException("Premature end of ar archive: truncated or incomplete GNU string table.");
+ throw new EOFException("Premature end of ar archive: Truncated or incomplete GNU string table.");
}
count(read);
return namebuffer;
@@ -488,20 +489,15 @@ private byte[] readGNUStringTable(final ArArchiveEntry entry) throws IOException
* @throws IOException if an I/O error occurs while reading the stream or if the signature is invalid.
*/
private void skipGlobalSignature() throws IOException {
- final long offset = getBytesRead();
- if (offset == 0) {
- final byte[] expectedMagic = ArArchiveEntry.HEADER_BYTES;
- final byte[] actualMagic = IOUtils.readRange(in, expectedMagic.length);
- count(actualMagic.length);
- if (expectedMagic.length != actualMagic.length) {
- throw new EOFException(String.format(
- "Premature end of ar archive: incomplete global header (expected %d bytes, got %d).",
- expectedMagic.length, actualMagic.length));
- }
- if (!Arrays.equals(expectedMagic, actualMagic)) {
- throw new ArchiveException(
- "Invalid global ar archive header: " + ArchiveUtils.toAsciiString(actualMagic));
- }
+ final byte[] expectedMagic = ArArchiveEntry.HEADER_BYTES;
+ final byte[] actualMagic = org.apache.commons.compress.utils.IOUtils.readRange(in, expectedMagic.length);
+ count(actualMagic.length);
+ if (expectedMagic.length != actualMagic.length) {
+ throw new EOFException(String.format("Premature end of ar archive: Incomplete global header (expected %d bytes, got %d).", expectedMagic.length,
+ actualMagic.length));
+ }
+ if (!Arrays.equals(expectedMagic, actualMagic)) {
+ throw new ArchiveException("Invalid global ar archive header: " + ArchiveUtils.toAsciiString(actualMagic));
}
}
@@ -521,7 +517,7 @@ private void skipRecordPadding() throws IOException {
final int c = in.read();
if (c < 0) {
throw new EOFException(String.format(
- "Premature end of ar archive: missing padding for entry '%s'.", currentEntry.getName()));
+ "Premature end of ar archive: Missing padding for entry '%s'.", currentEntry.getName()));
}
count(1);
}
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 44aca37590a..7257e5cd6c5 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
@@ -20,17 +20,19 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.zip.CRC32;
+import org.apache.commons.compress.archivers.AbstractArchiveBuilder;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
-import org.apache.commons.compress.utils.IOUtils;
+import org.apache.commons.compress.utils.ArchiveUtils;
+import org.apache.commons.io.EndianUtils;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BoundedInputStream;
import org.apache.commons.io.input.ChecksumInputStream;
@@ -38,7 +40,7 @@
* Implements the "arj" archive format as an InputStream.
*
*
* @NotThreadSafe
@@ -55,12 +57,15 @@ public class ArjArchiveInputStream extends ArchiveInputStream {
* ArjArchiveInputStream in = ArjArchiveInputStream.builder()
* .setPath(inputPath)
* .setCharset(StandardCharsets.UTF_8)
+ * .setSelfExtracting(false)
* .get();
* }
*
* @since 1.29.0
*/
- public static final class Builder extends AbstractBuilder {
+ public static final class Builder extends AbstractArchiveBuilder {
+
+ private boolean selfExtracting;
private Builder() {
setCharset(ENCODING_NAME);
@@ -70,12 +75,46 @@ private Builder() {
public ArjArchiveInputStream get() throws IOException {
return new ArjArchiveInputStream(this);
}
+
+ /**
+ * Enables compatibility with self-extracting (SFX) ARJ files, default to {@code false}.
+ *
+ * When {@code true}, the stream is scanned forward to locate the first
+ * valid ARJ main header. All bytes before that point are ignored, which
+ * allows reading ARJ data embedded in an executable stub.
+ *
+ * Caveat: This lenient pre-scan can mask corruption that
+ * would otherwise be reported at the start of a normal {@code .arj} file.
+ * Enable only when you expect an SFX input.
+ *
+ * Default to {@code false}.
+ *
+ * @param selfExtracting {@code true} if the input stream is for a self-extracting archive.
+ * @return {@code this} instance.
+ * @since 1.29.0
+ */
+ public Builder setSelfExtracting(final boolean selfExtracting) {
+ this.selfExtracting = selfExtracting;
+ return asThis();
+ }
}
private static final String ENCODING_NAME = "CP437";
private static final int ARJ_MAGIC_1 = 0x60;
private static final int ARJ_MAGIC_2 = 0xEA;
+ /**
+ * Maximum size of the basic header, in bytes.
+ *
+ * The value is taken from the reference implementation
+ */
+ private static final int MAX_BASIC_HEADER_SIZE = 2600;
+
+ /**
+ * Minimum size of the first header (the fixed-size part of the basic header), in bytes.
+ */
+ private static final int MIN_FIRST_HEADER_SIZE = 30;
+
/**
* Creates a new builder.
*
@@ -89,60 +128,62 @@ public static Builder builder() {
/**
* 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
+ * @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 dis;
+ private static int readUnsignedByte(final InputStream in) throws IOException {
+ final int value = in.read();
+ if (value == -1) {
+ throw new EOFException("Truncated ARJ archive: Expected more data");
+ }
+ return value & 0xff;
+ }
+
private final MainHeader mainHeader;
private LocalFileHeader currentLocalFileHeader;
private InputStream currentInputStream;
private ArjArchiveInputStream(final Builder builder) throws IOException {
- this(builder.getInputStream(), builder);
+ super(builder);
+ mainHeader = readMainHeader(builder.selfExtracting);
+ if ((mainHeader.arjFlags & MainHeader.Flags.GARBLED) != 0) {
+ throw new ArchiveException("Encrypted ARJ files are unsupported");
+ }
+ if ((mainHeader.arjFlags & MainHeader.Flags.VOLUME) != 0) {
+ throw new ArchiveException("Multi-volume ARJ files are unsupported");
+ }
}
/**
* 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
+ * Since 1.29.0: Throws {@link IOException}.
+ *
+ * @param inputStream the underlying stream, whose ownership is taken.
+ * @throws IOException if an exception occurs while reading.
*/
- public ArjArchiveInputStream(final InputStream inputStream) throws ArchiveException {
- this(inputStream, builder());
- }
-
- private ArjArchiveInputStream(final InputStream inputStream, final Builder builder) throws ArchiveException {
- super(new DataInputStream(inputStream), builder.getCharset());
- dis = (DataInputStream) in;
- try {
- mainHeader = readMainHeader();
- if ((mainHeader.arjFlags & MainHeader.Flags.GARBLED) != 0) {
- throw new ArchiveException("Encrypted ARJ files are unsupported");
- }
- if ((mainHeader.arjFlags & MainHeader.Flags.VOLUME) != 0) {
- throw new ArchiveException("Multi-volume ARJ files are unsupported");
- }
- } catch (final IOException e) {
- throw new ArchiveException(e.getMessage(), (Throwable) e);
- }
+ public ArjArchiveInputStream(final InputStream inputStream) throws IOException {
+ this(builder().setInputStream(inputStream));
}
/**
* Constructs the ArjInputStream, taking ownership of the inputStream that is passed in.
*
- * @param inputStream the underlying stream, whose ownership is taken
+ * Since 1.29.0: Throws {@link IOException}.
+ *
+ * @param inputStream the underlying stream, whose ownership is taken.
* @param charsetName the charset used for file names and comments in the archive. May be {@code null} to use the platform default.
- * @throws ArchiveException if an exception occurs while reading
+ * @throws IOException if an exception occurs while reading
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public ArjArchiveInputStream(final InputStream inputStream, final String charsetName) throws ArchiveException {
- this(inputStream, builder().setCharset(charsetName));
+ public ArjArchiveInputStream(final InputStream inputStream, final String charsetName) throws IOException {
+ this(builder().setInputStream(inputStream).setCharset(charsetName));
}
@Override
@@ -150,9 +191,53 @@ public boolean canReadEntryData(final ArchiveEntry ae) {
return ae instanceof ArjArchiveEntry && ((ArjArchiveEntry) ae).getMethod() == LocalFileHeader.Methods.STORED;
}
- @Override
- public void close() throws IOException {
- dis.close();
+ /**
+ * Verifies the CRC32 checksum of the given data against the next four bytes read from the input stream.
+ *
+ * @param data The data to verify.
+ * @return true if the checksum matches, false otherwise.
+ * @throws EOFException If the end of the stream is reached before reading the checksum.
+ * @throws IOException If an I/O error occurs.
+ */
+ @SuppressWarnings("Since15")
+ private boolean checkCRC32(final byte[] data) throws IOException {
+ final CRC32 crc32 = new CRC32();
+ crc32.update(data);
+ final long expectedCrc32 = readSwappedUnsignedInteger();
+ return crc32.getValue() == expectedCrc32;
+ }
+
+ /**
+ * Scans for the next valid ARJ header.
+ *
+ * @return The header bytes.
+ * @throws EOFException If the end of the stream is reached before a valid header is found.
+ * @throws IOException If an I/O error occurs.
+ */
+ private byte[] findMainHeader() throws IOException {
+ byte[] basicHeaderBytes;
+ try {
+ while (true) {
+ int first;
+ int second = readUnsignedByte();
+ do {
+ first = second;
+ second = readUnsignedByte();
+ } while (first != ARJ_MAGIC_1 && second != ARJ_MAGIC_2);
+ final int basicHeaderSize = readSwappedUnsignedShort();
+ // At least two bytes are required for the null-terminated name and comment
+ if (MIN_FIRST_HEADER_SIZE + 2 <= basicHeaderSize && basicHeaderSize <= MAX_BASIC_HEADER_SIZE) {
+ basicHeaderBytes = IOUtils.toByteArray(in, basicHeaderSize);
+ count(basicHeaderSize);
+ if (checkCRC32(basicHeaderBytes)) {
+ return basicHeaderBytes;
+ }
+ }
+ // CRC32 failed, continue scanning
+ }
+ } catch (final EOFException e) {
+ throw new ArchiveException("Corrupted ARJ archive: Unable to find valid main header");
+ }
}
/**
@@ -178,7 +263,7 @@ public ArjArchiveEntry getNextEntry() throws IOException {
if (currentInputStream != null) {
// return value ignored as IOUtils.skip ensures the stream is drained completely
final InputStream input = currentInputStream;
- org.apache.commons.io.IOUtils.skip(input, Long.MAX_VALUE);
+ IOUtils.consume(input);
currentInputStream.close();
currentLocalFileHeader = null;
currentInputStream = null;
@@ -187,10 +272,22 @@ public ArjArchiveEntry getNextEntry() throws IOException {
currentLocalFileHeader = readLocalFileHeader();
if (currentLocalFileHeader != null) {
// @formatter:off
+ final long currentPosition = getBytesRead();
currentInputStream = BoundedInputStream.builder()
- .setInputStream(dis)
+ .setInputStream(in)
.setMaxCount(currentLocalFileHeader.compressedSize)
.setPropagateClose(false)
+ .setAfterRead(read -> {
+ if (read < 0) {
+ throw new EOFException(String.format(
+ "Truncated ARJ archive: Entry '%s' expected %,d bytes, but only %,d were read.",
+ currentLocalFileHeader.name,
+ currentLocalFileHeader.compressedSize,
+ getBytesRead() - currentPosition
+ ));
+ }
+ count(read);
+ })
.get();
// @formatter:on
if (currentLocalFileHeader.method == LocalFileHeader.Methods.STORED) {
@@ -211,6 +308,7 @@ public ArjArchiveEntry getNextEntry() throws IOException {
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
@@ -223,62 +321,43 @@ public int read(final byte[] b, final int off, final int len) throws IOException
return currentInputStream.read(b, off, len);
}
- private int read16(final DataInputStream dataIn) throws IOException {
- final int value = dataIn.readUnsignedShort();
- count(2);
- return Integer.reverseBytes(value) >>> 16;
- }
-
- private int read32(final DataInputStream dataIn) throws IOException {
- final int value = dataIn.readInt();
- count(4);
- return Integer.reverseBytes(value);
- }
-
- private int read8(final DataInputStream dataIn) throws IOException {
- final int value = dataIn.readUnsignedByte();
- count(1);
- return value;
+ private String readComment(final InputStream dataIn) throws IOException {
+ return new String(readString(dataIn).toByteArray(), getCharset());
}
- 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 String readEntryName(final InputStream dataIn) throws IOException {
+ final ByteArrayOutputStream buffer = readString(dataIn);
+ ArchiveUtils.checkEntryNameLength(buffer.size(), getMaxEntryNameLength(), "ARJ");
+ return new String(buffer.toByteArray(), getCharset());
}
+ /**
+ * Reads the next valid ARJ header.
+ *
+ * @return The header bytes, or {@code null} if end of archive.
+ * @throws EOFException If the end of the stream is reached before a valid header is found.
+ * @throws IOException If an I/O error occurs.
+ */
private byte[] readHeader() throws IOException {
- boolean found = false;
- byte[] basicHeaderBytes = null;
- do {
- int first;
- int second = read8(dis);
- do {
- first = second;
- second = read8(dis);
- } while (first != ARJ_MAGIC_1 && second != ARJ_MAGIC_2);
- final int basicHeaderSize = read16(dis);
- if (basicHeaderSize == 0) {
- // end of archive
- return null;
- }
- if (basicHeaderSize <= 2600) {
- basicHeaderBytes = readRange(dis, basicHeaderSize);
- final long basicHeaderCrc32 = read32(dis) & 0xFFFFFFFFL;
- final CRC32 crc32 = new CRC32();
- crc32.update(basicHeaderBytes);
- if (basicHeaderCrc32 == crc32.getValue()) {
- found = true;
- }
- }
- } while (!found);
+ final int first = readUnsignedByte();
+ final int second = readUnsignedByte();
+ if (first != ARJ_MAGIC_1 || second != ARJ_MAGIC_2) {
+ throw new ArchiveException("Corrupted ARJ archive: Invalid ARJ header signature 0x%02X 0x%02X", first, second);
+ }
+ final int basicHeaderSize = readSwappedUnsignedShort();
+ if (basicHeaderSize == 0) {
+ // End of archive
+ return null;
+ }
+ // At least two bytes are required for the null-terminated name and comment
+ if (basicHeaderSize < MIN_FIRST_HEADER_SIZE + 2 || basicHeaderSize > MAX_BASIC_HEADER_SIZE) {
+ throw new ArchiveException("Corrupted ARJ archive: Invalid ARJ header size %,d", basicHeaderSize);
+ }
+ final byte[] basicHeaderBytes = IOUtils.toByteArray(in, basicHeaderSize);
+ count(basicHeaderSize);
+ if (!checkCRC32(basicHeaderBytes)) {
+ throw new ArchiveException("Corrupted ARJ archive: Invalid ARJ header CRC32 checksum");
+ }
return basicHeaderBytes;
}
@@ -287,126 +366,123 @@ private LocalFileHeader readLocalFileHeader() throws IOException {
if (basicHeaderBytes == null) {
return null;
}
- try (DataInputStream basicHeader = new DataInputStream(new ByteArrayInputStream(basicHeaderBytes))) {
-
- final int firstHeaderSize = basicHeader.readUnsignedByte();
- final byte[] firstHeaderBytes = readRange(basicHeader, firstHeaderSize - 1);
- pushedBackBytes(firstHeaderBytes.length);
- try (DataInputStream firstHeader = new DataInputStream(new ByteArrayInputStream(firstHeaderBytes))) {
-
- final LocalFileHeader localFileHeader = new LocalFileHeader();
- localFileHeader.archiverVersionNumber = firstHeader.readUnsignedByte();
- localFileHeader.minVersionToExtract = firstHeader.readUnsignedByte();
- localFileHeader.hostOS = firstHeader.readUnsignedByte();
- localFileHeader.arjFlags = firstHeader.readUnsignedByte();
- localFileHeader.method = firstHeader.readUnsignedByte();
- localFileHeader.fileType = firstHeader.readUnsignedByte();
- localFileHeader.reserved = firstHeader.readUnsignedByte();
- localFileHeader.dateTimeModified = read32(firstHeader);
- localFileHeader.compressedSize = 0xffffFFFFL & read32(firstHeader);
- localFileHeader.originalSize = 0xffffFFFFL & read32(firstHeader);
- localFileHeader.originalCrc32 = 0xffffFFFFL & read32(firstHeader);
- localFileHeader.fileSpecPosition = read16(firstHeader);
- localFileHeader.fileAccessMode = read16(firstHeader);
- pushedBackBytes(20);
- localFileHeader.firstChapter = firstHeader.readUnsignedByte();
- localFileHeader.lastChapter = firstHeader.readUnsignedByte();
-
- readExtraData(firstHeaderSize, firstHeader, localFileHeader);
-
- localFileHeader.name = readString(basicHeader);
- localFileHeader.comment = readString(basicHeader);
-
- final ArrayList extendedHeaders = new ArrayList<>();
- int extendedHeaderSize;
- while ((extendedHeaderSize = read16(dis)) > 0) {
- final byte[] extendedHeaderBytes = readRange(dis, extendedHeaderSize);
- final long extendedHeaderCrc32 = 0xffffFFFFL & read32(dis);
- final CRC32 crc32 = new CRC32();
- crc32.update(extendedHeaderBytes);
- if (extendedHeaderCrc32 != crc32.getValue()) {
- throw new ArchiveException("Extended header CRC32 verification failure");
+ final LocalFileHeader localFileHeader = new LocalFileHeader();
+ try (InputStream basicHeader = new ByteArrayInputStream(basicHeaderBytes)) {
+ final int firstHeaderSize = readUnsignedByte(basicHeader);
+ try (InputStream firstHeader = BoundedInputStream.builder().setInputStream(basicHeader).setMaxCount(firstHeaderSize - 1).get()) {
+ localFileHeader.archiverVersionNumber = readUnsignedByte(firstHeader);
+ localFileHeader.minVersionToExtract = readUnsignedByte(firstHeader);
+ localFileHeader.hostOS = readUnsignedByte(firstHeader);
+ localFileHeader.arjFlags = readUnsignedByte(firstHeader);
+ localFileHeader.method = readUnsignedByte(firstHeader);
+ localFileHeader.fileType = readUnsignedByte(firstHeader);
+ localFileHeader.reserved = readUnsignedByte(firstHeader);
+ localFileHeader.dateTimeModified = EndianUtils.readSwappedInteger(firstHeader);
+ localFileHeader.compressedSize = EndianUtils.readSwappedUnsignedInteger(firstHeader);
+ localFileHeader.originalSize = EndianUtils.readSwappedUnsignedInteger(firstHeader);
+ localFileHeader.originalCrc32 = EndianUtils.readSwappedUnsignedInteger(firstHeader);
+ localFileHeader.fileSpecPosition = EndianUtils.readSwappedShort(firstHeader);
+ localFileHeader.fileAccessMode = EndianUtils.readSwappedShort(firstHeader);
+ localFileHeader.firstChapter = readUnsignedByte(firstHeader);
+ localFileHeader.lastChapter = readUnsignedByte(firstHeader);
+ // Total read (including size byte): 10 + 4 * 4 + 2 * 2 = 30 bytes
+ if (firstHeaderSize >= MIN_FIRST_HEADER_SIZE + 4) {
+ localFileHeader.extendedFilePosition = EndianUtils.readSwappedInteger(firstHeader);
+ // Total read (including size byte): 30 + 4 = 34 bytes
+ if (firstHeaderSize >= MIN_FIRST_HEADER_SIZE + 4 + 12) {
+ localFileHeader.dateTimeAccessed = EndianUtils.readSwappedInteger(firstHeader);
+ localFileHeader.dateTimeCreated = EndianUtils.readSwappedInteger(firstHeader);
+ localFileHeader.originalSizeEvenForVolumes = EndianUtils.readSwappedInteger(firstHeader);
+ // Total read (including size byte): 34 + 12 = 46 bytes
}
- extendedHeaders.add(extendedHeaderBytes);
}
- localFileHeader.extendedHeaders = extendedHeaders.toArray(new byte[0][]);
-
- return localFileHeader;
}
+ localFileHeader.name = readEntryName(basicHeader);
+ localFileHeader.comment = readComment(basicHeader);
}
- }
-
- private MainHeader readMainHeader() throws IOException {
- final byte[] basicHeaderBytes = readHeader();
- if (basicHeaderBytes == null) {
- throw new ArchiveException("Archive ends without any headers");
+ final ArrayList extendedHeaders = new ArrayList<>();
+ int extendedHeaderSize;
+ while ((extendedHeaderSize = readSwappedUnsignedShort()) > 0) {
+ final byte[] extendedHeaderBytes = IOUtils.toByteArray(in, extendedHeaderSize);
+ count(extendedHeaderSize);
+ if (!checkCRC32(extendedHeaderBytes)) {
+ throw new ArchiveException("Corrupted ARJ archive: Extended header CRC32 verification failure");
+ }
+ extendedHeaders.add(extendedHeaderBytes);
}
- 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));
+ localFileHeader.extendedHeaders = extendedHeaders.toArray(new byte[0][]);
+ return localFileHeader;
+ }
+ private MainHeader readMainHeader(final boolean selfExtracting) throws IOException {
+ final byte[] basicHeaderBytes = selfExtracting ? findMainHeader() : readHeader();
final MainHeader header = new MainHeader();
- header.archiverVersionNumber = firstHeader.readUnsignedByte();
- header.minVersionToExtract = firstHeader.readUnsignedByte();
- header.hostOS = firstHeader.readUnsignedByte();
- header.arjFlags = firstHeader.readUnsignedByte();
- header.securityVersion = firstHeader.readUnsignedByte();
- header.fileType = firstHeader.readUnsignedByte();
- header.reserved = firstHeader.readUnsignedByte();
- header.dateTimeCreated = read32(firstHeader);
- header.dateTimeModified = read32(firstHeader);
- header.archiveSize = 0xffffFFFFL & read32(firstHeader);
- header.securityEnvelopeFilePosition = read32(firstHeader);
- header.fileSpecPosition = read16(firstHeader);
- header.securityEnvelopeLength = read16(firstHeader);
- pushedBackBytes(20); // count has already counted them via readRange
- header.encryptionVersion = firstHeader.readUnsignedByte();
- header.lastChapter = firstHeader.readUnsignedByte();
-
- if (firstHeaderSize >= 33) {
- header.arjProtectionFactor = firstHeader.readUnsignedByte();
- header.arjFlags2 = firstHeader.readUnsignedByte();
- firstHeader.readUnsignedByte();
- firstHeader.readUnsignedByte();
+ try (InputStream basicHeader = new ByteArrayInputStream(basicHeaderBytes)) {
+ final int firstHeaderSize = readUnsignedByte(basicHeader);
+ try (InputStream firstHeader = BoundedInputStream.builder().setInputStream(basicHeader).setMaxCount(firstHeaderSize - 1).get()) {
+ header.archiverVersionNumber = readUnsignedByte(firstHeader);
+ header.minVersionToExtract = readUnsignedByte(firstHeader);
+ header.hostOS = readUnsignedByte(firstHeader);
+ header.arjFlags = readUnsignedByte(firstHeader);
+ header.securityVersion = readUnsignedByte(firstHeader);
+ header.fileType = readUnsignedByte(firstHeader);
+ header.reserved = readUnsignedByte(firstHeader);
+ header.dateTimeCreated = EndianUtils.readSwappedInteger(firstHeader);
+ header.dateTimeModified = EndianUtils.readSwappedInteger(firstHeader);
+ header.archiveSize = EndianUtils.readSwappedUnsignedInteger(firstHeader);
+ header.securityEnvelopeFilePosition = EndianUtils.readSwappedInteger(firstHeader);
+ header.fileSpecPosition = EndianUtils.readSwappedShort(firstHeader);
+ header.securityEnvelopeLength = EndianUtils.readSwappedShort(firstHeader);
+ header.encryptionVersion = readUnsignedByte(firstHeader);
+ header.lastChapter = readUnsignedByte(firstHeader);
+ // Total read (including size byte): 10 + 4 * 4 + 2 * 2 = 30 bytes
+ if (firstHeaderSize >= MIN_FIRST_HEADER_SIZE + 4) {
+ header.arjProtectionFactor = readUnsignedByte(firstHeader);
+ header.arjFlags2 = readUnsignedByte(firstHeader);
+ readUnsignedByte(firstHeader);
+ readUnsignedByte(firstHeader);
+ // Total read (including size byte): 30 + 4 = 34 bytes
+ }
+ }
+ header.name = readEntryName(basicHeader);
+ header.comment = readComment(basicHeader);
}
-
- header.name = readString(basicHeader);
- header.comment = readString(basicHeader);
-
- final int extendedHeaderSize = read16(dis);
+ final int extendedHeaderSize = readSwappedUnsignedShort();
if (extendedHeaderSize > 0) {
- header.extendedHeaderBytes = readRange(dis, extendedHeaderSize);
- final long extendedHeaderCrc32 = 0xffffFFFFL & read32(dis);
- final CRC32 crc32 = new CRC32();
- crc32.update(header.extendedHeaderBytes);
- if (extendedHeaderCrc32 != crc32.getValue()) {
- throw new ArchiveException("Extended header CRC32 verification failure");
+ header.extendedHeaderBytes = IOUtils.toByteArray(in, extendedHeaderSize);
+ count(extendedHeaderSize);
+ if (!checkCRC32(header.extendedHeaderBytes)) {
+ throw new ArchiveException("Corrupted ARJ archive: Extended header CRC32 verification failure");
}
}
-
return header;
}
- 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();
- }
- return b;
- }
-
- private String readString(final DataInputStream dataIn) throws IOException {
+ private ByteArrayOutputStream readString(final InputStream dataIn) throws IOException {
try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
int nextByte;
- while ((nextByte = dataIn.readUnsignedByte()) != 0) {
+ while ((nextByte = readUnsignedByte(dataIn)) != 0) {
buffer.write(nextByte);
}
- return buffer.toString(getCharset().name());
+ return buffer;
}
}
+
+ private long readSwappedUnsignedInteger() throws IOException {
+ final long value = EndianUtils.readSwappedUnsignedInteger(in);
+ count(4);
+ return value;
+ }
+
+ private int readSwappedUnsignedShort() throws IOException {
+ final int value = EndianUtils.readSwappedUnsignedShort(in);
+ count(2);
+ return value;
+ }
+
+ private int readUnsignedByte() throws IOException {
+ final int value = readUnsignedByte(in);
+ count(1);
+ return value & 0xff;
+ }
}
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 d996954072c..1d580dc6b70 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
@@ -22,13 +22,14 @@
import java.io.IOException;
import java.io.InputStream;
+import org.apache.commons.compress.archivers.AbstractArchiveBuilder;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipEncoding;
import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
import org.apache.commons.compress.utils.ArchiveUtils;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.ParsingUtils;
+import org.apache.commons.io.IOUtils;
/**
* CpioArchiveInputStream is a stream for reading cpio streams. All formats of cpio are supported (old ASCII, old binary, new portable format and the new
@@ -79,7 +80,7 @@ public class CpioArchiveInputStream extends ArchiveInputStream
*
* @since 1.29.0
*/
- public static final class Builder extends AbstractBuilder {
+ public static final class Builder extends AbstractArchiveBuilder {
private int blockSize = BLOCK_SIZE;
@@ -198,66 +199,74 @@ public static boolean matches(final byte[] signature, final int length) {
private final ZipEncoding zipEncoding;
private CpioArchiveInputStream(final Builder builder) throws IOException {
- this(builder.getInputStream(), builder);
+ super(builder);
+ if (builder.blockSize <= 0) {
+ throw new IllegalArgumentException("blockSize must be bigger than 0");
+ }
+ this.blockSize = builder.blockSize;
+ this.zipEncoding = ZipEncodingHelper.getZipEncoding(builder.getCharset());
}
/**
* Constructs the cpio input stream with a blocksize of {@link CpioConstants#BLOCK_SIZE BLOCK_SIZE} and expecting ASCII file names.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param in The cpio stream
+ * @throws IOException if an I/O error has occurred.
*/
- public CpioArchiveInputStream(final InputStream in) {
- this(in, builder());
- }
-
- private CpioArchiveInputStream(final InputStream in, final Builder builder) {
- super(in, builder.getCharset());
- if (builder.blockSize <= 0) {
- throw new IllegalArgumentException("blockSize must be bigger than 0");
- }
- this.blockSize = builder.blockSize;
- this.zipEncoding = ZipEncodingHelper.getZipEncoding(builder.getCharset());
+ public CpioArchiveInputStream(final InputStream in) throws IOException {
+ this(builder().setInputStream(in));
}
/**
* Constructs the cpio input stream with a blocksize of {@link CpioConstants#BLOCK_SIZE BLOCK_SIZE} expecting ASCII file names.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param in The cpio stream
* @param blockSize The block size of the archive.
+ * @throws IOException if an I/O error has occurred.
* @since 1.5
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public CpioArchiveInputStream(final InputStream in, final int blockSize) {
- this(in, builder().setBlockSize(blockSize));
+ public CpioArchiveInputStream(final InputStream in, final int blockSize) throws IOException {
+ this(builder().setInputStream(in).setBlockSize(blockSize));
}
/**
* Constructs the cpio input stream with a blocksize of {@link CpioConstants#BLOCK_SIZE BLOCK_SIZE}.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param in The cpio stream
* @param blockSize The block size of the archive.
* @param encoding The encoding of file names to expect - use null for the platform's default.
* @throws IllegalArgumentException if {@code blockSize} is not bigger than 0
+ * @throws IOException if an I/O error has occurred.
* @since 1.6
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public CpioArchiveInputStream(final InputStream in, final int blockSize, final String encoding) {
- this(in, builder().setBlockSize(blockSize).setCharset(encoding));
+ public CpioArchiveInputStream(final InputStream in, final int blockSize, final String encoding) throws IOException {
+ this(builder().setInputStream(in).setBlockSize(blockSize).setCharset(encoding));
}
/**
* Constructs the cpio input stream with a blocksize of {@link CpioConstants#BLOCK_SIZE BLOCK_SIZE}.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param in The cpio stream
* @param encoding The encoding of file names to expect - use null for the platform's default.
+ * @throws IOException if an I/O error has occurred.
* @since 1.6
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public CpioArchiveInputStream(final InputStream in, final String encoding) {
- this(in, builder().setCharset(encoding));
+ public CpioArchiveInputStream(final InputStream in, final String encoding) throws IOException {
+ this(builder().setInputStream(in).setCharset(encoding));
}
/**
@@ -289,7 +298,7 @@ private void checkOpen() throws IOException {
/**
* Closes the CPIO input stream.
*
- * @throws IOException if an I/O error has occurred
+ * @throws IOException if an I/O error has occurred.
*/
@Override
public void close() throws IOException {
@@ -299,19 +308,6 @@ public void close() throws IOException {
}
}
- /**
- * Closes the current CPIO entry and positions the stream for reading the next entry.
- *
- * @throws IOException if an I/O error has occurred or if a CPIO file error has occurred
- */
- private void closeEntry() throws IOException {
- // the skip implementation of this class will not skip more
- // than Integer.MAX_VALUE bytes
- while (skip((long) Integer.MAX_VALUE) == Integer.MAX_VALUE) { // NOPMD NOSONAR
- // do nothing
- }
- }
-
/**
* Reads the next CPIO file entry and positions stream at the beginning of the entry data.
*
@@ -323,7 +319,7 @@ private void closeEntry() throws IOException {
public CpioArchiveEntry getNextCPIOEntry() throws IOException {
checkOpen();
if (entry != null) {
- closeEntry();
+ IOUtils.consume(this);
}
readFully(buffer2, 0, buffer2.length);
if (CpioUtil.byteArray2long(buffer2, false) == MAGIC_OLD_BINARY) {
@@ -371,17 +367,17 @@ public CpioArchiveEntry getNextEntry() throws IOException {
* @param off the start offset of the data
* @param len the maximum number of bytes read
* @return the actual number of bytes read, or -1 if the end of the entry is reached
+ * @throws NullPointerException if b is null
+ * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code b.length}.
* @throws IOException if an I/O error has occurred or if a CPIO file error has occurred
*/
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
- checkOpen();
- if (off < 0 || len < 0 || off > b.length - len) {
- throw new IndexOutOfBoundsException();
- }
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
+ checkOpen();
if (entry == null || entryEOF) {
return -1;
}
@@ -403,9 +399,9 @@ public int read(final byte[] b, final int off, final int len) throws IOException
final int tmpread = readFully(b, off, tmplength);
if (entry.getFormat() == FORMAT_NEW_CRC) {
for (int pos = 0; pos < tmpread; pos++) {
- crc += b[pos] & 0xFF;
- crc &= 0xFFFFFFFFL;
+ crc += b[off + pos] & 0xFF;
}
+ crc &= 0xFFFFFFFFL;
}
if (tmpread > 0) {
entryBytesRead += tmpread;
@@ -423,9 +419,10 @@ private long readBinaryLong(final int length, final boolean swapHalfWord) throws
return CpioUtil.byteArray2long(tmp, swapHalfWord);
}
- private String readCString(final int length) throws IOException {
+ private String readEntryName(int lengthWithNull) throws IOException {
+ final int length = ArchiveUtils.checkEntryNameLength(lengthWithNull - 1, getMaxEntryNameLength(), "CPIO");
// don't include trailing NUL in file name to decode
- final byte[] tmpBuffer = readRange(length - 1);
+ final byte[] tmpBuffer = readRange(length);
if (in.read() == -1) {
throw new EOFException();
}
@@ -433,7 +430,7 @@ private String readCString(final int length) throws IOException {
}
private int readFully(final byte[] b, final int off, final int len) throws IOException {
- final int count = IOUtils.readFully(in, b, off, len);
+ final int count = IOUtils.read(in, b, off, len);
count(count);
if (count < len) {
throw new EOFException();
@@ -470,7 +467,7 @@ private CpioArchiveEntry readNewEntry(final boolean hasCrc) throws IOException {
throw new ArchiveException("Found illegal entry with negative name length");
}
newEntry.setChksum(readAsciiLong(8, 16));
- final String name = readCString(ArchiveException.toIntExact(namesize));
+ final String name = readEntryName(ArchiveException.toIntExact(namesize));
newEntry.setName(name);
if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)) {
throw new ArchiveException(
@@ -504,7 +501,7 @@ private CpioArchiveEntry readOldAsciiEntry() throws IOException {
if (ret.getSize() < 0) {
throw new ArchiveException("Found illegal entry with negative length");
}
- final String name = readCString(ArchiveException.toIntExact(nameSize));
+ final String name = readEntryName(ArchiveException.toIntExact(nameSize));
ret.setName(name);
if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)) {
throw new ArchiveException(
@@ -534,7 +531,7 @@ private CpioArchiveEntry readOldBinaryEntry(final boolean swapHalfWord) throws I
if (oldEntry.getSize() < 0) {
throw new ArchiveException("Found illegal entry with negative length");
}
- final String name = readCString(ArchiveException.toIntExact(nameSize));
+ final String name = readEntryName(ArchiveException.toIntExact(nameSize));
oldEntry.setName(name);
if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)) {
throw new ArchiveException(
@@ -548,7 +545,7 @@ private CpioArchiveEntry readOldBinaryEntry(final boolean swapHalfWord) throws I
}
private byte[] readRange(final int len) throws IOException {
- final byte[] b = IOUtils.readRange(in, len);
+ final byte[] b = org.apache.commons.compress.utils.IOUtils.readRange(in, len);
count(b.length);
if (b.length < len) {
throw new EOFException();
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 e0e7d2361f7..54a1709e6ca 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
@@ -31,6 +31,7 @@
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipEncoding;
import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
+import org.apache.commons.io.IOUtils;
/**
* CpioArchiveOutputStream is a stream for writing CPIO streams. All formats of CPIO are supported (old ASCII, old binary, new portable format and the new
@@ -313,17 +314,17 @@ public void putArchiveEntry(final CpioArchiveEntry entry) throws IOException {
* @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 NullPointerException if b is null
+ * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code b.length}.
* @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 {
- checkOpen();
- if (off < 0 || len < 0 || off > b.length - len) {
- throw new IndexOutOfBoundsException();
- }
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return;
}
+ checkOpen();
if (this.entry == null) {
throw new ArchiveException("No current CPIO entry");
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioConstants.java b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioConstants.java
index 608ef5d4d59..f9c37d8156b 100644
--- a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioConstants.java
+++ b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioConstants.java
@@ -24,7 +24,7 @@
* Based on code from the jRPM project.
*
*
- * A list of the {@code C_xxx} constants is here.
+ * A list of the {@code C_xxx} constants is here.
*
*
* TODO Next major version: Update to a class.
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 be4aba459db..cb4d2fbd916 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
@@ -29,11 +29,14 @@
import java.util.Queue;
import java.util.Stack;
+import org.apache.commons.compress.MemoryLimitException;
+import org.apache.commons.compress.archivers.AbstractArchiveBuilder;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipEncoding;
import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
-import org.apache.commons.compress.utils.IOUtils;
+import org.apache.commons.compress.utils.ArchiveUtils;
+import org.apache.commons.io.IOUtils;
/**
* The DumpArchiveInputStream reads a Unix dump archive as an InputStream. Methods are provided to position at each successive entry in the archive, and the
@@ -60,7 +63,7 @@ public class DumpArchiveInputStream extends ArchiveInputStream
*
* @since 1.29.0
*/
- public static final class Builder extends AbstractBuilder {
+ public static final class Builder extends AbstractArchiveBuilder {
private Builder() {
}
@@ -88,9 +91,9 @@ public static Builder builder() {
* 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
+ * @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?
@@ -110,7 +113,7 @@ public static boolean matches(final byte[] buffer, final int length) {
private final DumpArchiveSummary summary;
private DumpArchiveEntry active;
private boolean isClosed;
- private boolean hasHitEOF;
+ private boolean eof;
private long entrySize;
private long entryOffset;
private int readIdx;
@@ -139,48 +142,30 @@ public static boolean matches(final byte[] buffer, final int length) {
private final ZipEncoding zipEncoding;
private DumpArchiveInputStream(final Builder builder) throws IOException {
- this(builder.getInputStream(), builder);
- }
-
- /**
- * Constructor using the platform's default encoding for file names.
- *
- * @param is stream to read from
- * @throws ArchiveException on error
- */
- public DumpArchiveInputStream(final InputStream is) throws ArchiveException {
- this(is, builder());
- }
-
- private DumpArchiveInputStream(final InputStream is, final Builder builder) throws ArchiveException {
- super(is, builder.getCharset());
- this.raw = new TapeInputStream(is);
- this.hasHitEOF = false;
+ super(builder);
+ this.raw = new TapeInputStream(in);
+ this.eof = false;
this.zipEncoding = ZipEncodingHelper.getZipEncoding(builder.getCharset());
- try {
- // read header, verify it's a dump archive.
- final byte[] headerBytes = raw.readRecord();
+ // read header, verify it's a dump archive.
+ final byte[] headerBytes = raw.readRecord();
- if (!DumpArchiveUtil.verify(headerBytes)) {
- throw new UnrecognizedFormatException();
- }
+ if (!DumpArchiveUtil.verify(headerBytes)) {
+ throw new UnrecognizedFormatException();
+ }
- // get summary information
- summary = new DumpArchiveSummary(headerBytes, this.zipEncoding);
+ // get summary information
+ summary = new DumpArchiveSummary(headerBytes, this.zipEncoding);
- // reset buffer with actual block size.
- raw.resetBlockSize(summary.getNTRec(), summary.isCompressed());
+ // reset buffer with actual block size.
+ raw.resetBlockSize(summary.getNTRec(), summary.isCompressed());
- // allocate our read buffer.
- blockBuffer = new byte[4 * DumpArchiveConstants.TP_SIZE];
+ // allocate our read buffer.
+ blockBuffer = new byte[4 * DumpArchiveConstants.TP_SIZE];
- // skip past CLRI and BITS segments since we don't handle them yet.
- readCLRI();
- readBITS();
- } catch (final IOException e) {
- throw new ArchiveException(e.getMessage(), (Throwable) e);
- }
+ // skip past CLRI and BITS segments since we don't handle them yet.
+ readCLRI();
+ readBITS();
// put in a dummy record for the root node.
final Dirent root = new Dirent(2, 2, 4, CURRENT_PATH_SEGMENT);
@@ -197,18 +182,37 @@ private DumpArchiveInputStream(final InputStream is, final Builder builder) thro
});
}
+ /**
+ * Constructor using the platform's default encoding for file names.
+ *
+ * Since 1.29.0: throws {@link IOException}.
+ *
+ * @param is stream to read from.
+ * @throws IOException on error.
+ */
+ public DumpArchiveInputStream(final InputStream is) throws IOException {
+ this(builder().setInputStream(is));
+ }
+
/**
* Constructs a new instance.
*
- * @param is stream to read from
- * @param encoding the encoding to use for file names, use null for the platform's default encoding
- * @throws ArchiveException on error
+ * Since 1.29.0: throws {@link IOException}.
+ *
+ * @param is stream to read from.
+ * @param encoding the encoding to use for file names, use null for the platform's default encoding.
+ * @throws IOException on error.
* @since 1.6
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public DumpArchiveInputStream(final InputStream is, final String encoding) throws ArchiveException {
- this(is, builder().setCharset(encoding));
+ public DumpArchiveInputStream(final InputStream is, final String encoding) throws IOException {
+ this(builder().setInputStream(is).setCharset(encoding));
+ }
+
+ private DumpArchiveEntry checkEntry(final DumpArchiveEntry entry) throws ArchiveException, MemoryLimitException {
+ ArchiveUtils.checkEntryNameLength(entry.getName().length(), getMaxEntryNameLength(), "DUMP");
+ return entry;
}
/**
@@ -236,8 +240,8 @@ public int getCount() {
/**
* Reads the next entry.
*
- * @return the next entry
- * @throws IOException on error
+ * @return the next entry.
+ * @throws IOException on error.
* @deprecated Use {@link #getNextEntry()}.
*/
@Deprecated
@@ -252,11 +256,11 @@ public DumpArchiveEntry getNextEntry() throws IOException {
// is there anything in the queue?
if (!queue.isEmpty()) {
- return queue.remove();
+ return checkEntry(queue.remove());
}
while (entry == null) {
- if (hasHitEOF) {
+ if (eof) {
return null;
}
@@ -302,7 +306,7 @@ public DumpArchiveEntry getNextEntry() throws IOException {
// check if this is an end-of-volume marker.
if (DumpArchiveConstants.SEGMENT_TYPE.END == active.getHeaderType()) {
- hasHitEOF = true;
+ eof = true;
return null;
}
@@ -335,13 +339,13 @@ public DumpArchiveEntry getNextEntry() throws IOException {
entry.setSimpleName(names.get(entry.getIno()).getName());
entry.setOffset(filepos);
- return entry;
+ return checkEntry(entry);
}
/**
* Gets full path for specified archive entry, or null if there's a gap.
*
- * @param entry
+ * @param entry The entry to query.
* @return full path for specified archive entry, or null if there's a gap.
* @throws DumpArchiveException Infinite loop detected in directory entries.
*/
@@ -383,31 +387,26 @@ private String getPath(final DumpArchiveEntry entry) throws DumpArchiveException
/**
* Gets the archive summary information.
*
- * @return the summary
+ * @return the summary.
*/
public DumpArchiveSummary getSummary() {
return summary;
}
/**
- * Reads bytes from the current dump archive entry.
- *
- * This method is aware of the boundaries of the current entry in the archive and will deal with them as if they were this stream's start and EOF.
+ * {@inheritDoc}
*
- * @param buf The buffer into which to place bytes read.
- * @param off The offset at which to place bytes read.
- * @param len The number of bytes to read.
- * @return The number of bytes read, or -1 at EOF.
- * @throws IOException on error
+ * This method is aware of the boundaries of the current entry in the archive and will deal with them as if they were this stream's start and EOF.
*/
@Override
public int read(final byte[] buf, int off, int len) throws IOException {
+ IOUtils.checkFromIndexSize(buf, off, len);
if (len == 0) {
return 0;
}
int totalRead = 0;
- if (hasHitEOF || isClosed || entryOffset >= entrySize) {
+ if (eof || isClosed || entryOffset >= entrySize) {
return -1;
}
@@ -528,7 +527,7 @@ private void readDirectoryEntry(DumpArchiveEntry entry) throws IOException {
final int datalen = DumpArchiveConstants.TP_SIZE * entry.getHeaderCount();
if (blockBuffer.length < datalen) {
- blockBuffer = IOUtils.readRange(raw, datalen);
+ blockBuffer = org.apache.commons.compress.utils.IOUtils.readRange(raw, datalen);
if (blockBuffer.length != datalen) {
throw new EOFException();
}
@@ -588,5 +587,4 @@ private void readDirectoryEntry(DumpArchiveEntry entry) throws IOException {
size -= DumpArchiveConstants.TP_SIZE;
}
}
-
}
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 3aeb8e8a5e2..ace547a96f7 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
@@ -29,7 +29,7 @@
import java.util.zip.Inflater;
import org.apache.commons.compress.archivers.ArchiveException;
-import org.apache.commons.compress.utils.IOUtils;
+import org.apache.commons.io.IOUtils;
/**
* Filter stream that mimics a physical tape drive capable of compressing the data stream.
@@ -127,10 +127,10 @@ public int read() throws IOException {
*
*
* @param len length to read, must be a multiple of the stream's record size.
- * @throws IOException Thrown if an I/O error occurs.
*/
@Override
public int read(final byte[] b, int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
@@ -245,14 +245,14 @@ private void readBlock(final boolean decompress) throws IOException {
* @throws IOException Thrown if an I/O error occurs.
*/
private void readFully(final byte[] b, final int off, final int len) throws IOException {
- final int count = IOUtils.readFully(in, b, off, len);
+ final int count = IOUtils.read(in, b, off, len);
if (count < len) {
throw new ShortFileException();
}
}
private byte[] readRange(final int len) throws IOException {
- final byte[] ret = IOUtils.readRange(in, len);
+ final byte[] ret = org.apache.commons.compress.utils.IOUtils.readRange(in, len);
if (ret.length < len) {
throw new ShortFileException();
}
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 df06fa3cd07..25ef04f61e4 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
@@ -42,7 +42,6 @@
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
-import org.apache.commons.compress.utils.IOUtils;
/**
* Provides a high level API for creating archives.
@@ -61,7 +60,7 @@ private static class ArchiverFileVisitor, E ext
private ArchiverFileVisitor(final O target, final Path directory, final LinkOption... linkOptions) {
this.outputStream = target;
this.directory = directory;
- this.linkOptions = linkOptions == null ? IOUtils.EMPTY_LINK_OPTIONS : linkOptions.clone();
+ this.linkOptions = linkOptions == null ? org.apache.commons.compress.utils.IOUtils.EMPTY_LINK_OPTIONS : linkOptions.clone();
}
@Override
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/Expander.java b/src/main/java/org/apache/commons/compress/archivers/examples/Expander.java
index e2a1b87c88d..e1e8e2303df 100644
--- a/src/main/java/org/apache/commons/compress/archivers/examples/Expander.java
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/Expander.java
@@ -395,11 +395,11 @@ public void expand(final String format, final SeekableByteChannel archive, final
if (!prefersSeekableByteChannel(format)) {
expand(format, c.track(Channels.newInputStream(archive)), targetDirectory, CloseableConsumer.NULL_CONSUMER);
} else if (ArchiveStreamFactory.TAR.equalsIgnoreCase(format)) {
- expand(c.track(new TarFile(archive)), targetDirectory);
+ expand(c.track(TarFile.builder().setChannel(archive).get()), targetDirectory);
} else if (ArchiveStreamFactory.ZIP.equalsIgnoreCase(format)) {
- expand(c.track(ZipFile.builder().setSeekableByteChannel(archive).get()), targetDirectory);
+ expand(c.track(ZipFile.builder().setChannel(archive).get()), targetDirectory);
} else if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format)) {
- expand(c.track(SevenZFile.builder().setSeekableByteChannel(archive).get()), targetDirectory);
+ expand(c.track(SevenZFile.builder().setChannel(archive).get()), targetDirectory);
} else {
// never reached as prefersSeekableByteChannel only returns true for TAR, ZIP and 7z
throw new ArchiveException("Don't know how to handle format '%s'", format);
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 de2775c7f60..1a3daaf84b8 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
@@ -81,23 +81,27 @@ private JarArchiveInputStream(final Builder builder) throws IOException {
/**
* Creates an instance from the input stream using the default encoding.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the input stream to wrap
*/
- public JarArchiveInputStream(final InputStream inputStream) {
- super(inputStream);
+ public JarArchiveInputStream(final InputStream inputStream) throws IOException {
+ this(jarInputStreamBuilder().setInputStream(inputStream));
}
/**
* Creates an instance from the input stream using the specified encoding.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the input stream to wrap
* @param encoding the encoding to use
* @since 1.10
* @deprecated Since 1.29.0, use {@link #jarInputStreamBuilder()}.
*/
@Deprecated
- public JarArchiveInputStream(final InputStream inputStream, final String encoding) {
- super(inputStream, encoding);
+ public JarArchiveInputStream(final InputStream inputStream, final String encoding) throws IOException {
+ this(jarInputStreamBuilder().setInputStream(inputStream).setCharset(encoding));
}
@Override
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 14a233157e5..8618577a02f 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
@@ -37,6 +37,7 @@
import org.apache.commons.compress.PasswordRequiredException;
import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.io.IOUtils;
final class AES256SHA256Decoder extends AbstractCoder {
@@ -169,6 +170,7 @@ private void flushBuffer() throws IOException {
@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
int gap = len + count > cipherBlockSize ? cipherBlockSize - count : len;
System.arraycopy(b, off, cipherBlockBuffer, count, gap);
count += gap;
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 083364c414e..14076c7847f 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
@@ -23,6 +23,8 @@
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
+import org.apache.commons.io.IOUtils;
+
final class BoundedSeekableByteChannelInputStream extends InputStream {
private static final int MAX_BUF_LEN = 8192;
private final ByteBuffer buffer;
@@ -63,9 +65,18 @@ public int read() throws IOException {
*
* This implementation may return 0 if the underlying {@link SeekableByteChannel} is non-blocking and currently hasn't got any bytes available.
*
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset in array b at which the data is written.
+ * @param len the maximum number of bytes to read.
+ * @return the total number of bytes read into the buffer, or -1 if EOF is reached.
+ * @throws NullPointerException if b is null.
+ * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code b.length}.
+ * @throws IOException if an I/O error occurs.
*/
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
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 fca70b07395..7e368655887 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
@@ -37,13 +37,23 @@ final class Folder {
/**
* Total number of input streams across all coders. This field is currently unused but technically part of the 7z API.
+ *
+ * Currently limited to {@code MAX_CODER_STREAMS_PER_FOLDER}
*/
- long totalInputStreams;
+ int totalInputStreams;
- /** Total number of output streams across all coders. */
- long totalOutputStreams;
+ /**
+ * Total number of output streams across all coders.
+ *
+ * Currently limited to {@code MAX_CODER_STREAMS_PER_FOLDER}
+ */
+ int totalOutputStreams;
- /** Mapping between input and output streams. */
+ /**
+ * Mapping between input and output streams.
+ *
+ * Its size is equal to {@code totalOutputStreams - 1}
+ */
BindPair[] bindPairs;
/** Indices of input streams, one per input stream not listed in bindPairs. */
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 28dfddb274a..11b3cf2d0d3 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
@@ -18,13 +18,8 @@
*/
package org.apache.commons.compress.archivers.sevenz;
-import static java.nio.charset.StandardCharsets.UTF_16LE;
-
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FilterInputStream;
@@ -32,31 +27,26 @@
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
-import java.nio.file.Files;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
-import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.zip.CRC32;
-import java.util.zip.CheckedInputStream;
import org.apache.commons.compress.MemoryLimitException;
+import org.apache.commons.compress.archivers.AbstractArchiveBuilder;
import org.apache.commons.compress.archivers.ArchiveException;
-import org.apache.commons.compress.utils.IOUtils;
+import org.apache.commons.compress.archivers.ArchiveFile;
+import org.apache.commons.compress.utils.ArchiveUtils;
import org.apache.commons.compress.utils.InputStreamStatistics;
-import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
-import org.apache.commons.io.build.AbstractOrigin.ByteArrayOrigin;
-import org.apache.commons.io.build.AbstractStreamBuilder;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.function.IOStream;
import org.apache.commons.io.input.BoundedInputStream;
import org.apache.commons.io.input.ChecksumInputStream;
import org.apache.commons.lang3.ArrayUtils;
@@ -84,7 +74,7 @@
* @NotThreadSafe
* @since 1.6
*/
-public class SevenZFile implements Closeable {
+public class SevenZFile implements ArchiveFile {
private static final class ArchiveStatistics {
private int numberOfPackedStreams;
@@ -105,10 +95,10 @@ private static final class ArchiveStatistics {
*/
void assertValidity(final int maxMemoryLimitKiB) throws IOException {
if (numberOfEntriesWithStream > 0 && numberOfFolders == 0) {
- throw new ArchiveException("Archive with entries but no folders");
+ throw new ArchiveException("7z archive with entries but no folders");
}
if (numberOfEntriesWithStream > numberOfUnpackSubStreams) {
- throw new ArchiveException("Archive doesn't contain enough substreams for entries");
+ throw new ArchiveException("7z archive doesn't contain enough substreams for entries");
}
MemoryLimitException.checkKiB(bytesToKiB(estimateSizeBytes()), maxMemoryLimitKiB);
}
@@ -168,8 +158,8 @@ private long streamMapSize() {
@Override
public String toString() {
- return String.format("Archive with %,d entries in %,d folders, estimated size %,d KiB.", numberOfEntries, numberOfFolders,
- kbToKiB(estimateSizeBytes()));
+ return String.format("7z archive with %,d entries in %,d folders, estimated size %,d KiB.", numberOfEntries, numberOfFolders,
+ bytesToKiB(estimateSizeBytes()));
}
}
@@ -178,14 +168,14 @@ public String toString() {
*
* @since 1.26.0
*/
- public static class Builder extends AbstractStreamBuilder {
+ public static class Builder extends AbstractArchiveBuilder {
static final int MEMORY_LIMIT_KIB = Integer.MAX_VALUE;
static final boolean USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES = false;
static final boolean TRY_TO_RECOVER_BROKEN_ARCHIVES = false;
- private SeekableByteChannel seekableByteChannel;
private String defaultName = DEFAULT_FILE_NAME;
+ private String name;
private byte[] password;
private int maxMemoryLimitKiB = MEMORY_LIMIT_KIB;
private boolean useDefaultNameForUnnamedEntries = USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES;
@@ -199,26 +189,18 @@ public static class Builder extends AbstractStreamBuilder {
@SuppressWarnings("resource") // Caller closes
@Override
public SevenZFile get() throws IOException {
- final SeekableByteChannel actualChannel;
- final String actualDescription;
- if (seekableByteChannel != null) {
- actualChannel = seekableByteChannel;
- actualDescription = defaultName;
- } else if (checkOrigin() instanceof ByteArrayOrigin) {
- actualChannel = new SeekableInMemoryByteChannel(checkOrigin().getByteArray());
- actualDescription = defaultName;
- } else {
- OpenOption[] openOptions = getOpenOptions();
- if (ArrayUtils.isEmpty(openOptions)) {
- openOptions = new OpenOption[] { StandardOpenOption.READ };
+ return new SevenZFile(this);
+ }
+
+ String getName() {
+ if (name == null) {
+ try {
+ name = getPath().toAbsolutePath().toString();
+ } catch (final UnsupportedOperationException e) {
+ name = defaultName;
}
- final Path path = getPath();
- actualChannel = Files.newByteChannel(path, openOptions);
- actualDescription = path.toAbsolutePath().toString();
}
- final boolean closeOnError = seekableByteChannel != null;
- return new SevenZFile(actualChannel, actualDescription, password, closeOnError, maxMemoryLimitKiB, useDefaultNameForUnnamedEntries,
- tryToRecoverBrokenArchives);
+ return name;
}
/**
@@ -242,7 +224,7 @@ public Builder setDefaultName(final String defaultName) {
* @return {@code this} instance.
*/
public Builder setMaxMemoryLimitKb(final int maxMemoryLimitKb) {
- this.maxMemoryLimitKiB = kbToKiB(maxMemoryLimitKb);
+ this.maxMemoryLimitKiB = maxMemoryLimitKb * 1000 / 1024;
return this;
}
@@ -261,6 +243,19 @@ public Builder setMaxMemoryLimitKiB(final int maxMemoryLimitKiB) {
return this;
}
+ Builder setName(final String name) {
+ this.name = name;
+ return this;
+ }
+
+ Builder setOptions(final SevenZFileOptions options) {
+ Objects.requireNonNull(options, "options");
+ this.maxMemoryLimitKiB = options.getMaxMemoryLimitInKb();
+ this.useDefaultNameForUnnamedEntries = options.getUseDefaultNameForUnnamedEntries();
+ this.tryToRecoverBrokenArchives = options.getTryToRecoverBrokenArchives();
+ return this;
+ }
+
/**
* Sets the password.
*
@@ -299,10 +294,11 @@ public Builder setPassword(final String password) {
*
* @param seekableByteChannel the input channel.
* @return {@code this} instance.
+ * @deprecated Since 1.29.0, use {@link #setChannel(java.nio.channels.Channel)}.
*/
+ @Deprecated
public Builder setSeekableByteChannel(final SeekableByteChannel seekableByteChannel) {
- this.seekableByteChannel = seekableByteChannel;
- return this;
+ return setChannel(seekableByteChannel);
}
/**
@@ -342,11 +338,73 @@ public Builder setUseDefaultNameForUnnamedEntries(final boolean useDefaultNameFo
static final byte[] SIGNATURE = { (byte) '7', (byte) 'z', (byte) 0xBC, (byte) 0xAF, (byte) 0x27, (byte) 0x1C };
/**
- * The maximum array size defined privately in {@link ByteArrayOutputStream}.
+ * Maximum number of coders permitted in a single 7z folder.
*
- * @since 1.29.0
+ * This limit is defined by the original 7-Zip implementation
+ * ({@code CPP/7zip/Archive/7z/7zIn.cpp}) to guard against malformed archives:
+ *
+ *
+ * #define k_Scan_NumCoders_MAX 64
+ *
*/
- public static int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
+ private static final int MAX_CODERS_PER_FOLDER = 64;
+
+ /**
+ * Maximum total number of coder input/output streams permitted in a single folder.
+ *
+ * This limit is also taken from the reference implementation
+ * ({@code CPP/7zip/Archive/7z/7zIn.cpp}):
+ *
+ *
+ * #define k_Scan_NumCodersStreams_in_Folder_MAX 64
+ *
+ */
+ private static final int MAX_CODER_STREAMS_PER_FOLDER = 64;
+
+ /** Minimum number of bytes a 7z UINT64 can occupy. */
+ private static final long MIN_UINT64_BYTES = 1L;
+
+ /** Number of bytes a 7z UINT32 occupies. */
+ private static final long UINT32_BYTES = 4L;
+
+ /** Number of bytes a 7z REAL_UINT64 occupies. */
+ private static final long REAL_UINT64_BYTES = 8L;
+
+ /**
+ * Computes a partial count or sum of 7z objects, throwing ArchiveException if any limit is exceeded.
+ *
+ * @param sum current sum
+ * @param y second integer
+ * @param description description of the value being added, for error messages
+ * @return the new sum
+ * @throws ArchiveException if the sum overflows an int
+ */
+ private static int accumulate(final int sum, final int y, final String description) throws ArchiveException {
+ try {
+ return Math.addExact(sum, y);
+ } catch (final ArithmeticException e) {
+ throw new ArchiveException("7z archive: Unsupported, cannot handle more than %,d %s, but %,d present", Integer.MAX_VALUE, description,
+ Long.sum(sum, y));
+ }
+ }
+
+ /**
+ * Computes a partial count or sum of 7z objects, throwing ArchiveException if any limit is exceeded.
+ *
+ * @param sum current sum
+ * @param y second integer
+ * @param description description of the value being added, for error messages
+ * @return the new sum
+ * @throws ArchiveException if the sum overflows an int
+ */
+ private static long accumulate(final long sum, final long y, final String description) throws ArchiveException {
+ try {
+ return Math.addExact(sum, y);
+ } catch (final ArithmeticException e) {
+ throw new ArchiveException("7z archive: Unsupported, cannot handle more than %,d %s, but %,d present", Integer.MAX_VALUE, description,
+ Long.sum(sum, y));
+ }
+ }
/**
* Creates a new Builder.
@@ -362,50 +420,55 @@ static long bytesToKiB(final long bytes) {
return bytes / 1024;
}
- private static ByteBuffer checkEndOfFile(final ByteBuffer buf, final int expectRemaining) throws EOFException {
- final int remaining = buf.remaining();
- if (remaining < expectRemaining) {
- throw new EOFException(String.format("remaining %,d < expectRemaining %,d", remaining, expectRemaining));
+ /**
+ * Checks that there are at least {@code expectRemaining} bytes remaining in the header.
+ *
+ * @param header The buffer containing the 7z header.
+ * @param expectRemaining The number of bytes expected to be remaining.
+ * @return {@code header} for easy chaining.
+ * @throws ArchiveException if there are not enough bytes remaining, implying that the 7z header is incomplete or corrupted.
+ */
+ private static ByteBuffer ensureRemaining(final ByteBuffer header, final long expectRemaining) throws ArchiveException {
+ if (expectRemaining > header.remaining()) {
+ throw new ArchiveException("7z archive: Corrupted, expecting %,d bytes, remaining header size %,d", expectRemaining, header.remaining());
}
- return buf;
- }
-
- private static void get(final ByteBuffer buf, final byte[] to) throws EOFException {
- checkEndOfFile(buf, to.length).get(to);
- }
-
- private static char getChar(final ByteBuffer buf) throws EOFException {
- return checkEndOfFile(buf, Character.BYTES).getChar();
+ return header;
}
- private static int getInt(final ByteBuffer buf) throws EOFException {
- return checkEndOfFile(buf, Integer.BYTES).getInt();
+ private static long crc32(final ByteBuffer header) {
+ final int currentPosition = header.position();
+ final CRC32 crc = new CRC32();
+ crc.update(header);
+ header.position(currentPosition);
+ return crc.getValue();
}
- private static long getLong(final ByteBuffer buf) throws EOFException {
- return checkEndOfFile(buf, Long.BYTES).getLong();
+ /**
+ * Wrapper of {@link ByteBuffer#get(byte[])} that checks remaining bytes first.
+ */
+ private static void get(final ByteBuffer buf, final byte[] to) throws ArchiveException {
+ ensureRemaining(buf, to.length).get(to);
}
/**
- * Gets the next unsigned byte as an int.
- *
- * @param buf the byte source.
- * @return the next unsigned byte as an int.
- * @throws EOFException Thrown if the given buffer doesn't have a remaining byte.
+ * Wrapper of {@link ByteBuffer#getInt()} that checks remaining bytes first.
*/
- private static int getUnsignedByte(final ByteBuffer buf) throws EOFException {
- if (!buf.hasRemaining()) {
- throw new EOFException();
- }
- return buf.get() & 0xff;
+ private static int getInt(final ByteBuffer buf) throws ArchiveException {
+ return ensureRemaining(buf, Integer.BYTES).getInt();
}
- private static int kbToKiB(final int kilobytes) {
- return kilobytes * 1000 / 1024;
+ /**
+ * Wrapper of {@link ByteBuffer#getLong()} that checks remaining bytes first.
+ */
+ private static long getLong(final ByteBuffer buf) throws ArchiveException {
+ return ensureRemaining(buf, Long.BYTES).getLong();
}
- static long kbToKiB(final long kilobytes) {
- return kilobytes * 1000 / 1024;
+ /**
+ * Checks remaining bytes and reads one unsigned byte.
+ */
+ private static int getUnsignedByte(final ByteBuffer header) throws ArchiveException {
+ return Byte.toUnsignedInt(ensureRemaining(header, Byte.BYTES).get());
}
/**
@@ -420,57 +483,123 @@ public static boolean matches(final byte[] buffer, final int ignored) {
return ArrayUtils.startsWith(buffer, SIGNATURE);
}
- private static SeekableByteChannel newByteChannel(final File file) throws IOException {
- return Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ));
+ /**
+ * Reads the size of a header field and validates that it is not larger than the remaining bytes in the header buffer.
+ *
+ * @param header the buffer containing the 7z header.
+ * @return a non-negative int.
+ * @throws ArchiveException if the value is truncated, too large, or exceeds the remaining bytes in the header buffer.
+ */
+ static int readFieldSize(final ByteBuffer header) throws ArchiveException {
+ final long propertySize = readUint64(header);
+ ensureRemaining(header, propertySize);
+ // propertySize is not larger than header.remaining() which is an int
+ return (int) propertySize;
+ }
+
+ /**
+ * Reads a 7z REAL_UINT64 from the header.
+ *
+ * @param header the buffer containing the 7z header.
+ * @return a non-negative long.
+ * @throws ArchiveException if the value is truncated or too large.
+ */
+ static long readRealUint64(final ByteBuffer header) throws IOException {
+ final long value = header.getLong();
+ if (value < 0) {
+ throw new ArchiveException("7z archive: Unsupported, cannot handle integer larger then %d, but was %s", Integer.MAX_VALUE,
+ Long.toUnsignedString(value));
+ }
+ return value;
}
- private static long readUint64(final ByteBuffer in) throws IOException {
+ /**
+ * Reads a 7z UINT32 from the header.
+ *
+ * @param header the buffer containing the 7z header.
+ * @return a non-negative long.
+ * @throws ArchiveException if the value is truncated.
+ */
+ static long readUint32(final ByteBuffer header) throws ArchiveException {
+ return Integer.toUnsignedLong(getInt(header));
+ }
+
+ /**
+ * Reads a 7z UINT64 from the header.
+ *
+ * @param header the buffer containing the 7z header.
+ * @return a non-negative long.
+ * @throws ArchiveException if the value is truncated or too large.
+ */
+ static long readUint64(final ByteBuffer header) throws ArchiveException {
// long rather than int as it might get shifted beyond the range of an int
- final long firstByte = getUnsignedByte(in);
+ final long firstByte = getUnsignedByte(header);
int mask = 0x80;
long value = 0;
for (int i = 0; i < 8; i++) {
if ((firstByte & mask) == 0) {
- return value | (firstByte & mask - 1) << 8 * i;
+ value |= (firstByte & mask - 1) << 8 * i;
+ break;
}
- final long nextByte = getUnsignedByte(in);
+ final long nextByte = getUnsignedByte(header);
value |= nextByte << 8 * i;
mask >>>= 1;
}
+ if (value < 0) {
+ throw new ArchiveException("7z archive: Unsupported, cannot handle integer values larger than %,d", Long.MAX_VALUE);
+ }
return value;
}
- private static int readUint64ToIntExact(final ByteBuffer in) throws IOException {
- return ArchiveException.toIntExact(readUint64(in));
+ /**
+ * Reads a 7z UINT64 from the header.
+ *
+ * If the value is used as the length of a header field, use {@link #readFieldSize} instead, which also validates it against the number of remaining
+ * bytes in the header.
+ *
+ * @param header the buffer containing the 7z header.
+ * @return a non-negative int.
+ * @throws ArchiveException if the value is truncated or too large.
+ * @see #readFieldSize(ByteBuffer)
+ */
+ private static int readUint64ToIntExact(final ByteBuffer header, final String description) throws ArchiveException {
+ final long value = readUint64(header);
+ // Values larger than Integer.MAX_VALUE are not formally forbidden, but we cannot handle them.
+ if (value > Integer.MAX_VALUE) {
+ throw new ArchiveException("7z archive: Unsupported, cannot handle %s larger then %,d, but was %,d", description, Integer.MAX_VALUE, value);
+ }
+ return (int) 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;
+ /**
+ * Skips the given number of bytes of an unsupported property.
+ *
+ * @param header the 7z header buffer.
+ * @param propertySize the number of bytes to skip.
+ * @throws ArchiveException if the property size exceeds the remaining bytes in the header buffer.
+ */
+ private static void skipBytesFully(final ByteBuffer header, final long propertySize) throws ArchiveException {
+ // propertySize is not larger than header.remaining(), which is an int
+ ensureRemaining(header, propertySize).position(header.position() + (int) propertySize);
}
/**
- * Throws IOException if the given value is not in {@code [0, Integer.MAX_VALUE]}.
+ * Throws ArchiveException if the given value is not in {@code [0, Integer.MAX_VALUE]}.
*
- * @param description A description for the IOException.
- * @param value The value to check.
+ * @param description A description for the exception.
+ * @param value The value to check, interpreted as unsigned.
* @return The given value as an int.
- * @throws IOException Thrown if the given value is not in {@code [0, Integer.MAX_VALUE]}.
+ * @throws ArchiveException Thrown if the given value is not in {@code [0, Integer.MAX_VALUE]}.
*/
- public static int toNonNegativeInt(final String description, final long value) throws IOException {
- if (value > Integer.MAX_VALUE || value < 0) {
- throw new ArchiveException("Cannot handle %s %,d", description, value);
+ private static int toNonNegativeInt(final String description, final long value) throws ArchiveException {
+ assert value >= 0 : "value is supposed to be non-negative";
+ if (value > Integer.MAX_VALUE) {
+ throw new ArchiveException("7z archive: Unsupported, cannot handle %s larger then %d, but was %s", description, Integer.MAX_VALUE,
+ Long.toUnsignedString(value));
}
return (int) value;
}
+
private final String fileName;
private SeekableByteChannel channel;
private final Archive archive;
@@ -486,6 +615,30 @@ public static int toNonNegativeInt(final String description, final long value) t
private final boolean tryToRecoverBrokenArchives;
+ private final int maxEntryNameLength;
+
+ private SevenZFile(final Builder builder) throws IOException {
+ this.channel = builder.getChannel(SeekableByteChannel.class);
+ try {
+ this.fileName = builder.getName();
+ this.maxEntryNameLength = builder.getMaxEntryNameLength();
+ this.maxMemoryLimitKiB = builder.maxMemoryLimitKiB;
+ this.useDefaultNameForUnnamedEntries = builder.useDefaultNameForUnnamedEntries;
+ this.tryToRecoverBrokenArchives = builder.tryToRecoverBrokenArchives;
+ final byte[] password = builder.password;
+ archive = readHeaders(password);
+ this.password = password != null ? Arrays.copyOf(password, password.length) : null;
+ } catch (final ArithmeticException | IllegalArgumentException e) {
+ final ArchiveException archiveException = new ArchiveException(e);
+ try {
+ channel.close();
+ } catch (final IOException suppressed) {
+ archiveException.addSuppressed(suppressed);
+ }
+ throw archiveException;
+ }
+ }
+
/**
* Reads a file as unencrypted 7z archive.
*
@@ -495,7 +648,7 @@ public static int toNonNegativeInt(final String description, final long value) t
*/
@Deprecated
public SevenZFile(final File fileName) throws IOException {
- this(fileName, SevenZFileOptions.DEFAULT);
+ this(builder().setFile(fileName));
}
/**
@@ -509,7 +662,7 @@ public SevenZFile(final File fileName) throws IOException {
@SuppressWarnings("resource") // caller closes
@Deprecated
public SevenZFile(final File file, final byte[] password) throws IOException {
- this(newByteChannel(file), file.getAbsolutePath(), password, true, SevenZFileOptions.DEFAULT);
+ this(builder().setFile(file).setPassword(password));
}
/**
@@ -523,7 +676,7 @@ public SevenZFile(final File file, final byte[] password) throws IOException {
*/
@Deprecated
public SevenZFile(final File file, final char[] password) throws IOException {
- this(file, password, SevenZFileOptions.DEFAULT);
+ this(builder().setFile(file).setPassword(password));
}
/**
@@ -539,8 +692,7 @@ public SevenZFile(final File file, final char[] password) throws IOException {
@SuppressWarnings("resource") // caller closes
@Deprecated
public SevenZFile(final File file, final char[] password, final SevenZFileOptions options) throws IOException {
- this(newByteChannel(file), // NOSONAR
- file.getAbsolutePath(), AES256SHA256Decoder.utf16Decode(password), true, options);
+ this(builder().setFile(file).setPassword(password).setOptions(options));
}
/**
@@ -554,7 +706,7 @@ public SevenZFile(final File file, final char[] password, final SevenZFileOption
*/
@Deprecated
public SevenZFile(final File file, final SevenZFileOptions options) throws IOException {
- this(file, null, options);
+ this(builder().setFile(file).setOptions(options));
}
/**
@@ -570,7 +722,7 @@ public SevenZFile(final File file, final SevenZFileOptions options) throws IOExc
*/
@Deprecated
public SevenZFile(final SeekableByteChannel channel) throws IOException {
- this(channel, SevenZFileOptions.DEFAULT);
+ this(builder().setChannel(channel));
}
/**
@@ -587,7 +739,7 @@ public SevenZFile(final SeekableByteChannel channel) throws IOException {
*/
@Deprecated
public SevenZFile(final SeekableByteChannel channel, final byte[] password) throws IOException {
- this(channel, DEFAULT_FILE_NAME, password);
+ this(builder().setChannel(channel).setPassword(password));
}
/**
@@ -604,7 +756,7 @@ public SevenZFile(final SeekableByteChannel channel, final byte[] password) thro
*/
@Deprecated
public SevenZFile(final SeekableByteChannel channel, final char[] password) throws IOException {
- this(channel, password, SevenZFileOptions.DEFAULT);
+ this(builder().setChannel(channel).setPassword(password));
}
/**
@@ -622,7 +774,7 @@ public SevenZFile(final SeekableByteChannel channel, final char[] password) thro
*/
@Deprecated
public SevenZFile(final SeekableByteChannel channel, final char[] password, final SevenZFileOptions options) throws IOException {
- this(channel, DEFAULT_FILE_NAME, password, options);
+ this(builder().setChannel(channel).setPassword(password).setOptions(options));
}
/**
@@ -639,7 +791,7 @@ public SevenZFile(final SeekableByteChannel channel, final char[] password, fina
*/
@Deprecated
public SevenZFile(final SeekableByteChannel channel, final SevenZFileOptions options) throws IOException {
- this(channel, DEFAULT_FILE_NAME, null, options);
+ this(builder().setChannel(channel).setOptions(options));
}
/**
@@ -656,7 +808,7 @@ public SevenZFile(final SeekableByteChannel channel, final SevenZFileOptions opt
*/
@Deprecated
public SevenZFile(final SeekableByteChannel channel, final String fileName) throws IOException {
- this(channel, fileName, SevenZFileOptions.DEFAULT);
+ this(builder().setChannel(channel).setName(fileName));
}
/**
@@ -674,50 +826,7 @@ public SevenZFile(final SeekableByteChannel channel, final String fileName) thro
*/
@Deprecated
public SevenZFile(final SeekableByteChannel channel, final String fileName, final byte[] password) throws IOException {
- this(channel, fileName, password, false, SevenZFileOptions.DEFAULT);
- }
-
- private SevenZFile(final SeekableByteChannel channel, final String fileName, final byte[] password, final boolean closeOnError, final int maxMemoryLimitKiB,
- final boolean useDefaultNameForUnnamedEntries, final boolean tryToRecoverBrokenArchives) throws IOException {
- boolean succeeded = false;
- this.channel = channel;
- this.fileName = fileName;
- this.maxMemoryLimitKiB = maxMemoryLimitKiB;
- this.useDefaultNameForUnnamedEntries = useDefaultNameForUnnamedEntries;
- this.tryToRecoverBrokenArchives = tryToRecoverBrokenArchives;
- try {
- archive = readHeaders(password);
- if (password != null) {
- this.password = Arrays.copyOf(password, password.length);
- } else {
- this.password = null;
- }
- succeeded = true;
- } catch (final ArithmeticException | IllegalArgumentException e) {
- throw new ArchiveException(e);
- } finally {
- if (!succeeded && closeOnError) {
- this.channel.close();
- }
- }
- }
-
- /**
- * Constructs a new instance.
- *
- * @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 closeOnError closes the channel on error.
- * @param options options.
- * @throws IOException if reading the archive fails
- * @deprecated Use {@link Builder#get()}.
- */
- @Deprecated
- private SevenZFile(final SeekableByteChannel channel, final String fileName, final byte[] password, final boolean closeOnError,
- final SevenZFileOptions options) throws IOException {
- this(channel, fileName, password, closeOnError, options.getMaxMemoryLimitInKb(), options.getUseDefaultNameForUnnamedEntries(),
- options.getTryToRecoverBrokenArchives());
+ this(builder().setChannel(channel).setName(fileName).setPassword(password));
}
/**
@@ -735,7 +844,7 @@ private SevenZFile(final SeekableByteChannel channel, final String fileName, fin
*/
@Deprecated
public SevenZFile(final SeekableByteChannel channel, final String fileName, final char[] password) throws IOException {
- this(channel, fileName, password, SevenZFileOptions.DEFAULT);
+ this(builder().setChannel(channel).setName(fileName).setPassword(password));
}
/**
@@ -754,7 +863,7 @@ public SevenZFile(final SeekableByteChannel channel, final String fileName, fina
*/
@Deprecated
public SevenZFile(final SeekableByteChannel channel, final String fileName, final char[] password, final SevenZFileOptions options) throws IOException {
- this(channel, fileName, AES256SHA256Decoder.utf16Decode(password), false, options);
+ this(builder().setChannel(channel).setName(fileName).setPassword(password).setOptions(options));
}
/**
@@ -772,7 +881,7 @@ public SevenZFile(final SeekableByteChannel channel, final String fileName, fina
*/
@Deprecated
public SevenZFile(final SeekableByteChannel channel, final String fileName, final SevenZFileOptions options) throws IOException {
- this(channel, fileName, null, false, options);
+ this(builder().setChannel(channel).setName(fileName).setOptions(options));
}
private InputStream buildDecoderStack(final Folder folder, final long folderOffset, final int firstPackStreamIndex, final SevenZArchiveEntry entry)
@@ -781,7 +890,7 @@ private InputStream buildDecoderStack(final Folder folder, final long folderOffs
InputStream inputStreamStack = new FilterInputStream(
new BufferedInputStream(new BoundedSeekableByteChannelInputStream(channel, archive.packSizes[firstPackStreamIndex]))) {
private void count(final int c) throws ArchiveException {
- compressedBytesReadFromCurrentEntry = ArchiveException.addExact(compressedBytesReadFromCurrentEntry, c);
+ compressedBytesReadFromCurrentEntry = accumulate(compressedBytesReadFromCurrentEntry, c, "compressed bytes read from current entry");
}
@Override
@@ -913,21 +1022,21 @@ private void buildDecodingStream(final int entryIndex, final boolean isRandomAcc
private void calculateStreamMap(final Archive archive) throws IOException {
int nextFolderPackStreamIndex = 0;
- final int numFolders = ArrayUtils.getLength(archive.folders);
- final int[] folderFirstPackStreamIndex = new int[checkIntArray(numFolders)];
+ final int numFolders = archive.folders.length;
+ final int[] folderFirstPackStreamIndex = intArray(numFolders);
for (int i = 0; i < numFolders; i++) {
folderFirstPackStreamIndex[i] = nextFolderPackStreamIndex;
- nextFolderPackStreamIndex = ArchiveException.addExact(nextFolderPackStreamIndex, archive.folders[i].packedStreams.length);
+ nextFolderPackStreamIndex = accumulate(nextFolderPackStreamIndex, archive.folders[i].packedStreams.length, "nextFolderPackStreamIndex");
}
long nextPackStreamOffset = 0;
final int numPackSizes = archive.packSizes.length;
- final long[] packStreamOffsets = new long[checkLongArray(numPackSizes)];
+ final long[] packStreamOffsets = longArray(numPackSizes);
for (int i = 0; i < numPackSizes; i++) {
packStreamOffsets[i] = nextPackStreamOffset;
- nextPackStreamOffset = ArchiveException.addExact(nextPackStreamOffset, archive.packSizes[i]);
+ nextPackStreamOffset = accumulate(nextPackStreamOffset, archive.packSizes[i], "nextPackStreamOffset");
}
- final int[] folderFirstFileIndex = new int[checkIntArray(numFolders)];
- final int[] fileFolderIndex = new int[checkIntArray(archive.files.length)];
+ final int[] folderFirstFileIndex = intArray(numFolders);
+ final int[] fileFolderIndex = intArray(archive.files.length);
int nextFolderIndex = 0;
int nextFolderUnpackStreamIndex = 0;
for (int i = 0; i < archive.files.length; i++) {
@@ -959,26 +1068,6 @@ private void calculateStreamMap(final Archive archive) throws IOException {
archive.streamMap = new StreamMap(folderFirstPackStreamIndex, packStreamOffsets, folderFirstFileIndex, fileFolderIndex);
}
- int checkByteArray(final int size) throws MemoryLimitException {
- MemoryLimitException.checkKiB(bytesToKiB(size * Byte.BYTES), maxMemoryLimitKiB);
- return size;
- }
-
- int checkIntArray(final int size) throws MemoryLimitException {
- MemoryLimitException.checkKiB(bytesToKiB(size * Integer.BYTES), maxMemoryLimitKiB);
- return size;
- }
-
- int checkLongArray(final int size) throws MemoryLimitException {
- MemoryLimitException.checkKiB(bytesToKiB(size * Long.BYTES), maxMemoryLimitKiB);
- return size;
- }
-
- int checkObjectArray(final int size) throws MemoryLimitException {
- MemoryLimitException.checkKiB(bytesToKiB(size * 4), maxMemoryLimitKiB); // assume compressed pointer
- return size;
- }
-
/**
* Closes the archive.
*
@@ -999,8 +1088,8 @@ public void close() throws IOException {
}
}
- private void computeIfAbsent(final Map archiveEntries, final int index) {
- archiveEntries.computeIfAbsent(index, i -> new SevenZArchiveEntry());
+ private SevenZArchiveEntry computeIfAbsent(final Map archiveEntries, final int index) {
+ return archiveEntries.computeIfAbsent(index, i -> new SevenZArchiveEntry());
}
private InputStream getCurrentStream() throws IOException {
@@ -1015,7 +1104,7 @@ private InputStream getCurrentStream() throws IOException {
// 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 (InputStream stream = deferredBlockStreams.remove(0)) {
- org.apache.commons.io.IOUtils.skip(stream, Long.MAX_VALUE, org.apache.commons.io.IOUtils::byteArray);
+ IOUtils.consume(stream);
}
compressedBytesReadFromCurrentEntry = 0;
}
@@ -1057,7 +1146,9 @@ public String getDefaultName() {
*
* @return a copy of meta-data of all archive entries.
* @since 1.11
+ * @deprecated Since 1.29.0, use {@link #entries()} or {@link #stream()}.
*/
+ @Deprecated
public Iterable getEntries() {
return new ArrayList<>(Arrays.asList(archive.files));
}
@@ -1073,6 +1164,7 @@ public Iterable getEntries() {
* @throws IOException if unable to create an input stream from the entry
* @since 1.20
*/
+ @Override
public InputStream getInputStream(final SevenZArchiveEntry entry) throws IOException {
int entryIndex = -1;
for (int i = 0; i < archive.files.length; i++) {
@@ -1153,43 +1245,75 @@ private boolean hasCurrentEntryBeenRead() {
}
private Archive initializeArchive(final StartHeader startHeader, final byte[] password, final boolean verifyCrc) throws IOException {
- final int nextHeaderSizeInt = toNonNegativeInt("startHeader.nextHeaderSize", startHeader.nextHeaderSize);
- MemoryLimitException.checkKiB(bytesToKiB(nextHeaderSizeInt), Math.min(bytesToKiB(SOFT_MAX_ARRAY_LENGTH), maxMemoryLimitKiB));
- 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 ArchiveException("Problem computing NextHeader CRC-32");
- }
- if (startHeader.nextHeaderCrc != cis.getChecksum().getValue()) {
- throw new ArchiveException("NextHeader CRC-32 mismatch");
- }
- channel.position(position);
- }
Archive archive = new Archive();
- ByteBuffer buf = ByteBuffer.allocate(nextHeaderSizeInt).order(ByteOrder.LITTLE_ENDIAN);
- readFully(buf);
- int nid = getUnsignedByte(buf);
+ ByteBuffer header = mapNextHeader(startHeader);
+ if (verifyCrc && startHeader.nextHeaderCrc != crc32(header)) {
+ throw new ArchiveException("Corrupted 7z archive: CRC error in next header");
+ }
+ int nid = getUnsignedByte(header);
if (nid == NID.kEncodedHeader) {
- buf = readEncodedHeader(buf, archive, password);
+ header = readEncodedHeader(header, archive, password);
// Archive gets rebuilt with the new header
archive = new Archive();
- nid = getUnsignedByte(buf);
+ nid = getUnsignedByte(header);
}
if (nid != NID.kHeader) {
- throw new ArchiveException("Broken or unsupported archive: no Header");
+ throw new ArchiveException("7z archive: Broken or unsupported, no Header");
}
- readHeader(buf, archive);
+ readHeader(header, archive);
archive.subStreamsInfo = null;
return archive;
}
+ /**
+ * Creates an int array while checking memory limits.
+ *
+ * @param size the size of the array
+ * @return the int array
+ * @throws MemoryLimitException if memory limit is exceeded
+ */
+ private int[] intArray(final int size) throws MemoryLimitException {
+ MemoryLimitException.checkKiB(bytesToKiB((long) size * Integer.BYTES), maxMemoryLimitKiB);
+ return new int[size];
+ }
+
+ /**
+ * Creates a long array while checking memory limits.
+ *
+ * @param size the size of the array
+ * @return the long array
+ * @throws MemoryLimitException if memory limit is exceeded
+ */
+ private long[] longArray(final int size) throws MemoryLimitException {
+ MemoryLimitException.checkKiB(bytesToKiB((long) size * Long.BYTES), maxMemoryLimitKiB);
+ return new long[size];
+ }
+
+ /**
+ * Maps the next header into memory.
+ *
+ * @param startHeader the start header
+ * @return the mapped ByteBuffer
+ * @throws IOException if an I/O error occurs
+ */
+ private ByteBuffer mapNextHeader(final StartHeader startHeader) throws IOException {
+ MemoryLimitException.checkKiB(bytesToKiB(startHeader.nextHeaderSize), Math.min(bytesToKiB(IOUtils.SOFT_MAX_ARRAY_LENGTH), maxMemoryLimitKiB));
+ // startHeader is already within the channel's bounds
+ if (channel instanceof FileChannel) {
+ final FileChannel fileChannel = (FileChannel) channel;
+ return fileChannel.map(FileChannel.MapMode.READ_ONLY, startHeader.position(), startHeader.nextHeaderSize).order(ByteOrder.LITTLE_ENDIAN);
+ }
+ channel.position(startHeader.position());
+ final ByteBuffer buf = ByteBuffer.allocate(startHeader.nextHeaderSize).order(ByteOrder.LITTLE_ENDIAN);
+ readFully(buf, "next header");
+ return buf;
+ }
+
/**
* 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
+ * @throws IOException if an I/O error has occurred.
*/
public int read() throws IOException {
@SuppressWarnings("resource") // does not allocate
@@ -1205,7 +1329,7 @@ public int read() throws IOException {
*
* @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
+ * @throws IOException if an I/O error has occurred.
*/
public int read(final byte[] b) throws IOException {
return read(b, 0, b.length);
@@ -1218,7 +1342,7 @@ public int read(final byte[] b) throws IOException {
* @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
+ * @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) {
@@ -1227,7 +1351,7 @@ public int read(final byte[] b, final int off, final int len) throws IOException
@SuppressWarnings("resource") // does not allocate
final int current = getCurrentStream().read(b, off, len);
if (current > 0) {
- uncompressedBytesReadFromCurrentEntry = ArchiveException.addExact(uncompressedBytesReadFromCurrentEntry, current);
+ uncompressedBytesReadFromCurrentEntry = accumulate(uncompressedBytesReadFromCurrentEntry, current, "uncompressedBytesReadFromCurrentEntry");
}
return current;
}
@@ -1246,18 +1370,19 @@ private BitSet readAllOrBits(final ByteBuffer header, final int size) throws IOE
return bits;
}
- private void readArchiveProperties(final ByteBuffer input) throws IOException {
+ private void readArchiveProperties(final ByteBuffer header) throws IOException {
// FIXME: the reference implementation just throws them away?
- long nid = readUint64(input);
+ long nid = readUint64(header);
while (nid != NID.kEnd) {
- final int propertySize = readUint64ToIntExact(input);
- final byte[] property = new byte[checkByteArray(propertySize)];
- get(input, property);
- nid = readUint64(input);
+ // We validate the size but ignore the value
+ final int propertySize = readFieldSize(header);
+ skipBytesFully(header, propertySize);
+ nid = readUint64(header);
}
}
private BitSet readBits(final ByteBuffer header, final int size) throws IOException {
+ ensureRemaining(header, (size + 7) / 8);
final BitSet bits = new BitSet(size);
int mask = 0;
int cache = 0;
@@ -1288,7 +1413,7 @@ private ByteBuffer readEncodedHeader(final ByteBuffer header, final Archive arch
// 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;
+ final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos;
channel.position(folderOffset);
InputStream inputStreamStack = new BoundedSeekableByteChannelInputStream(channel, archive.packSizes[firstPackStreamIndex]);
for (final Coder coder : folder.getOrderedCoders()) {
@@ -1308,8 +1433,8 @@ private ByteBuffer readEncodedHeader(final ByteBuffer header, final Archive arch
.get();
// @formatter:on
}
- final int unpackSize = toNonNegativeInt("unpackSize", folder.getUnpackSize());
- final byte[] nextHeader = IOUtils.readRange(inputStreamStack, unpackSize);
+ final int unpackSize = toNonNegativeInt("header", folder.getUnpackSize());
+ final byte[] nextHeader = org.apache.commons.compress.utils.IOUtils.readRange(inputStreamStack, unpackSize);
if (nextHeader.length < unpackSize) {
throw new ArchiveException("Premature end of stream");
}
@@ -1318,17 +1443,20 @@ private ByteBuffer readEncodedHeader(final ByteBuffer header, final Archive arch
}
private void readFilesInfo(final ByteBuffer header, final Archive archive) throws IOException {
- final int numFilesInt = readUint64ToIntExact(header);
+ final int numFilesInt = readUint64ToIntExact(header, "numFiles");
final Map fileMap = new LinkedHashMap<>();
BitSet isEmptyStream = null;
BitSet isEmptyFile = null;
BitSet isAnti = null;
+ final int originalLimit = header.limit();
while (true) {
final int propertyType = getUnsignedByte(header);
- if (propertyType == 0) {
+ if (propertyType == NID.kEnd) {
break;
}
- final long size = readUint64(header);
+ final int size = readFieldSize(header);
+ // Limit the buffer to the size of the property, so we don't read beyond it
+ header.limit(header.position() + size);
switch (propertyType) {
case NID.kEmptyStream: {
isEmptyStream = readBits(header, numFilesInt);
@@ -1344,21 +1472,22 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw
}
case NID.kName: {
/* final int external = */ getUnsignedByte(header);
- MemoryLimitException.checkKiB(bytesToKiB(size - 1), maxMemoryLimitKiB);
- final byte[] names = new byte[checkByteArray(ArchiveException.toIntExact(size - 1))];
- final int namesLength = names.length;
- get(header, names);
+ final StringBuilder entryName = new StringBuilder();
int nextFile = 0;
- int nextName = 0;
- for (int i = 0; i < namesLength; i += 2) {
- if (names[i] == 0 && names[i + 1] == 0) {
- computeIfAbsent(fileMap, nextFile);
- fileMap.get(nextFile).setName(new String(names, nextName, i - nextName, UTF_16LE));
- nextName = i + 2;
+ while (header.remaining() > 0) {
+ final char c = header.getChar();
+ if (c == 0) {
+ // Entry name length in UTF-16LE characters (not bytes)
+ // as it might be surprising to users for ASCII characters to take 2 bytes each.
+ ArchiveUtils.checkEntryNameLength(entryName.length(), maxEntryNameLength, "7z");
+ computeIfAbsent(fileMap, nextFile).setName(entryName.toString());
+ entryName.setLength(0);
nextFile++;
+ } else {
+ entryName.append(c);
}
}
- if (nextName != namesLength || nextFile != numFilesInt) {
+ if (entryName.length() != 0 || nextFile != numFilesInt) {
throw new ArchiveException("Error parsing file names");
}
break;
@@ -1427,6 +1556,12 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw
break;
}
}
+ // We should have consumed all the bytes by now
+ if (header.remaining() > 0) {
+ throw new ArchiveException("7z archive: Unsupported, property 0x%02d has %d trailing bytes.", propertyType, header.remaining());
+ }
+ // Restore original limit
+ header.limit(originalLimit);
}
int nonEmptyFileCounter = 0;
int emptyFileCounter = 0;
@@ -1438,16 +1573,13 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw
entryAtIndex.setHasStream(isEmptyStream == null || !isEmptyStream.get(i));
if (entryAtIndex.hasStream()) {
if (archive.subStreamsInfo == null) {
- throw new ArchiveException("Archive contains file with streams but no subStreamsInfo");
+ throw new ArchiveException("7z archive: Archive contains file with streams but no subStreamsInfo.");
}
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 ArchiveException("Broken archive, entry with negative size");
- }
++nonEmptyFileCounter;
} else {
entryAtIndex.setDirectory(isEmptyFile == null || !isEmptyFile.get(emptyFileCounter));
@@ -1461,20 +1593,22 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw
calculateStreamMap(archive);
}
- private Folder readFolder(final ByteBuffer header) throws IOException {
+ Folder readFolder(final ByteBuffer header) throws IOException {
final Folder folder = new Folder();
- final int numCoders = readUint64ToIntExact(header);
- final Coder[] coders = new Coder[checkObjectArray(numCoders)];
- long totalInStreams = 0;
- long totalOutStreams = 0;
+ final long numCoders = readUint64(header);
+ if (numCoders == 0 || numCoders > MAX_CODERS_PER_FOLDER) {
+ throw new ArchiveException("7z archive: Unsupported, " + numCoders + " coders in folder.");
+ }
+ final Coder[] coders = new Coder[(int) numCoders];
+ int totalInStreams = 0;
+ int totalOutStreams = 0;
for (int i = 0; i < coders.length; i++) {
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;
- final byte[] decompressionMethodId = new byte[idSize];
- get(header, decompressionMethodId);
+ final byte[] decompressionMethodId = toByteArray(header, idSize);
final long numInStreams;
final long numOutStreams;
if (isSimple) {
@@ -1482,34 +1616,50 @@ private Folder readFolder(final ByteBuffer header) throws IOException {
numOutStreams = 1;
} else {
numInStreams = readUint64(header);
+ if (numInStreams > MAX_CODER_STREAMS_PER_FOLDER) {
+ throw new ArchiveException("7z archive: Unsupported, %,d coder input streams in folder.", numInStreams);
+ }
numOutStreams = readUint64(header);
+ if (numOutStreams != 1) {
+ throw new ArchiveException("7z archive: Unsupported, %,d coder output streams in folder.", numOutStreams);
+ }
+ }
+ totalInStreams += (int) numInStreams;
+ if (totalInStreams > MAX_CODER_STREAMS_PER_FOLDER) {
+ throw new ArchiveException("7z archive: Unsupported, %,d coder input streams in folder.", totalInStreams);
}
- totalInStreams = ArchiveException.addExact(totalInStreams, numInStreams);
- totalOutStreams = ArchiveException.addExact(totalOutStreams, numOutStreams);
+ totalOutStreams += (int) numOutStreams;
byte[] properties = null;
if (hasAttributes) {
- final long propertiesSize = readUint64(header);
- properties = new byte[checkByteArray(ArchiveException.toIntExact(propertiesSize))];
- get(header, properties);
+ final int propertiesSize = readFieldSize(header);
+ properties = toByteArray(header, propertiesSize);
}
// would need to keep looping as above:
if (moreAlternativeMethods) {
- throw new ArchiveException("Alternative methods are unsupported, please report. The reference implementation doesn't support them either.");
+ throw new ArchiveException("7z archive: Unsupported, Alternative methods are unsupported, please report. "
+ + "The reference implementation doesn't support them either.");
}
coders[i] = new Coder(decompressionMethodId, numInStreams, numOutStreams, properties);
}
folder.coders = coders;
folder.totalInputStreams = totalInStreams;
folder.totalOutputStreams = totalOutStreams;
- final long numBindPairs = totalOutStreams - 1;
- final BindPair[] bindPairs = new BindPair[checkObjectArray(ArchiveException.toIntExact(numBindPairs))];
+ final int numBindPairs = totalOutStreams - 1;
+ final BindPair[] bindPairs = new BindPair[numBindPairs];
for (int i = 0; i < bindPairs.length; i++) {
- bindPairs[i] = new BindPair(readUint64(header), readUint64(header));
+ final long inIndex = readUint64(header);
+ if (inIndex >= totalInStreams) {
+ throw new ArchiveException("7z archive: Unsupported, Bind pair inIndex %d out of range.", inIndex);
+ }
+ final long outIndex = readUint64(header);
+ if (outIndex >= totalOutStreams) {
+ throw new ArchiveException("7z archive: Unsupported, Bind pair outIndex %d out of range.", inIndex);
+ }
+ bindPairs[i] = new BindPair(inIndex, outIndex);
}
folder.bindPairs = bindPairs;
- final long numPackedStreams = totalInStreams - numBindPairs;
- final int numPackedStreamsInt = ArchiveException.toIntExact(numPackedStreams);
- final long[] packedStreams = new long[checkObjectArray(numPackedStreamsInt)];
+ final int numPackedStreams = totalInStreams - numBindPairs;
+ final long[] packedStreams = new long[numPackedStreams];
if (numPackedStreams == 1) {
long i;
for (i = 0; i < totalInStreams; i++) {
@@ -1519,18 +1669,24 @@ private Folder readFolder(final ByteBuffer header) throws IOException {
}
packedStreams[0] = i;
} else {
- for (int i = 0; i < numPackedStreamsInt; i++) {
+ for (int i = 0; i < numPackedStreams; i++) {
packedStreams[i] = readUint64(header);
+ if (packedStreams[i] >= totalInStreams) {
+ throw new ArchiveException("7z archive: Unsupported, Packed stream index %d out of range.", packedStreams[i]);
+ }
}
}
folder.packedStreams = packedStreams;
return folder;
}
- private void readFully(final ByteBuffer buf) throws IOException {
- buf.rewind();
- IOUtils.readFully(channel, buf);
- buf.flip();
+ private void readFully(final ByteBuffer buf, final String description) throws IOException {
+ try {
+ IOUtils.readFully(channel, buf);
+ buf.flip();
+ } catch (final EOFException e) {
+ throw new ArchiveException("Truncated 7z archive: end of file reached while reading %s.", description);
+ }
}
private void readHeader(final ByteBuffer header, final Archive archive) throws IOException {
@@ -1544,7 +1700,7 @@ private void readHeader(final ByteBuffer header, final Archive archive) throws I
nid = getUnsignedByte(header);
}
if (nid == NID.kAdditionalStreamsInfo) {
- throw new ArchiveException("Additional streams unsupported");
+ throw new ArchiveException("7z archive: Additional streams unsupported");
// nid = getUnsignedByte(header);
}
if (nid == NID.kMainStreamsInfo) {
@@ -1558,54 +1714,39 @@ private void readHeader(final ByteBuffer header, final Archive archive) throws I
}
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);
+ final ByteBuffer startHeader = ByteBuffer.allocate(SIGNATURE_HEADER_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+ readFully(startHeader, "signature header");
+ final byte[] signature = new byte[SIGNATURE.length];
+ startHeader.get(signature);
if (!Arrays.equals(signature, SIGNATURE)) {
throw new ArchiveException("Bad 7z signature");
}
// 7zFormat.txt has it wrong - it's first major then minor
- final byte archiveVersionMajor = buf.get();
- final byte archiveVersionMinor = buf.get();
+ final byte archiveVersionMajor = startHeader.get();
+ final byte archiveVersionMinor = startHeader.get();
if (archiveVersionMajor != 0) {
- throw new ArchiveException("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 {
- headerLooksValid = true;
+ throw new ArchiveException("7z archive: Unsupported 7z version (%d,%d)", archiveVersionMajor, archiveVersionMinor);
}
- if (headerLooksValid) {
- return initializeArchive(readStartHeader(startHeaderCrc), password, true);
+ final long startHeaderCrc = readUint32(startHeader);
+ if (startHeaderCrc == crc32(startHeader)) {
+ return initializeArchive(readStartHeader(startHeader), password, true);
}
+ // See https://www.7-zip.org/recover.html - "There is no correct End Header at the end of archive"
// No valid header found - probably first file of multipart archive was removed too early. Scan for end header.
if (tryToRecoverBrokenArchives) {
return tryToLocateEndHeader(password);
}
- throw new ArchiveException("Archive seems to be invalid. You may want to retry and enable the tryToRecoverBrokenArchives if "
+ throw new ArchiveException("7z archive seems to be invalid. You may want to retry and enable the tryToRecoverBrokenArchives if "
+ "the archive could be a multi volume archive that has been closed prematurely.");
}
private void readPackInfo(final ByteBuffer header, final Archive archive) throws IOException {
archive.packPos = readUint64(header);
- final int numPackStreamsInt = readUint64ToIntExact(header);
+ final int numPackStreamsInt = readUint64ToIntExact(header, "numPackStreams");
int nid = getUnsignedByte(header);
if (nid == NID.kSize) {
- archive.packSizes = new long[checkLongArray(numPackStreamsInt)];
+ ensureRemaining(header, MIN_UINT64_BYTES * numPackStreamsInt);
+ archive.packSizes = longArray(numPackStreamsInt);
for (int i = 0; i < archive.packSizes.length; i++) {
archive.packSizes[i] = readUint64(header);
}
@@ -1613,10 +1754,11 @@ private void readPackInfo(final ByteBuffer header, final Archive archive) throws
}
if (nid == NID.kCRC) {
archive.packCrcsDefined = readAllOrBits(header, numPackStreamsInt);
- archive.packCrcs = new long[checkLongArray(numPackStreamsInt)];
+ ensureRemaining(header, UINT32_BYTES * archive.packCrcsDefined.cardinality());
+ archive.packCrcs = longArray(numPackStreamsInt);
for (int i = 0; i < numPackStreamsInt; i++) {
if (archive.packCrcsDefined.get(i)) {
- archive.packCrcs[i] = 0xffffFFFFL & getInt(header);
+ archive.packCrcs[i] = readUint32(header);
}
}
// read one more
@@ -1624,28 +1766,19 @@ private void readPackInfo(final ByteBuffer header, final Archive archive) throws
}
}
- private StartHeader readStartHeader(final long startHeaderCrc) throws IOException {
- // using Stream rather than ByteBuffer for the benefit of the built-in CRC check
- try (DataInputStream dataInputStream = new DataInputStream(ChecksumInputStream.builder()
- // @formatter:off
- .setChecksum(new CRC32())
- .setInputStream(new BoundedSeekableByteChannelInputStream(channel, 20))
- .setCountThreshold(20L)
- .setExpectedChecksumValue(startHeaderCrc)
- .get())) {
- // @formatter:on
- final long nextHeaderOffset = Long.reverseBytes(dataInputStream.readLong());
- if (nextHeaderOffset < 0 || nextHeaderOffset + SIGNATURE_HEADER_SIZE > channel.size()) {
- throw new ArchiveException("nextHeaderOffset is out of bounds");
- }
- final long nextHeaderSize = Long.reverseBytes(dataInputStream.readLong());
- final long nextHeaderEnd = nextHeaderOffset + nextHeaderSize;
- if (nextHeaderEnd < nextHeaderOffset || nextHeaderEnd + SIGNATURE_HEADER_SIZE > channel.size()) {
- throw new ArchiveException("nextHeaderSize is out of bounds");
- }
- final long nextHeaderCrc = 0xffffFFFFL & Integer.reverseBytes(dataInputStream.readInt());
- return new StartHeader(nextHeaderOffset, nextHeaderSize, nextHeaderCrc);
+ private StartHeader readStartHeader(final ByteBuffer startHeader) throws IOException {
+ final long nextHeaderOffset = readRealUint64(startHeader);
+ if (nextHeaderOffset > channel.size() - SIGNATURE_HEADER_SIZE) {
+ throw new ArchiveException("Truncated 7z archive: next header offset %,d exceeds file size (%,d bytes).",
+ nextHeaderOffset + SIGNATURE_HEADER_SIZE, channel.size());
}
+ final int nextHeaderSize = toNonNegativeInt("header", readRealUint64(startHeader));
+ if (nextHeaderSize > channel.size() - SIGNATURE_HEADER_SIZE - nextHeaderOffset) {
+ throw new ArchiveException("Truncated 7z archive: next header size %,d at offset %,d exceeds file size (%,d bytes).", nextHeaderSize,
+ nextHeaderOffset + SIGNATURE_HEADER_SIZE, channel.size());
+ }
+ final long nextHeaderCrc = readUint32(startHeader);
+ return new StartHeader(nextHeaderOffset, nextHeaderSize, nextHeaderCrc);
}
private void readStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException {
@@ -1671,14 +1804,13 @@ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive)
for (final Folder folder : archive.folders) {
folder.numUnpackSubStreams = 1;
}
- long unpackStreamsCount = archive.folders.length;
+ int 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 = ArchiveException.addExact(unpackStreamsCount, numStreams);
+ folder.numUnpackSubStreams = readUint64ToIntExact(header, "numUnpackSubStreams");
+ unpackStreamsCount = accumulate(unpackStreamsCount, folder.numUnpackSubStreams, "numUnpackStreams");
}
nid = getUnsignedByte(header);
}
@@ -1688,18 +1820,19 @@ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive)
if (folder.numUnpackSubStreams == 0) {
continue;
}
- long sum = 0;
+ long totalUnpackSize = 0;
if (nid == NID.kSize) {
+ ensureRemaining(header, MIN_UINT64_BYTES * (folder.numUnpackSubStreams - 1));
for (int i = 0; i < folder.numUnpackSubStreams - 1; i++) {
final long size = readUint64(header);
subStreamsInfo.unpackSizes[nextUnpackStream++] = size;
- sum = ArchiveException.addExact(sum, size);
+ totalUnpackSize = accumulate(totalUnpackSize, size, "unpackSize");
}
}
- if (sum > folder.getUnpackSize()) {
- throw new ArchiveException("Sum of unpack sizes of folder exceeds total unpack size");
+ if (totalUnpackSize > folder.getUnpackSize()) {
+ throw new ArchiveException("7z archive: Sum of unpack sizes of folder exceeds total unpack size");
}
- subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum;
+ subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - totalUnpackSize;
}
if (nid == NID.kSize) {
nid = getUnsignedByte(header);
@@ -1707,15 +1840,16 @@ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive)
int numDigests = 0;
for (final Folder folder : archive.folders) {
if (folder.numUnpackSubStreams != 1 || !folder.hasCrc) {
- numDigests = ArchiveException.addExact(numDigests, folder.numUnpackSubStreams);
+ numDigests = accumulate(numDigests, folder.numUnpackSubStreams, "numDigests");
}
}
if (nid == NID.kCRC) {
final BitSet hasMissingCrc = readAllOrBits(header, numDigests);
- final long[] missingCrcs = new long[checkLongArray(numDigests)];
+ ensureRemaining(header, UINT32_BYTES * hasMissingCrc.cardinality());
+ final long[] missingCrcs = longArray(numDigests);
for (int i = 0; i < numDigests; i++) {
if (hasMissingCrc.get(i)) {
- missingCrcs[i] = 0xffffFFFFL & getInt(header);
+ missingCrcs[i] = readUint32(header);
}
}
int nextCrc = 0;
@@ -1741,16 +1875,23 @@ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive)
private void readUnpackInfo(final ByteBuffer header, final Archive archive) throws IOException {
int nid = getUnsignedByte(header);
- final int numFoldersInt = readUint64ToIntExact(header);
- final Folder[] folders = new Folder[checkObjectArray(numFoldersInt)];
- archive.folders = folders;
+ final int numFoldersInt = readUint64ToIntExact(header, "numFolders");
/* final int external = */ getUnsignedByte(header);
+ // Verify available header bytes and memory limit before allocating array
+ // A folder requires at least 3 bytes: the number of coders (1 byte), the flag byte for the coder (1 byte),
+ // and at least 1 byte for the method id (1 byte)
+ ensureRemaining(header, 3L * numFoldersInt);
+ // Assumes compressed pointer
+ MemoryLimitException.checkKiB(bytesToKiB(numFoldersInt * 4L), maxMemoryLimitKiB);
+ final Folder[] folders = new Folder[numFoldersInt];
+ archive.folders = folders;
for (int i = 0; i < numFoldersInt; i++) {
folders[i] = readFolder(header);
}
nid = getUnsignedByte(header);
for (final Folder folder : folders) {
- folder.unpackSizes = new long[checkLongArray(toNonNegativeInt("totalOutputStreams", folder.totalOutputStreams))];
+ ensureRemaining(header, folder.totalOutputStreams);
+ folder.unpackSizes = longArray(folder.totalOutputStreams);
for (int i = 0; i < folder.totalOutputStreams; i++) {
folder.unpackSizes[i] = readUint64(header);
}
@@ -1758,10 +1899,11 @@ private void readUnpackInfo(final ByteBuffer header, final Archive archive) thro
nid = getUnsignedByte(header);
if (nid == NID.kCRC) {
final BitSet crcsDefined = readAllOrBits(header, numFoldersInt);
+ ensureRemaining(header, UINT32_BYTES * crcsDefined.cardinality());
for (int i = 0; i < numFoldersInt; i++) {
if (crcsDefined.get(i)) {
folders[i].hasCrc = true;
- folders[i].crc = 0xffffFFFFL & getInt(header);
+ folders[i].crc = readUint32(header);
} else {
folders[i].hasCrc = false;
}
@@ -1797,7 +1939,7 @@ private ArchiveStatistics sanityCheckAndCollectStatistics(final ByteBuffer heade
nid = getUnsignedByte(header);
}
if (nid == NID.kAdditionalStreamsInfo) {
- throw new ArchiveException("Additional streams unsupported");
+ throw new ArchiveException("7z archive: Additional streams unsupported");
// nid = getUnsignedByte(header);
}
if (nid == NID.kMainStreamsInfo) {
@@ -1809,7 +1951,7 @@ private ArchiveStatistics sanityCheckAndCollectStatistics(final ByteBuffer heade
nid = getUnsignedByte(header);
}
if (nid != NID.kEnd) {
- throw new ArchiveException("Badly terminated header, found %s", nid);
+ throw new ArchiveException("7z archive: Badly terminated header, found %s", nid);
}
return stats;
}
@@ -1817,23 +1959,25 @@ private ArchiveStatistics sanityCheckAndCollectStatistics(final ByteBuffer heade
private void sanityCheckArchiveProperties(final ByteBuffer header) throws IOException {
long nid = readUint64(header);
while (nid != NID.kEnd) {
- final int propertySize = toNonNegativeInt("propertySize", readUint64(header));
- if (skipBytesFully(header, propertySize) < propertySize) {
- throw new ArchiveException("Invalid property size");
- }
+ // We validate the size but ignore the value
+ final int propertySize = readFieldSize(header);
+ skipBytesFully(header, propertySize);
nid = readUint64(header);
}
}
private void sanityCheckFilesInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
- stats.numberOfEntries = toNonNegativeInt("numFiles", readUint64(header));
+ stats.numberOfEntries = readUint64ToIntExact(header, "numFiles");
int emptyStreams = -1;
+ final int originalLimit = header.limit();
while (true) {
final int propertyType = getUnsignedByte(header);
- if (propertyType == 0) {
+ if (propertyType == NID.kEnd) {
break;
}
- final long size = readUint64(header);
+ final int size = readFieldSize(header);
+ // Limit the buffer to the size of the property
+ header.limit(header.position() + size);
switch (propertyType) {
case NID.kEmptyStream: {
emptyStreams = readBits(header, stats.numberOfEntries).cardinality();
@@ -1841,171 +1985,140 @@ private void sanityCheckFilesInfo(final ByteBuffer header, final ArchiveStatisti
}
case NID.kEmptyFile: {
if (emptyStreams == -1) {
- throw new ArchiveException("Header format error: kEmptyStream must appear before kEmptyFile");
+ throw new ArchiveException("7z archive: Header format error: kEmptyStream must appear before kEmptyFile");
}
- readBits(header, emptyStreams);
+ skipBytesFully(header, size);
break;
}
case NID.kAnti: {
if (emptyStreams == -1) {
- throw new ArchiveException("Header format error: kEmptyStream must appear before kAnti");
+ throw new ArchiveException("7z archive: Header format error: kEmptyStream must appear before kAnti");
}
- readBits(header, emptyStreams);
+ skipBytesFully(header, size);
break;
}
case NID.kName: {
+ // 1 byte for external and sequence of zero-terminated UTF-16 strings.
+ if (size % 2 != 1) {
+ throw new ArchiveException("7z archive: File names length invalid");
+ }
final int external = getUnsignedByte(header);
if (external != 0) {
- throw new ArchiveException("Not implemented");
- }
- final int namesLength = toNonNegativeInt("file names length", size - 1);
- if ((namesLength & 1) != 0) {
- throw new ArchiveException("File names length invalid");
+ throw new ArchiveException("7z archive: Not implemented");
}
int filesSeen = 0;
- for (int i = 0; i < namesLength; i += 2) {
- final char c = getChar(header);
+ while (header.remaining() > 0) {
+ final char c = header.getChar();
if (c == 0) {
filesSeen++;
}
}
if (filesSeen != stats.numberOfEntries) {
- throw new ArchiveException("Invalid number of file names (%,d instead of %,d)", filesSeen, stats.numberOfEntries);
- }
- break;
- }
- case NID.kCTime: {
- final int timesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
- final int external = getUnsignedByte(header);
- if (external != 0) {
- throw new ArchiveException("Not implemented");
- }
- if (skipBytesFully(header, 8 * timesDefined) < 8 * timesDefined) {
- throw new ArchiveException("Invalid creation dates size");
- }
- break;
- }
- case NID.kATime: {
- final int timesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
- final int external = getUnsignedByte(header);
- if (external != 0) {
- throw new ArchiveException("Not implemented");
- }
- if (skipBytesFully(header, 8 * timesDefined) < 8 * timesDefined) {
- throw new ArchiveException("Invalid access dates size");
- }
- break;
- }
- case NID.kMTime: {
- final int timesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
- final int external = getUnsignedByte(header);
- if (external != 0) {
- throw new ArchiveException("Not implemented");
- }
- if (skipBytesFully(header, 8 * timesDefined) < 8 * timesDefined) {
- throw new ArchiveException("Invalid modification dates size");
+ throw new ArchiveException("7z archive: Invalid number of file names (%,d instead of %,d)", filesSeen, stats.numberOfEntries);
}
break;
}
+ case NID.kCTime:
+ case NID.kATime:
+ case NID.kMTime:
case NID.kWinAttributes: {
- final int attributesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
+ final int definedCount = readAllOrBits(header, stats.numberOfEntries).cardinality();
final int external = getUnsignedByte(header);
if (external != 0) {
- throw new ArchiveException("Not implemented");
- }
- if (skipBytesFully(header, 4 * attributesDefined) < 4 * attributesDefined) {
- throw new ArchiveException("Invalid windows attributes size");
+ throw new ArchiveException("7z archive: Not implemented");
}
+ skipBytesFully(header, (propertyType == NID.kWinAttributes ? UINT32_BYTES : REAL_UINT64_BYTES) * definedCount);
break;
}
case NID.kStartPos: {
- throw new ArchiveException("kStartPos is unsupported, please report");
+ throw new ArchiveException("7z archive: kStartPos is unsupported, please report");
}
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
- if (skipBytesFully(header, size) < size) {
- throw new ArchiveException("Incomplete kDummy property");
- }
+ skipBytesFully(header, size);
break;
}
default: {
// Compress up to 1.8.1 would throw an exception, now we ignore it (see COMPRESS-287
- if (skipBytesFully(header, size) < size) {
- throw new ArchiveException("Incomplete property of type " + propertyType);
- }
+ skipBytesFully(header, size);
break;
}
}
+ // We should have consumed all the bytes by now
+ if (header.remaining() > 0) {
+ throw new ArchiveException("7z archive: Unsupported, property 0x%02d has %d trailing bytes.", propertyType, header.remaining());
+ }
+ // Restore original limit
+ header.limit(originalLimit);
}
stats.numberOfEntriesWithStream = stats.numberOfEntries - Math.max(emptyStreams, 0);
}
private long sanityCheckFolder(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
- final int numCoders = toNonNegativeInt("numCoders", readUint64(header));
- if (numCoders == 0) {
- throw new ArchiveException("Folder without coders");
+ final long numCoders = readUint64(header);
+ if (numCoders == 0 || numCoders > MAX_CODERS_PER_FOLDER) {
+ throw new ArchiveException("7z archive: Unsupported, %,d coders in folder.", numCoders);
}
- stats.numberOfCoders = ArchiveException.addExact(stats.numberOfCoders, numCoders);
- long totalOutStreams = 0;
- long totalInStreams = 0;
+ stats.numberOfCoders = accumulate(stats.numberOfCoders, numCoders, "numCoders");
+ int totalInStreams = 0;
for (int i = 0; i < numCoders; i++) {
final int bits = getUnsignedByte(header);
final int idSize = bits & 0xf;
- get(header, new byte[idSize]);
+ skipBytesFully(header, idSize);
final boolean isSimple = (bits & 0x10) == 0;
final boolean hasAttributes = (bits & 0x20) != 0;
final boolean moreAlternativeMethods = (bits & 0x80) != 0;
if (moreAlternativeMethods) {
- throw new ArchiveException("Alternative methods are unsupported, please report. The reference implementation doesn't support them either.");
+ throw new ArchiveException(
+ "7z archive: Alternative methods are unsupported, please report. The reference implementation doesn't support them either.");
}
if (isSimple) {
totalInStreams++;
- totalOutStreams++;
} else {
- totalInStreams = ArchiveException.addExact(totalInStreams, toNonNegativeInt("numInStreams", readUint64(header)));
- totalOutStreams = ArchiveException.addExact(totalOutStreams, toNonNegativeInt("numOutStreams", readUint64(header)));
+ final long numInStreams = readUint64(header);
+ if (numInStreams > MAX_CODER_STREAMS_PER_FOLDER) {
+ throw new ArchiveException("7z archive: Unsupported, %,d coder input streams in folder.", numInStreams);
+ }
+ if (readUint64(header) != 1) {
+ throw new ArchiveException("7z archive: Unsupported, %,d coder output streams in folder.", readUint64(header));
+ }
+ totalInStreams += (int) numInStreams;
}
if (hasAttributes) {
- final int propertiesSize = toNonNegativeInt("propertiesSize", readUint64(header));
- if (skipBytesFully(header, propertiesSize) < propertiesSize) {
- throw new ArchiveException("Invalid propertiesSize in folder");
- }
+ final int propertiesSize = readFieldSize(header);
+ skipBytesFully(header, propertiesSize);
}
}
- toNonNegativeInt("totalInStreams", totalInStreams);
- toNonNegativeInt("totalOutStreams", totalOutStreams);
- stats.numberOfOutStreams = ArchiveException.addExact(stats.numberOfOutStreams, totalOutStreams);
- stats.numberOfInStreams = ArchiveException.addExact(stats.numberOfInStreams, totalInStreams);
- if (totalOutStreams == 0) {
- throw new ArchiveException("Total output streams can't be 0");
- }
- final int numBindPairs = toNonNegativeInt("numBindPairs", totalOutStreams - 1);
+ final int totalOutStreams = (int) numCoders;
+ stats.numberOfOutStreams = accumulate(stats.numberOfOutStreams, numCoders, "numOutStreams");
+ stats.numberOfInStreams = accumulate(stats.numberOfInStreams, totalInStreams, "numInStreams");
+ final int numBindPairs = totalOutStreams - 1;
if (totalInStreams < numBindPairs) {
- throw new ArchiveException("Total input streams can't be less than the number of bind pairs");
+ throw new ArchiveException("7z archive: Total input streams can't be less than the number of bind pairs");
}
- final BitSet inStreamsBound = new BitSet((int) totalInStreams);
+ final BitSet inStreamsBound = new BitSet(totalInStreams);
for (int i = 0; i < numBindPairs; i++) {
- final int inIndex = toNonNegativeInt("inIndex", readUint64(header));
+ final int inIndex = readUint64ToIntExact(header, "inIndex");
if (totalInStreams <= inIndex) {
- throw new ArchiveException("inIndex is bigger than number of inStreams");
+ throw new ArchiveException("7z archive: inIndex is bigger than number of inStreams");
}
inStreamsBound.set(inIndex);
- final int outIndex = toNonNegativeInt("outIndex", readUint64(header));
+ final int outIndex = readUint64ToIntExact(header, "outIndex");
if (totalOutStreams <= outIndex) {
- throw new ArchiveException("outIndex is bigger than number of outStreams");
+ throw new ArchiveException("7z archive: outIndex is bigger than number of outStreams");
}
}
final int numPackedStreams = toNonNegativeInt("numPackedStreams", totalInStreams - numBindPairs);
if (numPackedStreams == 1) {
if (inStreamsBound.nextClearBit(0) == -1) {
- throw new ArchiveException("Couldn't find stream's bind pair index");
+ throw new ArchiveException("7z archive: Couldn't find stream's bind pair index");
}
} else {
for (int i = 0; i < numPackedStreams; i++) {
- final int packedStreamIndex = toNonNegativeInt("packedStreamIndex", readUint64(header));
+ final int packedStreamIndex = readUint64ToIntExact(header, "packedStreamIndex");
if (packedStreamIndex >= totalInStreams) {
- throw new ArchiveException("packedStreamIndex is bigger than number of totalInStreams");
+ throw new ArchiveException("7z archive: packedStreamIndex is bigger than number of totalInStreams");
}
}
}
@@ -2014,33 +2127,31 @@ private long sanityCheckFolder(final ByteBuffer header, final ArchiveStatistics
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 ArchiveException("packPos (%,d) is out of range", packPos);
+ if (packPos > channel.size() - SIGNATURE_HEADER_SIZE) {
+ throw new ArchiveException("7z archive: packPos (%,d) is out of range", packPos);
}
- final long numPackStreams = readUint64(header);
- stats.numberOfPackedStreams = toNonNegativeInt("numPackStreams", numPackStreams);
+ stats.numberOfPackedStreams = readUint64ToIntExact(header, "numPackStreams");
int nid = getUnsignedByte(header);
if (nid == NID.kSize) {
long totalPackSizes = 0;
+ ensureRemaining(header, MIN_UINT64_BYTES * stats.numberOfPackedStreams);
for (int i = 0; i < stats.numberOfPackedStreams; i++) {
final long packSize = readUint64(header);
- totalPackSizes = ArchiveException.addExact(totalPackSizes, packSize);
- final long endOfPackStreams = SIGNATURE_HEADER_SIZE + packPos + totalPackSizes;
- if (packSize < 0 || endOfPackStreams > channel.size() || endOfPackStreams < packPos) {
- throw new ArchiveException("packSize (%,d) is out of range", packSize);
+ totalPackSizes = accumulate(totalPackSizes, packSize, "packSize");
+ // We check the total pack size against the file size.
+ if (totalPackSizes > channel.size() - SIGNATURE_HEADER_SIZE - packPos) {
+ throw new ArchiveException("7z archive: packSize (%,d) is out of range", packSize);
}
}
nid = getUnsignedByte(header);
}
if (nid == NID.kCRC) {
final int crcsDefined = readAllOrBits(header, stats.numberOfPackedStreams).cardinality();
- if (skipBytesFully(header, 4 * crcsDefined) < 4 * crcsDefined) {
- throw new ArchiveException("Invalid number of CRCs in PackInfo");
- }
+ skipBytesFully(header, 4L * crcsDefined);
nid = getUnsignedByte(header);
}
if (nid != NID.kEnd) {
- throw new ArchiveException("Badly terminated PackInfo (%s)", nid);
+ throw new ArchiveException("7z archive: Badly terminated PackInfo (%s)", nid);
}
}
@@ -2059,7 +2170,7 @@ private void sanityCheckStreamsInfo(final ByteBuffer header, final ArchiveStatis
nid = getUnsignedByte(header);
}
if (nid != NID.kEnd) {
- throw new ArchiveException("Badly terminated StreamsInfo");
+ throw new ArchiveException("7z archive: Badly terminated StreamsInfo");
}
}
@@ -2068,7 +2179,7 @@ private void sanityCheckSubStreamsInfo(final ByteBuffer header, final ArchiveSta
final List numUnpackSubStreamsPerFolder = new LinkedList<>();
if (nid == NID.kNumUnpackStream) {
for (int i = 0; i < stats.numberOfFolders; i++) {
- numUnpackSubStreamsPerFolder.add(toNonNegativeInt("numStreams", readUint64(header)));
+ numUnpackSubStreamsPerFolder.add(readUint64ToIntExact(header, "numStreams"));
}
stats.numberOfUnpackSubStreams = numUnpackSubStreamsPerFolder.stream().mapToLong(Integer::longValue).sum();
nid = getUnsignedByte(header);
@@ -2082,10 +2193,7 @@ private void sanityCheckSubStreamsInfo(final ByteBuffer header, final ArchiveSta
continue;
}
for (int i = 0; i < numUnpackSubStreams - 1; i++) {
- final long size = readUint64(header);
- if (size < 0) {
- throw new ArchiveException("Negative unpackSize");
- }
+ readUint64(header);
}
}
nid = getUnsignedByte(header);
@@ -2097,33 +2205,29 @@ private void sanityCheckSubStreamsInfo(final ByteBuffer header, final ArchiveSta
int folderIdx = 0;
for (final int numUnpackSubStreams : numUnpackSubStreamsPerFolder) {
if (numUnpackSubStreams != 1 || stats.folderHasCrc == null || !stats.folderHasCrc.get(folderIdx++)) {
- numDigests = ArchiveException.addExact(numDigests, numUnpackSubStreams);
+ numDigests = accumulate(numDigests, numUnpackSubStreams, "numDigests");
}
}
}
if (nid == NID.kCRC) {
- toNonNegativeInt("numDigests", numDigests);
final int missingCrcs = readAllOrBits(header, numDigests).cardinality();
- if (skipBytesFully(header, 4 * missingCrcs) < 4 * missingCrcs) {
- throw new ArchiveException("Invalid number of missing CRCs in SubStreamInfo");
- }
+ skipBytesFully(header, UINT32_BYTES * missingCrcs);
nid = getUnsignedByte(header);
}
if (nid != NID.kEnd) {
- throw new ArchiveException("Badly terminated SubStreamsInfo");
+ throw new ArchiveException("7z archive: Badly terminated SubStreamsInfo");
}
}
private void sanityCheckUnpackInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
int nid = getUnsignedByte(header);
if (nid != NID.kFolder) {
- throw new ArchiveException("Expected NID.kFolder, got %s", nid);
+ throw new ArchiveException("7z archive: Expected NID.kFolder, got %s", nid);
}
- final long numFolders = readUint64(header);
- stats.numberOfFolders = toNonNegativeInt("numFolders", numFolders);
+ stats.numberOfFolders = readUint64ToIntExact(header, "numFolders");
final int external = getUnsignedByte(header);
if (external != 0) {
- throw new ArchiveException("External unsupported");
+ throw new ArchiveException("7z archive: External unsupported");
}
final List numberOfOutputStreamsPerFolder = new LinkedList<>();
for (int i = 0; i < stats.numberOfFolders; i++) {
@@ -2132,31 +2236,26 @@ private void sanityCheckUnpackInfo(final ByteBuffer header, final ArchiveStatist
final long totalNumberOfBindPairs = stats.numberOfOutStreams - stats.numberOfFolders;
final long packedStreamsRequiredByFolders = stats.numberOfInStreams - totalNumberOfBindPairs;
if (packedStreamsRequiredByFolders < stats.numberOfPackedStreams) {
- throw new ArchiveException("Archive doesn't contain enough packed streams");
+ throw new ArchiveException("7z archive: Archive doesn't contain enough packed streams");
}
nid = getUnsignedByte(header);
if (nid != NID.kCodersUnpackSize) {
- throw new ArchiveException("Expected kCodersUnpackSize, got %s", nid);
+ throw new ArchiveException("7z archive: Expected kCodersUnpackSize, got %s", nid);
}
for (final long numberOfOutputStreams : numberOfOutputStreamsPerFolder) {
for (long i = 0; i < numberOfOutputStreams; i++) {
- final long unpackSize = readUint64(header);
- if (unpackSize < 0) {
- throw new IllegalArgumentException("Negative unpackSize");
- }
+ readUint64(header);
}
}
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 ArchiveException("Invalid number of CRCs in UnpackInfo");
- }
+ skipBytesFully(header, UINT32_BYTES * crcsDefined);
nid = getUnsignedByte(header);
}
if (nid != NID.kEnd) {
- throw new ArchiveException("Badly terminated UnpackInfo");
+ throw new ArchiveException("7z archive: Badly terminated UnpackInfo");
}
}
@@ -2231,6 +2330,32 @@ private boolean skipEntriesWhenNeeded(final int entryIndex, final boolean isInSa
return true;
}
+ /**
+ * {@inheritDoc}
+ *
+ * @since 1.29.0
+ */
+ @Override
+ public IOStream extends SevenZArchiveEntry> stream() {
+ return IOStream.of(archive.files);
+ }
+
+ /**
+ * Converts the given ByteBuffer to a byte array of the given size.
+ *
+ * @param header The buffer containing the 7z header data.
+ * @param size The size of the byte array to create.
+ * @return A byte array containing the data from the buffer.
+ * @throws IOException if there are insufficient resources to allocate the array or insufficient data in the buffer.
+ */
+ private byte[] toByteArray(final ByteBuffer header, final int size) throws IOException {
+ // Check if we have enough resources to allocate the array
+ MemoryLimitException.checkKiB(bytesToKiB(size * Byte.BYTES), maxMemoryLimitKiB);
+ final byte[] result = new byte[size];
+ get(header, result);
+ return result;
+ }
+
@Override
public String toString() {
return archive.toString();
@@ -2239,15 +2364,8 @@ public String toString() {
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;
- }
+ final long minPos = Math.max(channel.position(), channel.size() - searchLimit);
long pos = channel.size() - 1;
// Loop: Try from end of archive
while (pos > minPos) {
@@ -2262,9 +2380,10 @@ private Archive tryToLocateEndHeader(final byte[] password) throws IOException {
if (nid == NID.kEncodedHeader || nid == NID.kHeader) {
try {
// Try to initialize Archive structure from here
- final long nextHeaderOffset = pos - previousDataSize;
+ final long nextHeaderOffset = pos - SIGNATURE_HEADER_SIZE;
+ // Smaller than 1 MiB, so fits in an int
final long nextHeaderSize = channel.size() - pos;
- final StartHeader startHeader = new StartHeader(nextHeaderOffset, nextHeaderSize, 0);
+ final StartHeader startHeader = new StartHeader(nextHeaderOffset, (int) nextHeaderSize, 0);
final Archive result = initializeArchive(startHeader, password, false);
// Sanity check: There must be some data...
if (result.packSizes.length > 0 && result.files.length > 0) {
@@ -2275,6 +2394,6 @@ private Archive tryToLocateEndHeader(final byte[] password) throws IOException {
}
}
}
- throw new ArchiveException("Start header corrupt and unable to guess end header");
+ throw new ArchiveException("7z archive: Start header corrupt and unable to guess end header");
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
index 5a35824c151..5db75811c10 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
@@ -55,6 +55,7 @@
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.file.attribute.FileTimes;
import org.apache.commons.io.output.CountingOutputStream;
@@ -87,6 +88,7 @@ public void write(final byte[] b) throws IOException {
@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len > BUF_SIZE) {
channel.write(ByteBuffer.wrap(b, off, len));
} else {
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/StartHeader.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/StartHeader.java
index a1821dd83ac..c1049e85e38 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/StartHeader.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/StartHeader.java
@@ -21,12 +21,25 @@
final class StartHeader {
final long nextHeaderOffset;
- final long nextHeaderSize;
+ final int nextHeaderSize;
final long nextHeaderCrc;
- StartHeader(final long nextHeaderOffset, final long nextHeaderSize, final long nextHeaderCrc) {
+ StartHeader(final long nextHeaderOffset, final int nextHeaderSize, final long nextHeaderCrc) {
+ // The interval [SIGNATURE_HEADER_SIZE + nextHeaderOffset, SIGNATURE_HEADER_SIZE + nextHeaderOffset + nextHeaderSize)
+ // must be a valid range of the file, in particular must be within [0, Long.MAX_VALUE).
+ assert nextHeaderOffset >= 0 && nextHeaderOffset <= Long.MAX_VALUE - SevenZFile.SIGNATURE_HEADER_SIZE;
+ assert nextHeaderSize >= 0 && nextHeaderSize <= Long.MAX_VALUE - SevenZFile.SIGNATURE_HEADER_SIZE - nextHeaderOffset;
this.nextHeaderOffset = nextHeaderOffset;
this.nextHeaderSize = nextHeaderSize;
this.nextHeaderCrc = nextHeaderCrc;
}
+
+ /**
+ * Gets the position of the next header in the file.
+ *
+ * @return the position of the next header
+ */
+ long position() {
+ return SevenZFile.SIGNATURE_HEADER_SIZE + nextHeaderOffset;
+ }
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/SubStreamsInfo.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/SubStreamsInfo.java
index 3b5ece3e7e5..f15cba1ee74 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/SubStreamsInfo.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/SubStreamsInfo.java
@@ -41,15 +41,14 @@ final class SubStreamsInfo {
*/
final long[] crcs;
- SubStreamsInfo(final long totalUnpackStreams, final int maxMemoryLimitKiB) throws CompressException {
- final int intExactCount = Math.toIntExact(totalUnpackStreams);
- int alloc;
+ SubStreamsInfo(final int totalUnpackStreams, final int maxMemoryLimitKiB) throws CompressException {
+ long alloc;
try {
// 2 long arrays, just count the longs
- alloc = Math.multiplyExact(intExactCount, Long.BYTES * 2);
+ alloc = Math.multiplyExact(totalUnpackStreams, Long.BYTES * 2);
// one BitSet [boolean, long[], int]. just count the long array
- final int sizeOfBitSet = Math.multiplyExact(Long.BYTES, (intExactCount - 1 >> 6) + 1);
- alloc = Math.addExact(alloc, Math.multiplyExact(intExactCount, sizeOfBitSet));
+ final int sizeOfBitSet = Math.multiplyExact(Long.BYTES, (totalUnpackStreams - 1 >> 6) + 1);
+ alloc = Math.addExact(alloc, Math.multiplyExact(totalUnpackStreams, sizeOfBitSet));
} catch (final ArithmeticException e) {
throw new CompressException("Cannot create allocation request for a SubStreamsInfo of totalUnpackStreams %,d, maxMemoryLimitKiB %,d: %s",
totalUnpackStreams, maxMemoryLimitKiB, e);
@@ -57,8 +56,8 @@ final class SubStreamsInfo {
// Avoid false positives.
// Not a reliable check in old VMs or in low memory VMs.
// MemoryLimitException.checkKiB(SevenZFile.bytesToKiB(alloc), maxMemoryLimitKiB);
- this.hasCrc = new BitSet(intExactCount);
- this.crcs = new long[intExactCount];
- this.unpackSizes = new long[intExactCount];
+ this.hasCrc = new BitSet(totalUnpackStreams);
+ this.crcs = new long[totalUnpackStreams];
+ this.unpackSizes = new long[totalUnpackStreams];
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/AbstractTarBuilder.java b/src/main/java/org/apache/commons/compress/archivers/tar/AbstractTarBuilder.java
index 5555f5ccc2b..0f471a1ca1f 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/AbstractTarBuilder.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/AbstractTarBuilder.java
@@ -19,7 +19,7 @@
package org.apache.commons.compress.archivers.tar;
-import org.apache.commons.io.build.AbstractStreamBuilder;
+import org.apache.commons.compress.archivers.AbstractArchiveBuilder;
/**
* Abstracts TAR builder operations.
@@ -28,7 +28,7 @@
* @param the type of builder subclass.
* @since 1.29.0
*/
-public abstract class AbstractTarBuilder> extends AbstractStreamBuilder {
+public abstract class AbstractTarBuilder> extends AbstractArchiveBuilder {
private int blockSize = TarConstants.DEFAULT_BLKSIZE;
private int recordSize = TarConstants.DEFAULT_RCDSIZE;
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
index 652ba1fbc81..e0720114eca 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
@@ -50,7 +50,6 @@
import org.apache.commons.compress.archivers.EntryStreamOffsets;
import org.apache.commons.compress.archivers.zip.ZipEncoding;
import org.apache.commons.compress.utils.ArchiveUtils;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.ParsingUtils;
import org.apache.commons.io.file.attribute.FileTimes;
import org.apache.commons.lang3.StringUtils;
@@ -222,6 +221,20 @@ public class TarArchiveEntry implements ArchiveEntry, TarConstants, EntryStreamO
*/
private static final Pattern PAX_EXTENDED_HEADER_FILE_TIMES_PATTERN = Pattern.compile("-?\\d{1,19}(?:\\.\\d{1,19})?");
+ /**
+ * PAX header key for the path (file name) of the entry.
+ *
+ * @see #getName()
+ */
+ static final String PAX_NAME_KEY = "path";
+
+ /**
+ * PAX header key for the link path (link name) of the entry.
+ *
+ * @see #getLinkName()
+ */
+ static final String PAX_LINK_NAME_KEY = "linkpath";
+
private static FileTime fileTimeFromOptionalSeconds(final long seconds) {
return seconds <= 0 ? null : FileTimes.fromUnixTime(seconds);
}
@@ -393,7 +406,7 @@ private TarArchiveEntry(final boolean preserveAbsolutePath) {
}
this.userName = user;
this.file = null;
- this.linkOptions = IOUtils.EMPTY_LINK_OPTIONS;
+ this.linkOptions = org.apache.commons.compress.utils.IOUtils.EMPTY_LINK_OPTIONS;
this.preserveAbsolutePath = preserveAbsolutePath;
}
@@ -488,7 +501,7 @@ public TarArchiveEntry(final File file) {
public TarArchiveEntry(final File file, final String fileName) {
final String normalizedName = normalizeFileName(fileName, false);
this.file = file.toPath();
- this.linkOptions = IOUtils.EMPTY_LINK_OPTIONS;
+ this.linkOptions = org.apache.commons.compress.utils.IOUtils.EMPTY_LINK_OPTIONS;
try {
readFileMode(this.file, normalizedName);
} catch (final IOException e) {
@@ -578,7 +591,7 @@ public TarArchiveEntry(final Path file) throws IOException {
public TarArchiveEntry(final Path file, final String fileName, final LinkOption... linkOptions) throws IOException {
final String normalizedName = normalizeFileName(fileName, false);
this.file = file;
- this.linkOptions = linkOptions == null ? IOUtils.EMPTY_LINK_OPTIONS : linkOptions;
+ this.linkOptions = linkOptions == null ? org.apache.commons.compress.utils.IOUtils.EMPTY_LINK_OPTIONS : linkOptions;
readFileMode(file, normalizedName, linkOptions);
this.userName = "";
readOsSpecificProperties(file);
@@ -1051,7 +1064,7 @@ public long getRealSize() {
* when the entry represents a sparse file.
*
*
- * @return This entry's file size.
+ * @return This entry's file size, always >= 0.
*/
@Override
public long getSize() {
@@ -1629,10 +1642,10 @@ private void processPaxHeader(final String key, final String val, final Map sparseInputStreams;
-
- /** The index of current input stream being read when reading sparse entries. */
- private int currentSparseInputStreamIndex;
-
/** The meta-data about the current entry. */
private TarArchiveEntry currEntry;
+ /** The current input stream. */
+ private InputStream currentInputStream;
+
/** The encoding of the file. */
private final ZipEncoding zipEncoding;
@@ -175,101 +171,112 @@ public static boolean matches(final byte[] signature, final int length) {
private final boolean lenient;
- @SuppressWarnings("resource") // caller closes.
private TarArchiveInputStream(final Builder builder) throws IOException {
- this(builder.getInputStream(), builder);
+ super(builder);
+ this.zipEncoding = ZipEncodingHelper.getZipEncoding(builder.getCharset());
+ this.recordBuffer = new byte[builder.getRecordSize()];
+ this.blockSize = builder.getBlockSize();
+ this.lenient = builder.isLenient();
}
/**
* Constructs a new instance.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the input stream to use.
*/
- public TarArchiveInputStream(final InputStream inputStream) {
- this(inputStream, builder());
+ public TarArchiveInputStream(final InputStream inputStream) throws IOException {
+ this(builder().setInputStream(inputStream));
}
/**
* Constructs a new instance with default values.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the input stream to use.
* @param lenient when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
* {@link TarArchiveEntry#UNKNOWN}. When set to false such illegal fields cause an exception instead.
+ * @throws IOException if an I/O error occurs.
* @since 1.19
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public TarArchiveInputStream(final InputStream inputStream, final boolean lenient) {
- this(inputStream, builder().setLenient(lenient));
- }
-
- private TarArchiveInputStream(final InputStream inputStream, final Builder builder) {
- super(inputStream, builder.getCharset());
- this.zipEncoding = ZipEncodingHelper.getZipEncoding(builder.getCharset());
- this.recordBuffer = new byte[builder.getRecordSize()];
- this.blockSize = builder.getBlockSize();
- this.lenient = builder.isLenient();
+ public TarArchiveInputStream(final InputStream inputStream, final boolean lenient) throws IOException {
+ this(builder().setInputStream(inputStream).setLenient(lenient));
}
/**
* Constructs a new instance.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the input stream to use.
* @param blockSize the block size to use.
+ * @throws IOException if an I/O error occurs.
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public TarArchiveInputStream(final InputStream inputStream, final int blockSize) {
- this(inputStream, builder().setBlockSize(blockSize));
+ public TarArchiveInputStream(final InputStream inputStream, final int blockSize) throws IOException {
+ this(builder().setInputStream(inputStream).setBlockSize(blockSize));
}
/**
* Constructs a new instance.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the input stream to use.
* @param blockSize the block size to use.
* @param recordSize the record size to use.
+ * @throws IOException if an I/O error occurs.
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public TarArchiveInputStream(final InputStream inputStream, final int blockSize, final int recordSize) {
- this(inputStream, builder().setBlockSize(blockSize).setRecordSize(recordSize));
+ public TarArchiveInputStream(final InputStream inputStream, final int blockSize, final int recordSize) throws IOException {
+ this(builder().setInputStream(inputStream).setBlockSize(blockSize).setRecordSize(recordSize));
}
/**
* Constructs a new instance.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the input stream to use.
* @param blockSize the block size to use.
* @param recordSize the record size to use.
* @param encoding name of the encoding to use for file names.
+ * @throws IOException if an I/O error occurs.
* @since 1.4
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public TarArchiveInputStream(
- final InputStream inputStream, final int blockSize, final int recordSize, final String encoding) {
- this(
- inputStream,
- builder().setBlockSize(blockSize).setRecordSize(recordSize).setCharset(encoding));
+ public TarArchiveInputStream(final InputStream inputStream, final int blockSize, final int recordSize, final String encoding) throws IOException {
+ this(builder().setInputStream(inputStream).setBlockSize(blockSize).setRecordSize(recordSize).setCharset(encoding));
}
/**
* Constructs a new instance.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the input stream to use.
* @param blockSize the block size to use.
* @param recordSize the record size to use.
* @param encoding name of the encoding to use for file names.
* @param lenient when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
* {@link TarArchiveEntry#UNKNOWN}. When set to false such illegal fields cause an exception instead.
+ * @throws IOException if an I/O error occurs.
* @since 1.19
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public TarArchiveInputStream(final InputStream inputStream, final int blockSize, final int recordSize, final String encoding, final boolean lenient) {
+ public TarArchiveInputStream(final InputStream inputStream, final int blockSize, final int recordSize, final String encoding,
+ final boolean lenient) throws IOException {
// @formatter:off
- this(inputStream, builder()
+ this(builder()
+ .setInputStream(inputStream)
.setBlockSize(blockSize)
.setRecordSize(recordSize)
.setCharset(encoding)
@@ -280,28 +287,45 @@ public TarArchiveInputStream(final InputStream inputStream, final int blockSize,
/**
* Constructs a new instance.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the input stream to use.
* @param blockSize the block size to use.
* @param encoding name of the encoding to use for file names.
+ * @throws IOException if an I/O error occurs.
* @since 1.4
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public TarArchiveInputStream(final InputStream inputStream, final int blockSize, final String encoding) {
- this(inputStream, builder().setBlockSize(blockSize).setCharset(encoding));
+ public TarArchiveInputStream(final InputStream inputStream, final int blockSize, final String encoding) throws IOException {
+ this(builder().setInputStream(inputStream).setBlockSize(blockSize).setCharset(encoding));
}
/**
* Constructs a new instance.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the input stream to use.
* @param encoding name of the encoding to use for file names.
+ * @throws IOException if an I/O error occurs.
* @since 1.4
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public TarArchiveInputStream(final InputStream inputStream, final String encoding) {
- this(inputStream, builder().setCharset(encoding));
+ public TarArchiveInputStream(final InputStream inputStream, final String encoding) throws IOException {
+ this(builder().setInputStream(inputStream).setCharset(encoding));
+ }
+
+ private void afterRead(final int read) throws IOException {
+ // Count the bytes read
+ count(read);
+ // Check for truncated entries
+ if (read == -1 && entryOffset < currEntry.getSize()) {
+ throw new EOFException(String.format("Truncated TAR archive: Entry '%s' expected %,d bytes, actual %,d", currEntry.getName(), currEntry.getSize(),
+ entryOffset));
+ }
+ entryOffset += Math.max(0, read);
}
/**
@@ -332,8 +356,7 @@ public int available() throws IOException {
*
*/
private void buildSparseInputStreams() throws IOException {
- currentSparseInputStreamIndex = -1;
- sparseInputStreams = new ArrayList<>();
+ final List sparseInputStreams = new ArrayList<>();
final List sparseHeaders = currEntry.getOrderedSparseHeaders();
// Stream doesn't need to be closed at all as it doesn't use any resources
final InputStream zeroInputStream = new TarArchiveSparseZeroInputStream(); // NOSONAR
@@ -359,15 +382,15 @@ private void buildSparseInputStreams() throws IOException {
// @formatter:off
sparseInputStreams.add(BoundedInputStream.builder()
.setInputStream(in)
+ .setAfterRead(this::afterRead)
.setMaxCount(sparseHeader.getNumbytes())
+ .setPropagateClose(false)
.get());
// @formatter:on
}
offset = sparseHeader.getOffset() + sparseHeader.getNumbytes();
}
- if (!sparseInputStreams.isEmpty()) {
- currentSparseInputStreamIndex = 0;
- }
+ currentInputStream = new SequenceInputStream(Collections.enumeration(sparseInputStreams));
}
/**
@@ -388,10 +411,9 @@ public boolean canReadEntryData(final ArchiveEntry archiveEntry) {
@Override
public void close() throws IOException {
// Close all the input streams in sparseInputStreams
- if (sparseInputStreams != null) {
- for (final InputStream inputStream : sparseInputStreams) {
- inputStream.close();
- }
+ if (currentInputStream != null) {
+ currentInputStream.close();
+ currentInputStream = null;
}
in.close();
}
@@ -407,26 +429,6 @@ private void consumeRemainderOfLastBlock() throws IOException {
}
}
- /**
- * For FileInputStream, the skip always return the number you input, so we need the available bytes to determine how many bytes are actually skipped
- *
- * @param available available bytes returned by {@link InputStream#available()}.
- * @param skipped skipped bytes returned by {@link InputStream#skip()}.
- * @param expected bytes expected to skip.
- * @return number of bytes actually skipped.
- * @throws IOException if a truncated tar archive is detected.
- */
- private long getActuallySkipped(final long available, final long skipped, final long expected) throws IOException {
- long actuallySkipped = skipped;
- if (in instanceof FileInputStream) {
- actuallySkipped = Math.min(skipped, available);
- }
- if (actuallySkipped != expected) {
- throw new ArchiveException("Truncated TAR archive");
- }
- return actuallySkipped;
- }
-
/**
* Gets the current TAR Archive Entry that this input stream is processing
*
@@ -491,8 +493,8 @@ public TarArchiveEntry getNextEntry() throws IOException {
boolean lastWasSpecial = false;
do {
// If there is a current entry, skip any unread data and padding
- if (currEntry != null) {
- IOUtils.skip(this, Long.MAX_VALUE); // Skip to end of current entry
+ if (currentInputStream != null) {
+ IOUtils.consume(currentInputStream); // Skip to end of current entry
skipRecordPadding(); // Skip padding to align to the next record
}
// Read the next header record
@@ -507,12 +509,19 @@ public TarArchiveEntry getNextEntry() throws IOException {
}
// Parse the header into a new entry
currEntry = new TarArchiveEntry(globalPaxHeaders, headerBuf, zipEncoding, lenient);
+ // Set up the input stream for the new entry
+ currentInputStream = BoundedInputStream.builder()
+ .setInputStream(in)
+ .setAfterRead(this::afterRead)
+ .setMaxCount(currEntry.getSize())
+ .setPropagateClose(false)
+ .get();
entryOffset = 0;
- entrySize = currEntry.getSize();
lastWasSpecial = TarUtils.isSpecialTarRecord(currEntry);
if (lastWasSpecial) {
// Handle PAX, GNU long name, or other special records
- TarUtils.handleSpecialTarRecord(this, zipEncoding, currEntry, paxHeaders, sparseHeaders, globalPaxHeaders, globalSparseHeaders);
+ TarUtils.handleSpecialTarRecord(currentInputStream, zipEncoding, getMaxEntryNameLength(), currEntry, paxHeaders, sparseHeaders,
+ globalPaxHeaders, globalSparseHeaders);
}
} while (lastWasSpecial);
// Apply global and local PAX headers
@@ -520,9 +529,12 @@ public TarArchiveEntry getNextEntry() throws IOException {
// Handle sparse files
if (currEntry.isSparse()) {
if (currEntry.isOldGNUSparse()) {
+ // Old GNU sparse format uses extra header blocks for metadata.
+ // These blocks are not included in the entry’s size, so we cannot
+ // rely on BoundedInputStream here.
readOldGNUSparse();
} else if (currEntry.isPaxGNU1XSparse()) {
- currEntry.setSparseHeaders(TarUtils.parsePAX1XSparseHeaders(in, getRecordSize()));
+ currEntry.setSparseHeaders(TarUtils.parsePAX1XSparseHeaders(currentInputStream, getRecordSize()));
}
// sparse headers are all done reading, we need to build
// sparse input streams using these sparse headers
@@ -532,8 +544,6 @@ public TarArchiveEntry getNextEntry() throws IOException {
if (currEntry.isDirectory() && !currEntry.getName().endsWith("/")) {
currEntry.setName(currEntry.getName() + "/");
}
- // Update entry size in case it changed due to PAX headers
- entrySize = currEntry.getSize();
return currEntry;
}
@@ -634,40 +644,24 @@ public boolean markSupported() {
* @param offset The offset at which to place bytes read.
* @param numToRead The number of bytes to read.
* @return The number of bytes read, or -1 at EOF.
+ * @throws NullPointerException if {@code buf} is null
+ * @throws IndexOutOfBoundsException if {@code offset} or {@code numToRead} are negative,
+ * or if {@code offset + numToRead} is greater than {@code buf.length}.
* @throws IOException on error
*/
@Override
public int read(final byte[] buf, final int offset, int numToRead) throws IOException {
+ IOUtils.checkFromIndexSize(buf, offset, numToRead);
if (numToRead == 0) {
return 0;
}
- int totalRead = 0;
if (isAtEOF() || isDirectory()) {
return -1;
}
- if (currEntry == null) {
+ if (currEntry == null || currentInputStream == null) {
throw new IllegalStateException("No current tar entry");
}
- if (entryOffset >= currEntry.getRealSize()) {
- return -1;
- }
- numToRead = Math.min(numToRead, available());
- if (currEntry.isSparse()) {
- // for sparse entries, we need to read them in another way
- totalRead = readSparse(buf, offset, numToRead);
- } else {
- totalRead = in.read(buf, offset, numToRead);
- }
- if (totalRead == -1) {
- if (numToRead > 0) {
- throw new ArchiveException("Truncated TAR archive");
- }
- setAtEOF(true);
- } else {
- count(totalRead);
- entryOffset += totalRead;
- }
- return totalRead;
+ return currentInputStream.read(buf, offset, numToRead);
}
/**
@@ -687,9 +681,6 @@ private void readOldGNUSparse() throws IOException {
currEntry.getSparseHeaders().addAll(entry.getSparseHeaders());
} while (entry.isExtended());
}
- // sparse headers are all done reading, we need to build
- // sparse input streams using these sparse headers
- buildSparseInputStreams();
}
/**
@@ -699,7 +690,7 @@ private void readOldGNUSparse() throws IOException {
* @throws IOException on error.
*/
protected byte[] readRecord() throws IOException {
- final int readCount = IOUtils.readFully(in, recordBuffer);
+ final int readCount = IOUtils.read(in, recordBuffer);
count(readCount);
if (readCount != getRecordSize()) {
return null;
@@ -707,52 +698,6 @@ protected byte[] readRecord() throws IOException {
return recordBuffer;
}
- /**
- * For sparse tar entries, there are many "holes"(consisting of all 0) in the file. Only the non-zero data is stored in tar files, and they are stored
- * separately. The structure of non-zero data is introduced by the sparse headers using the offset, where a block of non-zero data starts, and numbytes, the
- * length of the non-zero data block. When reading sparse entries, the actual data is read out with "holes" and non-zero data combined according to
- * the sparse headers.
- *
- * @param buf The buffer into which to place bytes read.
- * @param offset The offset at which to place bytes read.
- * @param numToRead The number of bytes to read.
- * @return The number of bytes read, or -1 at EOF.
- * @throws IOException on error.
- */
- private int readSparse(final byte[] buf, final int offset, final int numToRead) throws IOException {
- // if there are no actual input streams, just read from the original input stream
- if (sparseInputStreams == null || sparseInputStreams.isEmpty()) {
- return in.read(buf, offset, numToRead);
- }
- if (currentSparseInputStreamIndex >= sparseInputStreams.size()) {
- return -1;
- }
- final InputStream currentInputStream = sparseInputStreams.get(currentSparseInputStreamIndex);
- final int readLen = currentInputStream.read(buf, offset, numToRead);
- // if the current input stream is the last input stream,
- // just return the number of bytes read from current input stream
- if (currentSparseInputStreamIndex == sparseInputStreams.size() - 1) {
- return readLen;
- }
- // if EOF of current input stream is meet, open a new input stream and recursively call read
- if (readLen == -1) {
- currentSparseInputStreamIndex++;
- return readSparse(buf, offset, numToRead);
- }
- // if the rest data of current input stream is not long enough, open a new input stream
- // and recursively call read
- if (readLen < numToRead) {
- currentSparseInputStreamIndex++;
- final int readLenOfNext = readSparse(buf, offset + readLen, numToRead - readLen);
- if (readLenOfNext == -1) {
- return readLen;
- }
- return readLen + readLenOfNext;
- }
- // if the rest data of current input stream is enough(which means readLen == len), just return readLen
- return readLen;
- }
-
/**
* Since we do not support marking just yet, we do nothing.
*/
@@ -793,21 +738,11 @@ public long skip(final long n) throws IOException {
if (n <= 0 || isDirectory()) {
return 0;
}
- final long availableOfInputStream = in.available();
- final long available = currEntry.getRealSize() - entryOffset;
- final long numToSkip = Math.min(n, available);
- long skipped;
- if (!currEntry.isSparse()) {
- skipped = IOUtils.skip(in, numToSkip);
- // for non-sparse entry, we should get the bytes actually skipped bytes along with
- // inputStream.available() if inputStream is instance of FileInputStream
- skipped = getActuallySkipped(availableOfInputStream, skipped, numToSkip);
- } else {
- skipped = skipSparse(numToSkip);
+ if (currEntry == null || currentInputStream == null) {
+ throw new IllegalStateException("No current tar entry");
}
- count(skipped);
- entryOffset += skipped;
- return skipped;
+ // Use Apache Commons IO to skip as it handles skipping fully
+ return IOUtils.skip(currentInputStream, n);
}
/**
@@ -816,37 +751,15 @@ public long skip(final long n) throws IOException {
* @throws IOException if a truncated tar archive is detected.
*/
private void skipRecordPadding() throws IOException {
- if (!isDirectory() && this.entrySize > 0 && this.entrySize % getRecordSize() != 0) {
- final long available = in.available();
- final long numRecords = this.entrySize / getRecordSize() + 1;
- final long padding = numRecords * getRecordSize() - this.entrySize;
- long skipped = IOUtils.skip(in, padding);
- skipped = getActuallySkipped(available, skipped, padding);
+ final long entrySize = currEntry != null ? currEntry.getSize() : 0;
+ if (!isDirectory() && entrySize > 0 && entrySize % getRecordSize() != 0) {
+ final long padding = getRecordSize() - (entrySize % getRecordSize());
+ final long skipped = IOUtils.skip(in, padding);
count(skipped);
- }
- }
-
- /**
- * Skip n bytes from current input stream, if the current input stream doesn't have enough data to skip, jump to the next input stream and skip the rest
- * bytes, keep doing this until total n bytes are skipped or the input streams are all skipped
- *
- * @param n bytes of data to skip.
- * @return actual bytes of data skipped.
- * @throws IOException if an I/O error occurs.
- */
- private long skipSparse(final long n) throws IOException {
- if (sparseInputStreams == null || sparseInputStreams.isEmpty()) {
- return in.skip(n);
- }
- long bytesSkipped = 0;
- while (bytesSkipped < n && currentSparseInputStreamIndex < sparseInputStreams.size()) {
- final InputStream currentInputStream = sparseInputStreams.get(currentSparseInputStreamIndex);
- bytesSkipped += currentInputStream.skip(n - bytesSkipped);
- if (bytesSkipped < n) {
- currentSparseInputStreamIndex++;
+ if (skipped != padding) {
+ throw new EOFException(String.format("Truncated TAR archive: Failed to skip record padding for entry '%s'", currEntry.getName()));
}
}
- return bytesSkipped;
}
/**
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStream.java
index 4ab04832a72..7024810fce8 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStream.java
@@ -39,6 +39,7 @@
import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
import org.apache.commons.compress.utils.FixedLengthBlockOutputStream;
import org.apache.commons.io.Charsets;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.file.attribute.FileTimes;
import org.apache.commons.io.output.CountingOutputStream;
import org.apache.commons.lang3.ArrayFill;
@@ -627,10 +628,14 @@ private void transferModTime(final TarArchiveEntry from, final TarArchiveEntry t
* @param wBuf The buffer to write to the archive.
* @param wOffset The offset in the buffer from which to get bytes.
* @param numToWrite The number of bytes to write.
+ * @throws NullPointerException if {@code wBuf} is null
+ * @throws IndexOutOfBoundsException if {@code wOffset} or {@code numToWrite} are negative,
+ * or if {@code wOffset + numToWrite} is greater than {@code wBuf.length}.
* @throws IOException on error
*/
@Override
public void write(final byte[] wBuf, final int wOffset, final int numToWrite) throws IOException {
+ IOUtils.checkFromIndexSize(wBuf, wOffset, numToWrite);
if (!haveUnclosedEntry) {
throw new IllegalStateException("No current tar entry");
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveSparseZeroInputStream.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveSparseZeroInputStream.java
index 513ed072222..263619a2a27 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveSparseZeroInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveSparseZeroInputStream.java
@@ -18,7 +18,11 @@
*/
package org.apache.commons.compress.archivers.tar;
+import java.io.IOException;
import java.io.InputStream;
+import java.util.Arrays;
+
+import org.apache.commons.io.IOUtils;
/**
* This is an InputStream that always return 0, this is used when reading the "holes" of a sparse file
@@ -35,6 +39,16 @@ public int read() {
return 0;
}
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
+ if (len == 0) {
+ return 0;
+ }
+ Arrays.fill(b, off, off + len, (byte) 0);
+ return len;
+ }
+
/**
* Returns the input.
*
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java
index 9ebb6c1d6e1..0612626ed48 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java
@@ -18,31 +18,31 @@
*/
package org.apache.commons.compress.archivers.tar;
-import java.io.Closeable;
+import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.io.SequenceInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
-import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.compress.archivers.ArchiveFile;
import org.apache.commons.compress.archivers.zip.ZipEncoding;
import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.utils.ArchiveUtils;
import org.apache.commons.compress.utils.BoundedArchiveInputStream;
-import org.apache.commons.compress.utils.BoundedSeekableByteChannelInputStream;
-import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
-import org.apache.commons.io.function.IOIterable;
-import org.apache.commons.io.function.IOIterator;
+import org.apache.commons.io.function.IOStream;
import org.apache.commons.io.input.BoundedInputStream;
/**
@@ -50,93 +50,47 @@
*
* @since 1.21
*/
-public class TarFile implements Closeable, IOIterable {
+public class TarFile implements ArchiveFile {
+ /**
+ * InputStream that reads a specific entry from the archive.
+ *
+ * It ensures that:
+ *
+ * - No more than the specified number of bytes are read from the underlying channel.
+ * - If the end of the entry is reached before the expected number of bytes, an {@link EOFException} is thrown.
+ *
+ */
private final class BoundedTarEntryInputStream extends BoundedArchiveInputStream {
private final SeekableByteChannel channel;
- private final TarArchiveEntry entry;
-
- private long entryOffset;
+ private final long end;
- private int currentSparseInputStreamIndex;
-
- BoundedTarEntryInputStream(final TarArchiveEntry entry, final SeekableByteChannel channel) throws IOException {
- super(entry.getDataOffset(), entry.getRealSize());
- if (channel.size() - entry.getSize() < entry.getDataOffset()) {
- throw new ArchiveException("Entry size exceeds archive size");
- }
- this.entry = entry;
+ BoundedTarEntryInputStream(final long start, final long remaining, final SeekableByteChannel channel) {
+ super(start, remaining);
+ this.end = start + remaining;
this.channel = channel;
}
@Override
protected int read(final long pos, final ByteBuffer buf) throws IOException {
- if (entryOffset >= entry.getRealSize()) {
- return -1;
- }
- final int totalRead;
- if (entry.isSparse()) {
- totalRead = readSparse(entryOffset, buf, buf.limit());
- } else {
- totalRead = readArchive(pos, buf);
- }
+ Objects.requireNonNull(buf, "ByteBuffer");
+ // The caller ensures that [pos, pos + buf.remaining()] is within [start, end]
+ channel.position(pos);
+ final int totalRead = channel.read(buf);
if (totalRead == -1) {
- if (buf.array().length > 0) {
- throw new ArchiveException("Truncated TAR archive");
+ if (buf.remaining() > 0) {
+ throw new EOFException(String.format("Truncated TAR archive: Expected at least %d bytes, but got only %d bytes",
+ end, channel.position()));
}
+ // Marks the TarFile as having reached EOF.
setAtEOF(true);
} else {
- entryOffset += totalRead;
buf.flip();
}
return totalRead;
}
-
- private int readArchive(final long pos, final ByteBuffer buf) throws IOException {
- channel.position(pos);
- return channel.read(buf);
- }
-
- private int readSparse(final long pos, final ByteBuffer buf, final int numToRead) throws IOException {
- // if there are no actual input streams, just read from the original archive
- final List entrySparseInputStreams = sparseInputStreams.get(entry.getName());
- if (entrySparseInputStreams == null || entrySparseInputStreams.isEmpty()) {
- return readArchive(entry.getDataOffset() + pos, buf);
- }
- if (currentSparseInputStreamIndex >= entrySparseInputStreams.size()) {
- return -1;
- }
- final InputStream currentInputStream = entrySparseInputStreams.get(currentSparseInputStreamIndex);
- final byte[] bufArray = new byte[numToRead];
- final int readLen = currentInputStream.read(bufArray);
- if (readLen != -1) {
- buf.put(bufArray, 0, readLen);
- }
- // if the current input stream is the last input stream,
- // just return the number of bytes read from current input stream
- if (currentSparseInputStreamIndex == entrySparseInputStreams.size() - 1) {
- return readLen;
- }
- // if EOF of current input stream is meet, open a new input stream and recursively call read
- if (readLen == -1) {
- currentSparseInputStreamIndex++;
- return readSparse(pos, buf, numToRead);
- }
- // if the rest data of current input stream is not long enough, open a new input stream
- // and recursively call read
- if (readLen < numToRead) {
- currentSparseInputStreamIndex++;
- final int readLenOfNext = readSparse(pos + readLen, buf, numToRead - readLen);
- if (readLenOfNext == -1) {
- return readLen;
- }
- return readLen + readLenOfNext;
- }
- // if the rest data of current input stream is enough(which means readLen == len), just return readLen
- return readLen;
- }
}
// @formatter:off
@@ -159,32 +113,18 @@ private int readSparse(final long pos, final ByteBuffer buf, final int numToRead
*/
// @formatter:on
public static final class Builder extends AbstractTarBuilder {
-
- private SeekableByteChannel channel;
-
/**
* Constructs a new instance.
*/
private Builder() {
- // empty
+ // Default options
+ setOpenOptions(StandardOpenOption.READ);
}
@Override
public TarFile get() throws IOException {
return new TarFile(this);
}
-
- /**
- * Sets the SeekableByteChannel.
- *
- * @param channel the SeekableByteChannel.
- * @return {@code this} instance.
- */
- public Builder setSeekableByteChannel(final SeekableByteChannel channel) {
- this.channel = channel;
- return asThis();
- }
-
}
/**
@@ -233,14 +173,32 @@ public static Builder builder() {
private final Map> sparseInputStreams = new HashMap<>();
+ private final int maxEntryNameLength;
+
private TarFile(final Builder builder) throws IOException {
- this.archive = builder.channel != null ? builder.channel : Files.newByteChannel(builder.getPath());
- this.zipEncoding = ZipEncodingHelper.getZipEncoding(builder.getCharset());
- this.recordSize = builder.getRecordSize();
- this.recordBuffer = ByteBuffer.allocate(this.recordSize);
- this.blockSize = builder.getBlockSize();
- this.lenient = builder.isLenient();
- forEach(entries::add);
+ this.archive = builder.getChannel(SeekableByteChannel.class);
+ try {
+ this.zipEncoding = ZipEncodingHelper.getZipEncoding(builder.getCharset());
+ this.recordSize = builder.getRecordSize();
+ this.recordBuffer = ByteBuffer.allocate(this.recordSize);
+ this.blockSize = builder.getBlockSize();
+ this.lenient = builder.isLenient();
+ this.maxEntryNameLength = builder.getMaxEntryNameLength();
+ // Populate `entries` explicitly here instead of using `forEach`/`stream`,
+ // because both rely on `entries` internally.
+ // Using them would cause a self-referential loop and leave `entries` empty.
+ TarArchiveEntry entry;
+ while ((entry = getNextTarEntry()) != null) {
+ entries.add(entry);
+ }
+ } catch (final IOException ex) {
+ try {
+ this.archive.close();
+ } catch (final IOException e) {
+ ex.addSuppressed(e);
+ }
+ throw ex;
+ }
}
/**
@@ -252,7 +210,7 @@ private TarFile(final Builder builder) throws IOException {
*/
@Deprecated
public TarFile(final byte[] content) throws IOException {
- this(new SeekableInMemoryByteChannel(content));
+ this(builder().setByteArray(content));
}
/**
@@ -266,7 +224,7 @@ public TarFile(final byte[] content) throws IOException {
*/
@Deprecated
public TarFile(final byte[] content, final boolean lenient) throws IOException {
- this(new SeekableInMemoryByteChannel(content), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, null, lenient);
+ this(builder().setByteArray(content).setLenient(lenient));
}
/**
@@ -279,7 +237,7 @@ public TarFile(final byte[] content, final boolean lenient) throws IOException {
*/
@Deprecated
public TarFile(final byte[] content, final String encoding) throws IOException {
- this(new SeekableInMemoryByteChannel(content), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, encoding, false);
+ this(builder().setByteArray(content).setCharset(encoding));
}
/**
@@ -291,7 +249,7 @@ public TarFile(final byte[] content, final String encoding) throws IOException {
*/
@Deprecated
public TarFile(final File archive) throws IOException {
- this(archive.toPath());
+ this(builder().setFile(archive));
}
/**
@@ -305,7 +263,7 @@ public TarFile(final File archive) throws IOException {
*/
@Deprecated
public TarFile(final File archive, final boolean lenient) throws IOException {
- this(archive.toPath(), lenient);
+ this(builder().setFile(archive).setLenient(lenient));
}
/**
@@ -318,7 +276,7 @@ public TarFile(final File archive, final boolean lenient) throws IOException {
*/
@Deprecated
public TarFile(final File archive, final String encoding) throws IOException {
- this(archive.toPath(), encoding);
+ this(builder().setFile(archive).setCharset(encoding));
}
/**
@@ -328,7 +286,7 @@ public TarFile(final File archive, final String encoding) throws IOException {
* @throws IOException when reading the tar archive fails.
*/
public TarFile(final Path archivePath) throws IOException {
- this(Files.newByteChannel(archivePath), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, null, false);
+ this(builder().setPath(archivePath));
}
/**
@@ -342,7 +300,7 @@ public TarFile(final Path archivePath) throws IOException {
*/
@Deprecated
public TarFile(final Path archivePath, final boolean lenient) throws IOException {
- this(Files.newByteChannel(archivePath), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, null, lenient);
+ this(builder().setPath(archivePath).setLenient(lenient));
}
/**
@@ -355,7 +313,7 @@ public TarFile(final Path archivePath, final boolean lenient) throws IOException
*/
@Deprecated
public TarFile(final Path archivePath, final String encoding) throws IOException {
- this(Files.newByteChannel(archivePath), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, encoding, false);
+ this(builder().setPath(archivePath).setCharset(encoding));
}
/**
@@ -367,7 +325,7 @@ public TarFile(final Path archivePath, final String encoding) throws IOException
*/
@Deprecated
public TarFile(final SeekableByteChannel content) throws IOException {
- this(content, TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, null, false);
+ this(builder().setChannel(content));
}
/**
@@ -385,7 +343,7 @@ public TarFile(final SeekableByteChannel content) throws IOException {
@Deprecated
public TarFile(final SeekableByteChannel archive, final int blockSize, final int recordSize, final String encoding, final boolean lenient)
throws IOException {
- this(builder().setSeekableByteChannel(archive).setBlockSize(blockSize).setRecordSize(recordSize).setCharset(encoding).setLenient(lenient));
+ this(builder().setChannel(archive).setBlockSize(blockSize).setRecordSize(recordSize).setCharset(encoding).setLenient(lenient));
}
/**
@@ -421,7 +379,7 @@ private void buildSparseInputStreams() throws IOException {
// possible integer overflow
throw new ArchiveException("Unreadable TAR archive, sparse block offset or length too big");
}
- streams.add(new BoundedSeekableByteChannelInputStream(start, sparseHeader.getNumbytes(), archive));
+ streams.add(new BoundedTarEntryInputStream(start, sparseHeader.getNumbytes(), archive));
}
offset = sparseHeader.getOffset() + sparseHeader.getNumbytes();
}
@@ -448,7 +406,9 @@ private void consumeRemainderOfLastBlock() throws IOException {
* Gets all TAR Archive Entries from the TarFile.
*
* @return All entries from the tar file.
+ * @deprecated Since 1.29.0, use {@link #entries()} or {@link #stream()}.
*/
+ @Deprecated
public List getEntries() {
return new ArrayList<>(entries);
}
@@ -460,9 +420,16 @@ public List getEntries() {
* @return Input stream of the provided entry.
* @throws IOException Corrupted TAR archive. Can't read entry.
*/
+ @Override
public InputStream getInputStream(final TarArchiveEntry entry) throws IOException {
try {
- return new BoundedTarEntryInputStream(entry, archive);
+ // Sparse entries are composed of multiple fragments: wrap them in a SequenceInputStream
+ if (entry.isSparse()) {
+ final List streams = sparseInputStreams.get(entry.getName());
+ return new SequenceInputStream(streams != null ? Collections.enumeration(streams) : Collections.emptyEnumeration());
+ }
+ // Regular entries are bounded: wrap in BoundedTarEntryInputStream to enforce size and detect premature EOF
+ return new BoundedTarEntryInputStream(entry.getDataOffset(), entry.getSize(), archive);
} catch (final RuntimeException e) {
throw new ArchiveException("Corrupted TAR archive. Can't read entry", (Throwable) e);
}
@@ -484,6 +451,7 @@ private TarArchiveEntry getNextTarEntry() throws IOException {
final List sparseHeaders = new ArrayList<>();
// Handle special tar records
boolean lastWasSpecial = false;
+ InputStream currentStream;
do {
// If there is a current entry, skip any unread data and padding
if (currEntry != null) {
@@ -504,10 +472,11 @@ private TarArchiveEntry getNextTarEntry() throws IOException {
// Parse the header into a new entry
final long position = archive.position();
currEntry = new TarArchiveEntry(globalPaxHeaders, headerBuf.array(), zipEncoding, lenient, position);
+ currentStream = new BoundedTarEntryInputStream(currEntry.getDataOffset(), currEntry.getSize(), archive);
lastWasSpecial = TarUtils.isSpecialTarRecord(currEntry);
if (lastWasSpecial) {
// Handle PAX, GNU long name, or other special records
- TarUtils.handleSpecialTarRecord(getInputStream(currEntry), zipEncoding, currEntry, paxHeaders, sparseHeaders, globalPaxHeaders,
+ TarUtils.handleSpecialTarRecord(currentStream, zipEncoding, maxEntryNameLength, currEntry, paxHeaders, sparseHeaders, globalPaxHeaders,
globalSparseHeaders);
}
} while (lastWasSpecial);
@@ -515,11 +484,21 @@ private TarArchiveEntry getNextTarEntry() throws IOException {
TarUtils.applyPaxHeadersToEntry(currEntry, paxHeaders, sparseHeaders, globalPaxHeaders, globalSparseHeaders);
// Handle sparse files
if (currEntry.isSparse()) {
+ // These sparse formats have the sparse headers in the entry
if (currEntry.isOldGNUSparse()) {
+ // Old GNU sparse format uses extra header blocks for metadata.
+ // These blocks are not included in the entry’s size, so we cannot
+ // rely on BoundedTarEntryInputStream here.
readOldGNUSparse();
+ // Reposition to the start of the entry data to correctly compute the sparse streams
+ currEntry.setDataOffset(archive.position());
} else if (currEntry.isPaxGNU1XSparse()) {
- currEntry.setSparseHeaders(TarUtils.parsePAX1XSparseHeaders(getInputStream(currEntry), recordSize));
- currEntry.setDataOffset(currEntry.getDataOffset() + recordSize);
+ final long position = archive.position();
+ currEntry.setSparseHeaders(TarUtils.parsePAX1XSparseHeaders(currentStream, recordSize));
+ // Adjust the current entry to point to the start of the sparse file data
+ final long sparseHeadersSize = archive.position() - position;
+ currEntry.setSize(currEntry.getSize() - sparseHeadersSize);
+ currEntry.setDataOffset(currEntry.getDataOffset() + sparseHeadersSize);
}
// sparse headers are all done reading, we need to build
// sparse input streams using these sparse headers
@@ -573,38 +552,6 @@ private boolean isEOFRecord(final ByteBuffer headerBuf) {
return headerBuf == null || ArchiveUtils.isArrayZero(headerBuf.array(), recordSize);
}
- @Override
- public IOIterator iterator() {
- return new IOIterator() {
-
- private TarArchiveEntry next;
-
- @Override
- public boolean hasNext() throws IOException {
- if (next == null) {
- next = getNextTarEntry();
- }
- return next != null;
- }
-
- @Override
- public TarArchiveEntry next() throws IOException {
- if (next == null) {
- next = getNextTarEntry();
- }
- final TarArchiveEntry tmp = next;
- next = null;
- return tmp;
- }
-
- @Override
- public Iterator unwrap() {
- return null;
- }
-
- };
- }
-
/**
* Adds the sparse chunks from the current entry to the sparse chunks, including any additional sparse entries following the current entry.
*
@@ -620,12 +567,8 @@ private void readOldGNUSparse() throws IOException {
}
entry = new TarArchiveSparseEntry(headerBuf.array());
currEntry.getSparseHeaders().addAll(entry.getSparseHeaders());
- currEntry.setDataOffset(currEntry.getDataOffset() + recordSize);
} while (entry.isExtended());
}
- // sparse headers are all done reading, we need to build
- // sparse input streams using these sparse headers
- buildSparseInputStreams();
}
/**
@@ -671,13 +614,22 @@ protected final void setAtEOF(final boolean eof) {
*/
private void skipRecordPadding() throws IOException {
if (!isDirectory() && currEntry.getSize() > 0 && currEntry.getSize() % recordSize != 0) {
- final long numRecords = currEntry.getSize() / recordSize + 1;
- final long padding = numRecords * recordSize - currEntry.getSize();
+ final long padding = recordSize - (currEntry.getSize() % recordSize);
repositionForwardBy(padding);
throwExceptionIfPositionIsNotInArchive();
}
}
+ /**
+ * {@inheritDoc}
+ *
+ * @since 1.29.0
+ */
+ @Override
+ public IOStream extends TarArchiveEntry> stream() {
+ return IOStream.of(entries);
+ }
+
/**
* Checks if the current position of the SeekableByteChannel is in the archive.
*
@@ -685,7 +637,7 @@ private void skipRecordPadding() throws IOException {
*/
private void throwExceptionIfPositionIsNotInArchive() throws IOException {
if (archive.size() < archive.position()) {
- throw new ArchiveException("Truncated TAR archive");
+ throw new EOFException("Truncated TAR archive: Archive should be at least " + archive.position() + " bytes but was " + archive.size() + " bytes");
}
}
@@ -710,12 +662,4 @@ private void tryToConsumeSecondEOFRecord() throws IOException {
}
}
}
-
- @Override
- public Iterable unwrap() {
- // Commons IO 2.21.0:
- // return asIterable();
- return null;
- }
-
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
index 3938ce2f3ba..0dcf31bec12 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
@@ -18,6 +18,7 @@
*/
package org.apache.commons.compress.archivers.tar;
+import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
@@ -33,23 +34,21 @@
import java.util.Map;
import java.util.regex.Pattern;
+import org.apache.commons.compress.MemoryLimitException;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.zip.ZipEncoding;
import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
import org.apache.commons.compress.utils.ArchiveUtils;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.ParsingUtils;
-import org.apache.commons.io.input.BoundedInputStream;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
-import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
/**
* This class provides static utility methods to work with byte streams.
*
* @Immutable
*/
-// CheckStyle:HideUtilityClassConstructorCheck OFF (bc)
-public class TarUtils {
+public final class TarUtils {
private static final Pattern HEADER_STRINGS_PATTERN = Pattern.compile(",");
@@ -354,6 +353,7 @@ public static void formatUnsignedOctalString(final long value, final byte[] buff
*
* @param input the input stream from which to read the special tar entry content.
* @param encoding the encoding to use for reading names.
+ * @param maxEntryNameLength the maximum allowed length for entry names.
* @param entry the tar entry to handle.
* @param paxHeaders the map to update with PAX headers.
* @param sparseHeaders the list to update with sparse headers.
@@ -361,27 +361,27 @@ public static void formatUnsignedOctalString(final long value, final byte[] buff
* @param globalSparseHeaders the list to update with global sparse headers.
* @throws IOException if an I/O error occurs while reading the entry.
*/
- static void handleSpecialTarRecord(final InputStream input, final ZipEncoding encoding, final TarArchiveEntry entry, final Map paxHeaders,
- final List sparseHeaders, final Map globalPaxHeaders,
+ static void handleSpecialTarRecord(final InputStream input, final ZipEncoding encoding, final int maxEntryNameLength, final TarArchiveEntry entry,
+ final Map paxHeaders, final List sparseHeaders, final Map globalPaxHeaders,
final List globalSparseHeaders) throws IOException {
if (entry.isGNULongLinkEntry()) {
// GNU long link entry: read and store the link path
- final String longLinkName = readLongName(input, encoding, entry);
+ final String longLinkName = readLongName(input, encoding, maxEntryNameLength, entry);
paxHeaders.put("linkpath", longLinkName);
} else if (entry.isGNULongNameEntry()) {
// GNU long name entry: read and store the file path
- final String longName = readLongName(input, encoding, entry);
+ final String longName = readLongName(input, encoding, maxEntryNameLength, entry);
paxHeaders.put("path", longName);
} else if (entry.isGlobalPaxHeader()) {
// Global PAX header: clear and update global PAX and sparse headers
globalSparseHeaders.clear();
globalPaxHeaders.clear();
- globalPaxHeaders.putAll(parsePaxHeaders(input, globalSparseHeaders, globalPaxHeaders, entry.getSize()));
+ globalPaxHeaders.putAll(parsePaxHeaders(input, globalPaxHeaders, entry.getSize(), maxEntryNameLength, globalSparseHeaders));
} else if (entry.isPaxHeader()) {
// PAX header: clear and update local PAX and sparse headers, parse GNU sparse headers if present
sparseHeaders.clear();
paxHeaders.clear();
- paxHeaders.putAll(parsePaxHeaders(input, sparseHeaders, globalPaxHeaders, entry.getSize()));
+ paxHeaders.putAll(parsePaxHeaders(input, globalPaxHeaders, entry.getSize(), maxEntryNameLength, sparseHeaders));
if (paxHeaders.containsKey(TarGnuSparseKeys.MAP)) {
sparseHeaders.addAll(parseFromPAX01SparseHeaders(paxHeaders.get(TarGnuSparseKeys.MAP)));
}
@@ -461,7 +461,7 @@ public static boolean parseBoolean(final byte[] buffer, final int offset) {
* @throws IOException Corrupted TAR archive.
* @since 1.21
*/
- protected static List parseFromPAX01SparseHeaders(final String sparseMap) throws IOException {
+ static List parseFromPAX01SparseHeaders(final String sparseMap) throws IOException {
final List sparseHeaders = new ArrayList<>();
final String[] sparseHeaderStrings = HEADER_STRINGS_PATTERN.split(sparseMap);
if (sparseHeaderStrings.length % 2 == 1) {
@@ -614,30 +614,6 @@ public static long parseOctalOrBinary(final byte[] buffer, final int offset, fin
return parseBinaryBigInteger(buffer, offset, length, negative);
}
- /**
- * For PAX Format 0.1, the sparse headers are stored in a single variable : GNU.sparse.map
- *
- *
- * GNU.sparse.map: Map of non-null data chunks. It is a string consisting of comma-separated values "offset,size[,offset-1,size-1...]"
- *
- *
- * Will internally invoke {@link #parseFromPAX01SparseHeaders} and map IOExceptions to a RzuntimeException, You should use
- * {@link #parseFromPAX01SparseHeaders} directly instead.
- *
- *
- * @param sparseMap the sparse map string consisting of comma-separated values "offset,size[,offset-1,size-1...]".
- * @return sparse headers parsed from sparse map.
- * @deprecated use #parseFromPAX01SparseHeaders instead.
- */
- @Deprecated
- protected static List parsePAX01SparseHeaders(final String sparseMap) {
- try {
- return parseFromPAX01SparseHeaders(sparseMap);
- } catch (final IOException ex) {
- throw new UncheckedIOException(ex.getMessage(), ex);
- }
- }
-
/**
* For PAX Format 1.X: The sparse map itself is stored in the file data block, preceding the actual file data. It consists of a series of decimal numbers
* delimited by newlines. The map is padded with nulls to the nearest block boundary. The first number gives the number of entries in the map. Following are
@@ -648,7 +624,7 @@ protected static List parsePAX01SparseHeaders(final Stri
* @return sparse headers.
* @throws IOException if an I/O error occurs.
*/
- protected static List parsePAX1XSparseHeaders(final InputStream inputStream, final int recordSize) throws IOException {
+ static List parsePAX1XSparseHeaders(final InputStream inputStream, final int recordSize) throws IOException {
// for 1.X PAX Headers
final List sparseHeaders = new ArrayList<>();
long bytesRead = 0;
@@ -681,65 +657,36 @@ protected static List parsePAX1XSparseHeaders(final Inpu
return sparseHeaders;
}
- /**
- * For PAX Format 0.0, the sparse headers(GNU.sparse.offset and GNU.sparse.numbytes) may appear multi times, and they look like:
- *
- *
- * GNU.sparse.size=size
- * GNU.sparse.numblocks=numblocks
- * repeat numblocks times
- * GNU.sparse.offset=offset
- * GNU.sparse.numbytes=numbytes
- * end repeat
- *
- *
- * For PAX Format 0.1, the sparse headers are stored in a single variable: GNU.sparse.map
- *
- *
- * GNU.sparse.map: Map of non-null data chunks. It is a string consisting of comma-separated values "offset,size[,offset-1,size-1...]"
- *
- *
- * @param inputStream input stream to read keys and values.
- * @param sparseHeaders used in PAX Format 0.0 & 0.1, as it may appear multiple times, the sparse headers need to be stored in an array, not a map.
- * @param globalPaxHeaders global PAX headers of the tar archive.
- * @return map of PAX headers values found inside the current (local or global) PAX headers tar entry.
- * @throws IOException if an I/O error occurs.
- * @deprecated use the four-arg version instead.
- */
- @Deprecated
- protected static Map parsePaxHeaders(final InputStream inputStream, final List sparseHeaders,
- final Map globalPaxHeaders) throws IOException {
- return parsePaxHeaders(inputStream, sparseHeaders, globalPaxHeaders, -1);
- }
/**
- * For PAX Format 0.0, the sparse headers(GNU.sparse.offset and GNU.sparse.numbytes) may appear multi times, and they look like:
- *
- *
- * GNU.sparse.size=size
- * GNU.sparse.numblocks=numblocks
- * repeat numblocks times
- * GNU.sparse.offset=offset
- * GNU.sparse.numbytes=numbytes
- * end repeat
- *
- *
- * For PAX Format 0.1, the sparse headers are stored in a single variable : GNU.sparse.map
- *
- *
- * GNU.sparse.map: Map of non-null data chunks. It is a string consisting of comma-separated values "offset,size[,offset-1,size-1...]"
- *
- *
- * @param inputStream input stream to read keys and values
- * @param sparseHeaders used in PAX Format 0.0 & 0.1, as it may appear multiple times, the sparse headers need to be stored in an array, not a map
- * @param globalPaxHeaders global PAX headers of the tar archive
- * @param headerSize total size of the PAX header, will be ignored if negative
- * @return map of PAX headers values found inside the current (local or global) PAX headers tar entry.
- * @throws IOException if an I/O error occurs.
- * @since 1.21
+ * Parses and processes the contents of a PAX header block.
+ *
+ * This method reads key–value pairs from the given input stream, applies
+ * them to the provided global PAX headers, and performs additional handling:
+ *
+ *
+ * - Validates that entry path lengths do not exceed the specified maximum.
+ * - Extracts GNU sparse headers (format 0.0) if present, adding them to the
+ * {@code sparseHeaders} list. These headers may occur multiple times and
+ * do not follow the key–value format of standard PAX entries.
+ *
+ *
+ * @param inputStream The input stream providing PAX header data
+ * @param globalPaxHeaders The global PAX headers of the tar archive
+ * @param headerSize The total size of the PAX header block; always non-negative
+ * @param maxEntryPathLength The maximum permitted length for entry paths
+ * @param sparseHeaders Output list to collect any GNU sparse 0.0 headers found
+ * @return A map of PAX headers merged with the supplied global headers
+ * @throws EOFException If the stream ends unexpectedly
+ * @throws MemoryLimitException If the headers exceed memory limits
+ * @throws ArchiveException If a header is malformed or contains invalid data
+ * @throws IOException If an I/O error occurs while reading
*/
- protected static Map parsePaxHeaders(final InputStream inputStream, final List sparseHeaders,
- final Map globalPaxHeaders, final long headerSize) throws IOException {
+ static Map parsePaxHeaders(final InputStream inputStream, final Map globalPaxHeaders, final long headerSize,
+ final int maxEntryPathLength, final List super TarArchiveStructSparse> sparseHeaders) throws IOException {
+ assert headerSize >= 0 : "headerSize must be non-negative";
+ // Check if there is enough memory to store the headers
+ MemoryLimitException.checkBytes(headerSize, Long.MAX_VALUE);
final Map headers = new HashMap<>(globalPaxHeaders);
Long offset = null;
// Format is "length keyword=value\n";
@@ -760,23 +707,26 @@ protected static Map parsePaxHeaders(final InputStream inputStre
while ((ch = inputStream.read()) != -1) {
read++;
totalRead++;
- if (totalRead < 0 || headerSize >= 0 && totalRead >= headerSize) {
+ if (totalRead < 0 || totalRead >= headerSize) {
break;
}
if (ch == '=') { // end of keyword
final String keyword = coll.toString(StandardCharsets.UTF_8);
// Get rest of entry
final int restLen = len - read;
+ // Validate entry length
+ // 1. Ignore empty keywords
if (restLen <= 1) { // only NL
headers.remove(keyword);
- } else if (headerSize >= 0 && restLen > headerSize - totalRead) {
+ // 2. Entry length exceeds header size
+ } else if (restLen > headerSize - totalRead) {
throw new ArchiveException("PAX header value size %,d exceeds size of header record.", restLen);
} else {
- final byte[] rest = IOUtils.readRange(inputStream, restLen);
- final int got = rest.length;
- if (got != restLen) {
- throw new ArchiveException("Failed to read PAX header: Expected %,d bytes, read %,d.", restLen, got);
+ // 3. Entry length exceeds configurable file and link name limits
+ if (TarArchiveEntry.PAX_NAME_KEY.equals(keyword) || TarArchiveEntry.PAX_LINK_NAME_KEY.equals(keyword)) {
+ ArchiveUtils.checkEntryNameLength(restLen - 1, maxEntryPathLength, "TAR");
}
+ final byte[] rest = IOUtils.toByteArray(inputStream, restLen, IOUtils.DEFAULT_BUFFER_SIZE);
totalRead += restLen;
// Drop trailing NL
if (rest[restLen - 1] != '\n') {
@@ -791,8 +741,8 @@ protected static Map parsePaxHeaders(final InputStream inputStre
sparseHeaders.add(new TarArchiveStructSparse(offset, 0));
}
try {
- offset = Long.valueOf(value);
- } catch (final NumberFormatException ex) {
+ offset = ParsingUtils.parseLongValue(value);
+ } catch (final IOException ex) {
throw new ArchiveException("Failed to read PAX header: Offset %s contains a non-numeric value.",
TarGnuSparseKeys.OFFSET);
}
@@ -806,7 +756,13 @@ protected static Map parsePaxHeaders(final InputStream inputStre
throw new ArchiveException("Failed to read PAX header: %s is expected before GNU.sparse.numbytes shows up.",
TarGnuSparseKeys.OFFSET);
}
- final long numbytes = ParsingUtils.parseLongValue(value);
+ final long numbytes;
+ try {
+ numbytes = ParsingUtils.parseLongValue(value);
+ } catch (final IOException ex) {
+ throw new ArchiveException("Failed to read PAX header: Numbytes %s contains a non-numeric value.",
+ TarGnuSparseKeys.NUMBYTES);
+ }
if (numbytes < 0) {
throw new ArchiveException("Failed to read PAX header: %s contains negative value.", TarGnuSparseKeys.NUMBYTES);
}
@@ -888,30 +844,18 @@ private static long[] readLineOfNumberForPax1x(final InputStream inputStream) th
* @throws IOException if an I/O error occurs or the entry is truncated.
* @throws ArchiveException if the entry size is invalid.
*/
- static String readLongName(final InputStream input, final ZipEncoding encoding, final TarArchiveEntry entry)
- throws IOException {
- final long size = entry.getSize();
- // The encoding requires a byte array, whose size must be a positive int.
- if (size > Integer.MAX_VALUE) {
- throw new ArchiveException("Invalid long name entry: size %,d exceeds maximum allowed.", entry.getSize());
- }
- // Read the long name incrementally to limit memory allocation in case of a corrupted entry.
- final BoundedInputStream boundedInput = BoundedInputStream.builder()
- .setInputStream(input)
- .setMaxCount(size)
- .setPropagateClose(false)
- .get();
- final UnsynchronizedByteArrayOutputStream outputStream = UnsynchronizedByteArrayOutputStream.builder().get();
- final long read = org.apache.commons.io.IOUtils.copyLarge(boundedInput, outputStream);
- if (read != size) {
- throw new ArchiveException("Truncated long name entry: expected %,d bytes, read %,d bytes.", size, read);
- }
- final byte[] name = outputStream.toByteArray();
- int length = name.length;
- while (length > 0 && name[length - 1] == 0) {
- length--;
- }
- return encoding.decode(Arrays.copyOf(name, length));
+ static String readLongName(final InputStream input, final ZipEncoding encoding, final int maxEntryNameLength,
+ final TarArchiveEntry entry) throws IOException {
+ final int declaredLength = ArchiveUtils.checkEntryNameLength(entry.getSize(), maxEntryNameLength, "TAR");
+ final byte[] name = org.apache.commons.compress.utils.IOUtils.readRange(input, declaredLength);
+ int actualLength = name.length;
+ if (actualLength != declaredLength) {
+ throw new EOFException(String.format("Truncated long name entry: Expected %,d bytes, read %,d bytes.", declaredLength, actualLength));
+ }
+ while (actualLength > 0 && name[actualLength - 1] == 0) {
+ actualLength--;
+ }
+ return encoding.decode(Arrays.copyOf(name, actualLength));
}
/**
@@ -927,7 +871,7 @@ static List readSparseStructs(final byte[] buffer, final
throw new ArchiveException("Corrupted TAR archive: Sparse entry with negative offset.");
}
if (sparseHeader.getNumbytes() < 0) {
- throw new ArchiveException("Corrupted TAR archive: sparse entry with negative numbytes.");
+ throw new ArchiveException("Corrupted TAR archive: Sparse entry with negative numbytes.");
}
sparseHeaders.add(sparseHeader);
} catch (final IllegalArgumentException e) {
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/BinaryTree.java b/src/main/java/org/apache/commons/compress/archivers/zip/BinaryTree.java
index 0df1cebd1d5..b87d1670745 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/BinaryTree.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/BinaryTree.java
@@ -24,7 +24,6 @@
import java.io.InputStream;
import org.apache.commons.compress.archivers.ArchiveException;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.ArrayFill;
/**
@@ -53,7 +52,7 @@ static BinaryTree decode(final InputStream inputStream, final int totalNumberOfV
throw new ArchiveException("Cannot read the size of the encoded tree, unexpected end of stream");
}
- final byte[] encodedTree = IOUtils.readRange(inputStream, size);
+ final byte[] encodedTree = org.apache.commons.compress.utils.IOUtils.readRange(inputStream, size);
if (encodedTree.length != size) {
throw new EOFException();
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java
index 59879f562d4..c47650c5685 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java
@@ -45,7 +45,7 @@
* Extension that adds better handling of extra fields and provides access to the internal and external file attributes.
*
*
- * The extra data is expected to follow the recommendation of APPNOTE.TXT:
+ * The extra data is expected to follow the recommendation of APPNOTE.TXT:
*
*
* - the extra byte array consists of a sequence of extra fields
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
index d02e625688a..7c77fefcc44 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
@@ -42,6 +42,7 @@
import java.util.zip.ZipException;
import org.apache.commons.compress.MemoryLimitException;
+import org.apache.commons.compress.archivers.AbstractArchiveBuilder;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
@@ -49,8 +50,8 @@
import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
import org.apache.commons.compress.utils.ArchiveUtils;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.InputStreamStatistics;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BoundedInputStream;
import org.apache.commons.lang3.ArrayUtils;
@@ -85,7 +86,7 @@ public class ZipArchiveInputStream extends ArchiveInputStream i
* @since 1.29.0
*/
public abstract static class AbstractBuilder>
- extends ArchiveInputStream.AbstractBuilder {
+ extends AbstractArchiveBuilder {
private boolean useUnicodeExtraFields = true;
private boolean supportStoredEntryDataDescriptor;
@@ -408,62 +409,70 @@ public static boolean matches(final byte[] buffer, final int length) {
* @since 1.29.0
*/
protected ZipArchiveInputStream(final AbstractBuilder, ?> builder) throws IOException {
- this(builder.getInputStream(), builder);
+ super(builder);
+ this.in = new PushbackInputStream(in, buf.capacity());
+ this.zipEncoding = ZipEncodingHelper.getZipEncoding(builder.getCharset());
+ this.useUnicodeExtraFields = builder.isUseUnicodeExtraFields();
+ this.supportStoredEntryDataDescriptor = builder.isSupportStoredEntryDataDescriptor();
+ this.skipSplitSignature = builder.isSkipSplitSignature();
+ // haven't read anything so far
+ buf.limit(0);
}
/**
* Constructs an instance using UTF-8 encoding.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the stream to wrap.
+ * @throws IOException if an I/O error occurs.
*/
- public ZipArchiveInputStream(final InputStream inputStream) {
- this(inputStream, builder());
- }
-
- private ZipArchiveInputStream(final InputStream inputStream, final AbstractBuilder, ?> builder) {
- super(inputStream, builder.getCharset());
- this.in = new PushbackInputStream(inputStream, buf.capacity());
- this.zipEncoding = ZipEncodingHelper.getZipEncoding(builder.getCharset());
- this.useUnicodeExtraFields = builder.isUseUnicodeExtraFields();
- this.supportStoredEntryDataDescriptor = builder.isSupportStoredEntryDataDescriptor();
- this.skipSplitSignature = builder.isSkipSplitSignature();
- // haven't read anything so far
- buf.limit(0);
+ public ZipArchiveInputStream(final InputStream inputStream) throws IOException {
+ this(builder().setInputStream(inputStream));
}
/**
* Constructs an instance using the specified encoding.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the stream to wrap.
* @param encoding the encoding to use for file names, use null for the platform's default encoding.
+ * @throws IOException if an I/O error occurs.
* @since 1.5
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public ZipArchiveInputStream(final InputStream inputStream, final String encoding) {
- this(inputStream, builder().setCharset(encoding));
+ public ZipArchiveInputStream(final InputStream inputStream, final String encoding) throws IOException {
+ this(builder().setInputStream(inputStream).setCharset(encoding));
}
/**
* Constructs an instance using the specified encoding.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the stream to wrap.
* @param encoding the encoding to use for file names, use null for the platform's default encoding.
* @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
+ * @throws IOException if an I/O error occurs.
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@Deprecated
- public ZipArchiveInputStream(final InputStream inputStream, final String encoding, final boolean useUnicodeExtraFields) {
- this(inputStream, builder().setCharset(encoding).setUseUnicodeExtraFields(useUnicodeExtraFields));
+ public ZipArchiveInputStream(final InputStream inputStream, final String encoding, final boolean useUnicodeExtraFields) throws IOException {
+ this(builder().setInputStream(inputStream).setCharset(encoding).setUseUnicodeExtraFields(useUnicodeExtraFields));
}
/**
* Constructs an instance using the specified encoding.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the stream to wrap.
* @param encoding the encoding to use for file names, use null for the platform's default encoding.
* @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
* @param supportStoredEntryDataDescriptor whether the stream will try to read STORED entries that use a data descriptor.
+ * @throws IOException if an I/O error occurs.
* @since 1.1
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@@ -472,9 +481,10 @@ public ZipArchiveInputStream(
final InputStream inputStream,
final String encoding,
final boolean useUnicodeExtraFields,
- final boolean supportStoredEntryDataDescriptor) {
+ final boolean supportStoredEntryDataDescriptor) throws IOException {
// @formatter:off
- this(inputStream, builder()
+ this(builder()
+ .setInputStream(inputStream)
.setCharset(encoding)
.setUseUnicodeExtraFields(useUnicodeExtraFields)
.setSupportStoredEntryDataDescriptor(supportStoredEntryDataDescriptor));
@@ -484,12 +494,15 @@ public ZipArchiveInputStream(
/**
* Constructs an instance using the specified encoding.
*
+ * Since 1.29.0: throws {@link IOException}.
+ *
* @param inputStream the stream to wrap.
* @param encoding the encoding to use for file names, use null for the platform's default encoding.
* @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
* @param supportStoredEntryDataDescriptor whether the stream will try to read STORED entries that use a data descriptor.
* @param skipSplitSignature Whether the stream will try to skip the zip split signature(08074B50) at the beginning.
* You will need to set this to true if you want to read a split archive.
+ * @throws IOException if an I/O error occurs.
* @since 1.20
* @deprecated Since 1.29.0, use {@link #builder()}.
*/
@@ -499,9 +512,10 @@ public ZipArchiveInputStream(
final String encoding,
final boolean useUnicodeExtraFields,
final boolean supportStoredEntryDataDescriptor,
- final boolean skipSplitSignature) {
+ final boolean skipSplitSignature) throws IOException {
// @formatter:off
- this(inputStream, builder()
+ this(builder()
+ .setInputStream(inputStream)
.setCharset(encoding)
.setUseUnicodeExtraFields(useUnicodeExtraFields)
.setSupportStoredEntryDataDescriptor(supportStoredEntryDataDescriptor)
@@ -861,7 +875,7 @@ public ZipArchiveEntry getNextZipEntry() throws IOException {
} else {
off += 3 * WORD;
}
- final int fileNameLen = ZipShort.getValue(lfhBuf, off);
+ final int fileNameLen = ArchiveUtils.checkEntryNameLength(ZipShort.getValue(lfhBuf, off), getMaxEntryNameLength(), "ZIP");
off += SHORT;
final int extraLen = ZipShort.getValue(lfhBuf, off);
final byte[] fileName = readRange(fileNameLen);
@@ -1033,6 +1047,7 @@ private void pushback(final byte[] buf, final int offset, final int length) thro
@Override
public int read(final byte[] buffer, final int offset, final int length) throws IOException {
+ IOUtils.checkFromIndexSize(buffer, offset, length);
if (length == 0) {
return 0;
}
@@ -1216,7 +1231,7 @@ private void readFully(final byte[] b) throws IOException {
private void readFully(final byte[] b, final int off) throws IOException {
final int len = b.length - off;
- final int count = IOUtils.readFully(in, b, off, len);
+ final int count = IOUtils.read(in, b, off, len);
count(count);
if (count < len) {
throw new EOFException();
@@ -1253,7 +1268,7 @@ private int readOneByte() throws IOException {
}
private byte[] readRange(final int len) throws IOException {
- final byte[] ret = IOUtils.readRange(in, len);
+ final byte[] ret = org.apache.commons.compress.utils.IOUtils.readRange(in, len);
count(ret.length);
if (ret.length < len) {
throw new EOFException();
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java
index 07d3492c5aa..4cbdf4f1e47 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java
@@ -41,6 +41,7 @@
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.io.Charsets;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
/**
@@ -1568,10 +1569,14 @@ private int versionNeededToExtractMethod(final int zipMethod) {
* @param b the byte array to write.
* @param offset the start position to write from.
* @param length the number of bytes to write.
+ * @throws NullPointerException if {@code b} is null
+ * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative,
+ * or if {@code offset + length} is greater than {@code b.length}.
* @throws IOException on error.
*/
@Override
public void write(final byte[] b, final int offset, final int length) throws IOException {
+ IOUtils.checkFromIndexSize(b, offset, length);
if (entry == null) {
throw new IllegalStateException("No current entry");
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
index 3e28361c33d..e0cd181e8e4 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
@@ -20,7 +20,6 @@
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
-import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
@@ -39,7 +38,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
-import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
@@ -51,21 +49,20 @@
import java.util.zip.Inflater;
import java.util.zip.ZipException;
+import org.apache.commons.compress.archivers.AbstractArchiveBuilder;
import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.compress.archivers.ArchiveFile;
import org.apache.commons.compress.archivers.EntryStreamOffsets;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
+import org.apache.commons.compress.utils.ArchiveUtils;
import org.apache.commons.compress.utils.BoundedArchiveInputStream;
import org.apache.commons.compress.utils.BoundedSeekableByteChannelInputStream;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.InputStreamStatistics;
-import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
-import org.apache.commons.io.Charsets;
import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.io.build.AbstractOrigin.ByteArrayOrigin;
-import org.apache.commons.io.build.AbstractStreamBuilder;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.function.IOFunction;
import org.apache.commons.io.function.IOStream;
import org.apache.commons.io.input.BoundedInputStream;
@@ -92,7 +89,7 @@
* - close is allowed to throw IOException.
*
*/
-public class ZipFile implements Closeable {
+public class ZipFile implements ArchiveFile {
/**
* Lock-free implementation of BoundedInputStream. The implementation uses positioned reads on the underlying archive file channel and therefore performs
@@ -132,13 +129,13 @@ protected int read(final long pos, final ByteBuffer buf) throws IOException {
*
* @since 1.26.0
*/
- public static class Builder extends AbstractStreamBuilder {
+ public static class Builder extends AbstractArchiveBuilder {
- static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
- private SeekableByteChannel seekableByteChannel;
+ private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private boolean useUnicodeExtraFields = true;
private boolean ignoreLocalFileHeader;
private long maxNumberOfDisks = 1;
+ private String name;
private IOFunction zstdInputStreamFactory;
/**
@@ -147,30 +144,23 @@ public static class Builder extends AbstractStreamBuilder {
public Builder() {
setCharset(DEFAULT_CHARSET);
setCharsetDefault(DEFAULT_CHARSET);
+ setOpenOptions(StandardOpenOption.READ);
}
@Override
public ZipFile get() throws IOException {
- final SeekableByteChannel actualChannel;
- final String actualDescription;
- if (seekableByteChannel != null) {
- actualChannel = seekableByteChannel;
- actualDescription = actualChannel.getClass().getSimpleName();
- } else if (checkOrigin() instanceof ByteArrayOrigin) {
- actualChannel = new SeekableInMemoryByteChannel(checkOrigin().getByteArray());
- actualDescription = actualChannel.getClass().getSimpleName();
- } else {
- OpenOption[] openOptions = getOpenOptions();
- if (openOptions.length == 0) {
- openOptions = new OpenOption[] { StandardOpenOption.READ };
+ return new ZipFile(this);
+ }
+
+ String getName() {
+ if (name == null) {
+ try {
+ name = getPath().toAbsolutePath().toString();
+ } catch (final UnsupportedOperationException ex) {
+ name = "unknown";
}
- final Path path = getPath();
- actualChannel = openZipChannel(path, maxNumberOfDisks, openOptions);
- actualDescription = path.toString();
}
- final boolean closeOnError = seekableByteChannel != null;
- return new ZipFile(actualChannel, actualDescription, getCharset(), useUnicodeExtraFields, closeOnError, ignoreLocalFileHeader,
- zstdInputStreamFactory);
+ return name;
}
/**
@@ -195,15 +185,21 @@ public Builder setMaxNumberOfDisks(final long maxNumberOfDisks) {
return this;
}
+ Builder setName(final String name) {
+ this.name = name;
+ return this;
+ }
+
/**
* The actual channel, overrides any other input aspects like a File, Path, and so on.
*
* @param seekableByteChannel The actual channel.
* @return {@code this} instance.
+ * @deprecated Since 1.29.0, use {@link #setChannel} instead.
*/
+ @Deprecated
public Builder setSeekableByteChannel(final SeekableByteChannel seekableByteChannel) {
- this.seekableByteChannel = seekableByteChannel;
- return this;
+ return setChannel(seekableByteChannel);
}
/**
@@ -283,10 +279,6 @@ public long getUncompressedCount() {
}
}
- private static final String DEFAULT_CHARSET_NAME = StandardCharsets.UTF_8.name();
-
- private static final EnumSet READ = EnumSet.of(StandardOpenOption.READ);
-
private static final int HASH_SIZE = 509;
static final int NIBLET_MASK = 0x0f;
static final int BYTE_SHIFT = 8;
@@ -508,22 +500,11 @@ public static Builder builder() {
* @param zipFile file to close, can be null
*/
public static void closeQuietly(final ZipFile zipFile) {
- org.apache.commons.io.IOUtils.closeQuietly(zipFile);
- }
-
- /**
- * Creates a new SeekableByteChannel for reading.
- *
- * @param path the path to the file to open or create
- * @return a new seekable byte channel
- * @throws IOException if an I/O error occurs
- */
- private static SeekableByteChannel newReadByteChannel(final Path path) throws IOException {
- return Files.newByteChannel(path, READ);
+ IOUtils.closeQuietly(zipFile);
}
private static SeekableByteChannel openZipChannel(final Path path, final long maxNumberOfDisks, final OpenOption[] openOptions) throws IOException {
- final FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);
+ final FileChannel channel = FileChannel.open(path, openOptions);
try {
final boolean is64 = positionAtEndOfCentralDirectoryRecord(channel);
final long numberOfDisks;
@@ -570,7 +551,7 @@ private static SeekableByteChannel openZipChannel(final Path path, final long ma
return lowercase;
}).collect(Collectors.toList()), openOptions);
} catch (final Throwable ex) {
- org.apache.commons.io.IOUtils.closeQuietly(channel);
+ IOUtils.closeQuietly(channel);
throw ex;
}
}
@@ -751,6 +732,43 @@ private static boolean tryToLocateSignature(final SeekableByteChannel channel, f
private long firstLocalFileHeaderOffset;
+ private final int maxEntryNameLength;
+
+ private ZipFile(final Builder builder) throws IOException {
+ SeekableByteChannel archive;
+ try {
+ final Path path = builder.getPath();
+ archive = openZipChannel(path, builder.maxNumberOfDisks, builder.getOpenOptions());
+ } catch (final UnsupportedOperationException e) {
+ archive = builder.getChannel(SeekableByteChannel.class);
+ }
+ this.archive = archive;
+ try {
+ this.isSplitZipArchive = this.archive instanceof ZipSplitReadOnlySeekableByteChannel;
+ this.encoding = builder.getCharset();
+ this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
+ this.useUnicodeExtraFields = builder.useUnicodeExtraFields;
+ this.zstdInputStreamFactory = builder.zstdInputStreamFactory;
+ this.maxEntryNameLength = builder.getMaxEntryNameLength();
+ final Map entriesWithoutUTF8Flag = populateFromCentralDirectory();
+ if (!builder.ignoreLocalFileHeader) {
+ resolveLocalFileHeaderData(entriesWithoutUTF8Flag);
+ }
+ fillNameMap();
+ } catch (final IOException e) {
+ final ArchiveException archiveException = e instanceof ArchiveException
+ ? (ArchiveException) e
+ : new ArchiveException("Error reading Zip content from " + builder.getName(), (Throwable) e);
+ this.closed = true;
+ try {
+ this.archive.close();
+ } catch (final IOException ioException) {
+ archiveException.addSuppressed(ioException);
+ }
+ throw archiveException;
+ }
+ }
+
/**
* Opens the given file for reading, assuming "UTF8" for file names.
*
@@ -760,7 +778,7 @@ private static boolean tryToLocateSignature(final SeekableByteChannel channel, f
*/
@Deprecated
public ZipFile(final File file) throws IOException {
- this(file, DEFAULT_CHARSET_NAME);
+ this(builder().setFile(file));
}
/**
@@ -773,7 +791,7 @@ public ZipFile(final File file) throws IOException {
*/
@Deprecated
public ZipFile(final File file, final String encoding) throws IOException {
- this(file.toPath(), encoding, true);
+ this(builder().setFile(file).setCharset(encoding));
}
/**
@@ -787,7 +805,7 @@ public ZipFile(final File file, final String encoding) throws IOException {
*/
@Deprecated
public ZipFile(final File file, final String encoding, final boolean useUnicodeExtraFields) throws IOException {
- this(file.toPath(), encoding, useUnicodeExtraFields, false);
+ this(builder().setFile(file).setCharset(encoding).setUseUnicodeExtraFields(useUnicodeExtraFields));
}
/**
@@ -809,9 +827,8 @@ public ZipFile(final File file, final String encoding, final boolean useUnicodeE
* @deprecated Use {@link Builder#get()}.
*/
@Deprecated
- @SuppressWarnings("resource") // Caller closes
public ZipFile(final File file, final String encoding, final boolean useUnicodeExtraFields, final boolean ignoreLocalFileHeader) throws IOException {
- this(newReadByteChannel(file.toPath()), file.getAbsolutePath(), encoding, useUnicodeExtraFields, true, ignoreLocalFileHeader);
+ this(builder().setFile(file).setCharset(encoding).setUseUnicodeExtraFields(useUnicodeExtraFields).setIgnoreLocalFileHeader(ignoreLocalFileHeader));
}
/**
@@ -824,7 +841,7 @@ public ZipFile(final File file, final String encoding, final boolean useUnicodeE
*/
@Deprecated
public ZipFile(final Path path) throws IOException {
- this(path, DEFAULT_CHARSET_NAME);
+ this(builder().setPath(path));
}
/**
@@ -838,7 +855,7 @@ public ZipFile(final Path path) throws IOException {
*/
@Deprecated
public ZipFile(final Path path, final String encoding) throws IOException {
- this(path, encoding, true);
+ this(builder().setPath(path).setCharset(encoding));
}
/**
@@ -853,7 +870,7 @@ public ZipFile(final Path path, final String encoding) throws IOException {
*/
@Deprecated
public ZipFile(final Path path, final String encoding, final boolean useUnicodeExtraFields) throws IOException {
- this(path, encoding, useUnicodeExtraFields, false);
+ this(builder().setPath(path).setCharset(encoding).setUseUnicodeExtraFields(useUnicodeExtraFields));
}
/**
@@ -877,7 +894,7 @@ public ZipFile(final Path path, final String encoding, final boolean useUnicodeE
@SuppressWarnings("resource") // Caller closes
@Deprecated
public ZipFile(final Path path, final String encoding, final boolean useUnicodeExtraFields, final boolean ignoreLocalFileHeader) throws IOException {
- this(newReadByteChannel(path), path.toAbsolutePath().toString(), encoding, useUnicodeExtraFields, true, ignoreLocalFileHeader);
+ this(builder().setPath(path).setCharset(encoding).setUseUnicodeExtraFields(useUnicodeExtraFields).setIgnoreLocalFileHeader(ignoreLocalFileHeader));
}
/**
@@ -893,7 +910,7 @@ public ZipFile(final Path path, final String encoding, final boolean useUnicodeE
*/
@Deprecated
public ZipFile(final SeekableByteChannel channel) throws IOException {
- this(channel, "a SeekableByteChannel", DEFAULT_CHARSET_NAME, true);
+ this(builder().setChannel(channel));
}
/**
@@ -910,33 +927,7 @@ public ZipFile(final SeekableByteChannel channel) throws IOException {
*/
@Deprecated
public ZipFile(final SeekableByteChannel channel, final String encoding) throws IOException {
- this(channel, "a SeekableByteChannel", encoding, true);
- }
-
- private ZipFile(final SeekableByteChannel channel, final String channelDescription, final Charset encoding, final boolean useUnicodeExtraFields,
- final boolean closeOnError, final boolean ignoreLocalFileHeader, final IOFunction zstdInputStream) throws IOException {
- this.isSplitZipArchive = channel instanceof ZipSplitReadOnlySeekableByteChannel;
- this.encoding = Charsets.toCharset(encoding, Builder.DEFAULT_CHARSET);
- this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
- this.useUnicodeExtraFields = useUnicodeExtraFields;
- this.archive = channel;
- this.zstdInputStreamFactory = zstdInputStream;
- boolean success = false;
- try {
- final Map entriesWithoutUTF8Flag = populateFromCentralDirectory();
- if (!ignoreLocalFileHeader) {
- resolveLocalFileHeaderData(entriesWithoutUTF8Flag);
- }
- fillNameMap();
- success = true;
- } catch (final IOException e) {
- throw new ArchiveException("Error reading Zip content from " + channelDescription, (Throwable) e);
- } finally {
- this.closed = !success;
- if (!success && closeOnError) {
- org.apache.commons.io.IOUtils.closeQuietly(archive);
- }
- }
+ this(builder().setChannel(channel).setCharset(encoding));
}
/**
@@ -956,7 +947,7 @@ private ZipFile(final SeekableByteChannel channel, final String channelDescripti
@Deprecated
public ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields)
throws IOException {
- this(channel, channelDescription, encoding, useUnicodeExtraFields, false, false);
+ this(builder().setChannel(channel).setName(channelDescription).setCharset(encoding).setUseUnicodeExtraFields(useUnicodeExtraFields));
}
/**
@@ -984,12 +975,8 @@ public ZipFile(final SeekableByteChannel channel, final String channelDescriptio
@Deprecated
public ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields,
final boolean ignoreLocalFileHeader) throws IOException {
- this(channel, channelDescription, encoding, useUnicodeExtraFields, false, ignoreLocalFileHeader);
- }
-
- private ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields,
- final boolean closeOnError, final boolean ignoreLocalFileHeader) throws IOException {
- this(channel, channelDescription, Charsets.toCharset(encoding), useUnicodeExtraFields, closeOnError, ignoreLocalFileHeader, null);
+ this(builder().setChannel(channel).setName(channelDescription).setCharset(encoding).setUseUnicodeExtraFields(useUnicodeExtraFields)
+ .setIgnoreLocalFileHeader(ignoreLocalFileHeader));
}
/**
@@ -1001,7 +988,7 @@ private ZipFile(final SeekableByteChannel channel, final String channelDescripti
*/
@Deprecated
public ZipFile(final String name) throws IOException {
- this(new File(name).toPath(), DEFAULT_CHARSET_NAME);
+ this(builder().setFile(name));
}
/**
@@ -1014,7 +1001,7 @@ public ZipFile(final String name) throws IOException {
*/
@Deprecated
public ZipFile(final String name, final String encoding) throws IOException {
- this(new File(name).toPath(), encoding, true);
+ this(builder().setFile(name).setCharset(encoding));
}
/**
@@ -1153,8 +1140,10 @@ public String getEncoding() {
* Entries will be returned in the same order they appear within the archive's central directory.
*
*
- * @return all entries as {@link ZipArchiveEntry} instances
+ * @return all entries as {@link ZipArchiveEntry} instances.
+ * @deprecated Since 1.29.0, use {@link #entries()} or {@link #stream()}.
*/
+ @Deprecated
public Enumeration getEntries() {
return Collections.enumeration(entries);
}
@@ -1163,7 +1152,7 @@ public Enumeration getEntries() {
* Gets all named entries in the same order they appear within the archive's central directory.
*
* @param name name of the entry.
- * @return the Iterable<ZipArchiveEntry> corresponding to the given name
+ * @return the Iterable<ZipArchiveEntry> corresponding to the given name.
* @since 1.6
*/
public Iterable getEntries(final String name) {
@@ -1176,7 +1165,7 @@ public Iterable getEntries(final String name) {
* Entries will be returned in the same order their contents appear within the archive.
*
*
- * @return all entries as {@link ZipArchiveEntry} instances
+ * @return all entries as {@link ZipArchiveEntry} instances.
* @since 1.1
*/
public Enumeration getEntriesInPhysicalOrder() {
@@ -1188,7 +1177,7 @@ public Enumeration getEntriesInPhysicalOrder() {
* Gets all named entries in the same order their contents appear within the archive.
*
* @param name name of the entry.
- * @return the Iterable<ZipArchiveEntry> corresponding to the given name
+ * @return the Iterable<ZipArchiveEntry> corresponding to the given name.
* @since 1.6
*/
public Iterable getEntriesInPhysicalOrder(final String name) {
@@ -1213,7 +1202,7 @@ public ZipArchiveEntry getEntry(final String name) {
/**
* Gets the offset of the first local file header in the file.
*
- * @return the length of the content before the first local file header
+ * @return the length of the content before the first local file header.
* @since 1.23
*/
public long getFirstLocalFileHeaderOffset() {
@@ -1227,6 +1216,7 @@ public long getFirstLocalFileHeaderOffset() {
* @return a stream to read the entry from. The returned stream implements {@link InputStreamStatistics}.
* @throws IOException if unable to create an input stream from the zipEntry.
*/
+ @Override
public InputStream getInputStream(final ZipArchiveEntry entry) throws IOException {
if (!(entry instanceof Entry)) {
return null;
@@ -1303,7 +1293,7 @@ public void close() throws IOException {
* {@code true} in the constructor. An IOException can also be thrown from the body of the method if this lookup fails for some reason.
*
*
- * @param entry The entry to get the stream for
+ * @param entry The entry to get the stream for.
* @return The raw input stream containing (possibly) compressed data.
* @throws IOException if there is a problem reading data offset (added in version 1.22).
* @since 1.11
@@ -1325,15 +1315,15 @@ public InputStream getRawInputStream(final ZipArchiveEntry entry) throws IOExcep
* This method assumes the symbolic link's file name uses the same encoding that as been specified for this ZipFile.
*
*
- * @param entry ZipArchiveEntry object that represents the symbolic link
- * @return entry's content as a String
- * @throws IOException problem with content's input stream
+ * @param entry ZipArchiveEntry object that represents the symbolic link.
+ * @return entry's content as a String.
+ * @throws IOException problem with content's input stream.
* @since 1.5
*/
public String getUnixSymlink(final ZipArchiveEntry entry) throws IOException {
if (entry != null && entry.isUnixSymlink()) {
try (InputStream in = getInputStream(entry)) {
- return zipEncoding.decode(org.apache.commons.io.IOUtils.toByteArray(in));
+ return zipEncoding.decode(IOUtils.toByteArray(in));
}
}
return null;
@@ -1527,7 +1517,7 @@ private void readCentralDirectoryEntry(final Map< 0) {
throw new ArchiveException("Broken archive, entry with negative fileNameLen");
@@ -1554,7 +1544,7 @@ private void readCentralDirectoryEntry(final Map< fileNameLen) {
throw new EOFException();
}
@@ -1565,7 +1555,7 @@ private void readCentralDirectoryEntry(final Map< extraLen) {
throw new EOFException();
}
@@ -1578,7 +1568,7 @@ private void readCentralDirectoryEntry(final Map< commentLen) {
throw new EOFException();
}
@@ -1605,7 +1595,7 @@ private void resolveLocalFileHeaderData(final Map< extraFieldLen) {
throw new EOFException();
}
@@ -1757,6 +1747,7 @@ private boolean startsWithLocalFileHeader() throws IOException {
* @throws IllegalStateException if the ZIP file has been closed.
* @since 1.28.0
*/
+ @Override
public IOStream extends ZipArchiveEntry> stream() {
return IOStream.adapt(entries.stream());
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitOutputStream.java
index accc0b8a041..834859bc4ed 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitOutputStream.java
@@ -33,6 +33,7 @@
import java.util.TreeMap;
import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.file.PathUtils;
/**
@@ -252,10 +253,14 @@ public void write(final byte[] b) throws IOException {
* @param b data to write
* @param off offset of the start of data in param b
* @param len the length of data to write
+ * @throws NullPointerException if {@code b} is null
+ * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative,
+ * or if {@code off + len} is greater than {@code b.length}.
* @throws IOException if an I/O error occurs.
*/
@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len <= 0) {
return;
}
diff --git a/src/main/java/org/apache/commons/compress/changes/ChangeSetPerformer.java b/src/main/java/org/apache/commons/compress/changes/ChangeSetPerformer.java
index 871094bcf53..47b3f710f04 100644
--- a/src/main/java/org/apache/commons/compress/changes/ChangeSetPerformer.java
+++ b/src/main/java/org/apache/commons/compress/changes/ChangeSetPerformer.java
@@ -31,6 +31,7 @@
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.compress.changes.Change.ChangeType;
+import org.apache.commons.io.IOUtils;
/**
* Performs ChangeSet operations on a stream. This class is thread safe and can be used multiple times. It operates on a copy of the ChangeSet. If the ChangeSet
@@ -134,7 +135,7 @@ public ChangeSetPerformer(final ChangeSet changeSet) {
*/
private void copyStream(final InputStream inputStream, final O outputStream, final E archiveEntry) throws IOException {
outputStream.putArchiveEntry(archiveEntry);
- org.apache.commons.io.IOUtils.copy(inputStream, outputStream);
+ IOUtils.copy(inputStream, outputStream);
outputStream.closeArchiveEntry();
}
diff --git a/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java b/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java
index ccc097c7d36..7b93ca40ca5 100644
--- a/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java
+++ b/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java
@@ -57,8 +57,8 @@
import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
import org.apache.commons.compress.compressors.zstandard.ZstdUtils;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.Sets;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
/**
@@ -236,7 +236,7 @@ static String detect(final InputStream inputStream, final Set compressor
inputStream.mark(signature.length);
int signatureLength = -1;
try {
- signatureLength = IOUtils.readFully(inputStream, signature);
+ signatureLength = IOUtils.read(inputStream, signature);
inputStream.reset();
} catch (final IOException e) {
throw new CompressorException("Failed to read signature.", e);
diff --git a/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java
index 40110dbdbe0..2f3784bc539 100644
--- a/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java
@@ -33,6 +33,7 @@
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.utils.BitInputStream;
import org.apache.commons.compress.utils.InputStreamStatistics;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.CloseShieldInputStream;
/**
@@ -712,28 +713,15 @@ public int read() throws IOException {
throw new CompressorException("Stream closed");
}
- /*
- * (non-Javadoc)
- *
- * @see InputStream#read(byte[], int, int)
- */
@Override
public int read(final byte[] dest, final int offs, final int len) throws IOException {
- if (offs < 0) {
- throw new IndexOutOfBoundsException("offs(" + offs + ") < 0.");
- }
- if (len < 0) {
- throw new IndexOutOfBoundsException("len(" + len + ") < 0.");
- }
- if (offs + len > dest.length) {
- throw new IndexOutOfBoundsException("offs(" + offs + ") + len(" + len + ") > dest.length(" + dest.length + ").");
+ IOUtils.checkFromIndexSize(dest, offs, len);
+ if (len == 0) {
+ return 0;
}
if (this.bin == null) {
throw new CompressorException("Stream closed");
}
- if (len == 0) {
- return 0;
- }
final int hi = offs + len;
int destOffs = offs;
diff --git a/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorOutputStream.java b/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorOutputStream.java
index d3a9fd9089e..fb48afbb59f 100644
--- a/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorOutputStream.java
@@ -23,6 +23,7 @@
import java.util.Arrays;
import org.apache.commons.compress.compressors.CompressorOutputStream;
+import org.apache.commons.io.IOUtils;
/**
* An output stream that compresses into the BZip2 format into another stream.
@@ -1149,15 +1150,7 @@ private void sendMTFValues7() throws IOException {
@Override
public void write(final byte[] buf, int offs, final int len) throws IOException {
- if (offs < 0) {
- throw new IndexOutOfBoundsException("offs(" + offs + ") < 0.");
- }
- if (len < 0) {
- throw new IndexOutOfBoundsException("len(" + len + ") < 0.");
- }
- if (offs + len > buf.length) {
- throw new IndexOutOfBoundsException("offs(" + offs + ") + len(" + len + ") > buf.length(" + buf.length + ").");
- }
+ IOUtils.checkFromIndexSize(buf, offs, len);
checkOpen();
for (final int hi = offs + len; offs < hi;) {
write0(buf[offs++]);
diff --git a/src/main/java/org/apache/commons/compress/compressors/bzip2/BlockSort.java b/src/main/java/org/apache/commons/compress/compressors/bzip2/BlockSort.java
index b4e77ab0866..2a6117d292c 100644
--- a/src/main/java/org/apache/commons/compress/compressors/bzip2/BlockSort.java
+++ b/src/main/java/org/apache/commons/compress/compressors/bzip2/BlockSort.java
@@ -62,10 +62,10 @@
* For more information see for example:
*
*
- * - Burrows, M. and Wheeler, D.: A Block-sorting Lossless Data Compression
+ *
- Burrows, M. and Wheeler, D.: A Block-sorting Lossless Data Compression
* Algorithm
- * - Manber, U. and Myers, G.: Suffix arrays: A new method for on-line string searches
- * - Bentley, J.L. and Sedgewick, R.: Fast Algorithms for Sorting and
+ *
- Manber, U. and Myers, G.: Suffix arrays: A new method for on-line string searches
+ * - Bentley, J.L. and Sedgewick, R.: Fast Algorithms for Sorting and
* Searching Strings
*
*
diff --git a/src/main/java/org/apache/commons/compress/compressors/deflate/DeflateCompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/deflate/DeflateCompressorInputStream.java
index 9a0974da97e..5a8b265dbad 100644
--- a/src/main/java/org/apache/commons/compress/compressors/deflate/DeflateCompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/deflate/DeflateCompressorInputStream.java
@@ -25,6 +25,7 @@
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.utils.InputStreamStatistics;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BoundedInputStream;
/**
@@ -110,12 +111,8 @@ public int read() throws IOException {
return ret;
}
- /** {@inheritDoc} */
@Override
public int read(final byte[] buf, final int off, final int len) throws IOException {
- if (len == 0) {
- return 0;
- }
final int ret = in.read(buf, off, len);
count(ret);
return ret;
@@ -124,6 +121,6 @@ public int read(final byte[] buf, final int off, final int len) throws IOExcepti
/** {@inheritDoc} */
@Override
public long skip(final long n) throws IOException {
- return org.apache.commons.io.IOUtils.skip(in, n);
+ return IOUtils.skip(in, n);
}
}
diff --git a/src/main/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStream.java
index 0b5623b79d6..d0f35c51586 100644
--- a/src/main/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStream.java
@@ -102,11 +102,9 @@ public int read() throws IOException {
}
}
- /**
- * @throws java.io.EOFException if the underlying stream is exhausted before the end of deflated data was reached.
- */
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
diff --git a/src/main/java/org/apache/commons/compress/compressors/gzip/GzipCompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/gzip/GzipCompressorInputStream.java
index 916f7672098..681d7961608 100644
--- a/src/main/java/org/apache/commons/compress/compressors/gzip/GzipCompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/gzip/GzipCompressorInputStream.java
@@ -445,6 +445,7 @@ public int read() throws IOException {
*/
@Override
public int read(final byte[] b, int off, int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
diff --git a/src/main/java/org/apache/commons/compress/compressors/gzip/GzipCompressorOutputStream.java b/src/main/java/org/apache/commons/compress/compressors/gzip/GzipCompressorOutputStream.java
index a62cee24426..e36b4cb7021 100644
--- a/src/main/java/org/apache/commons/compress/compressors/gzip/GzipCompressorOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/gzip/GzipCompressorOutputStream.java
@@ -29,6 +29,7 @@
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorOutputStream;
+import org.apache.commons.io.IOUtils;
/**
* Compressed output stream using the gzip format. This implementation improves over the standard {@link GZIPOutputStream} class by allowing the configuration
@@ -127,6 +128,7 @@ public void write(final byte[] buffer) throws IOException {
*/
@Override
public void write(final byte[] buffer, final int offset, final int length) throws IOException {
+ IOUtils.checkFromIndexSize(buffer, offset, length);
checkOpen();
if (deflater.finished()) {
throw new CompressorException("Cannot write more data, the end of the compressed data stream has been reached.");
diff --git a/src/main/java/org/apache/commons/compress/compressors/lz4/BlockLZ4CompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/lz4/BlockLZ4CompressorInputStream.java
index cb8d13364a0..5dffa27cc68 100644
--- a/src/main/java/org/apache/commons/compress/compressors/lz4/BlockLZ4CompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/lz4/BlockLZ4CompressorInputStream.java
@@ -24,6 +24,7 @@
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.lz77support.AbstractLZ77CompressorInputStream;
import org.apache.commons.compress.utils.ByteUtils;
+import org.apache.commons.io.IOUtils;
/**
* CompressorInputStream for the LZ4 block format.
@@ -89,11 +90,9 @@ private boolean initializeBackReference() throws IOException {
return true;
}
- /**
- * {@inheritDoc}
- */
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
diff --git a/src/main/java/org/apache/commons/compress/compressors/lz4/FramedLZ4CompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/lz4/FramedLZ4CompressorInputStream.java
index 9128e534e9d..3cd88e4b648 100644
--- a/src/main/java/org/apache/commons/compress/compressors/lz4/FramedLZ4CompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/lz4/FramedLZ4CompressorInputStream.java
@@ -26,8 +26,8 @@
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.utils.ByteUtils;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.InputStreamStatistics;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BoundedInputStream;
/**
@@ -159,7 +159,7 @@ private void appendToBlockDependencyBuffer(final byte[] b, final int off, int le
@Override
public void close() throws IOException {
try {
- org.apache.commons.io.IOUtils.close(currentBlock);
+ IOUtils.close(currentBlock);
currentBlock = null;
} finally {
inputStream.close();
@@ -235,9 +235,9 @@ public int read() throws IOException {
return read(oneByte, 0, 1) == -1 ? -1 : oneByte[0] & 0xFF;
}
- /** {@inheritDoc} */
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
@@ -289,7 +289,7 @@ private void readFrameDescriptor() throws IOException {
contentHash.update(bdByte);
if (expectContentSize) { // for now, we don't care, contains the uncompressed size
final byte[] contentSize = new byte[8];
- final int skipped = IOUtils.readFully(inputStream, contentSize);
+ final int skipped = IOUtils.read(inputStream, contentSize);
count(skipped);
if (8 != skipped) {
throw new CompressorException("Premature end of stream while reading content size");
@@ -332,7 +332,7 @@ private int readOneByte() throws IOException {
private boolean readSignature(final boolean firstFrame) throws IOException {
final String garbageMessage = firstFrame ? "Not a LZ4 frame stream" : "LZ4 frame stream followed by garbage";
final byte[] b = new byte[4];
- int read = IOUtils.readFully(inputStream, b);
+ int read = IOUtils.read(inputStream, b);
count(read);
if (0 == read && !firstFrame) {
// good LZ4 frame and nothing after it
@@ -369,12 +369,12 @@ private int skipSkippableFrame(final byte[] b) throws IOException {
if (len < 0) {
throw new CompressorException("Found illegal skippable frame with negative size");
}
- final long skipped = org.apache.commons.io.IOUtils.skip(inputStream, len);
+ final long skipped = IOUtils.skip(inputStream, len);
count(skipped);
if (len != skipped) {
throw new CompressorException("Premature end of stream while skipping frame");
}
- read = IOUtils.readFully(inputStream, b);
+ read = IOUtils.read(inputStream, b);
count(read);
}
return read;
@@ -382,7 +382,7 @@ private int skipSkippableFrame(final byte[] b) throws IOException {
private void verifyChecksum(final org.apache.commons.codec.digest.XXHash32 hash, final String kind) throws IOException {
final byte[] checksum = new byte[4];
- final int read = IOUtils.readFully(inputStream, checksum);
+ final int read = IOUtils.read(inputStream, checksum);
count(read);
if (4 != read) {
throw new CompressorException("Premature end of stream while reading %s checksum", kind);
diff --git a/src/main/java/org/apache/commons/compress/compressors/lz4/FramedLZ4CompressorOutputStream.java b/src/main/java/org/apache/commons/compress/compressors/lz4/FramedLZ4CompressorOutputStream.java
index d53088dde81..f9f2c5b7acc 100644
--- a/src/main/java/org/apache/commons/compress/compressors/lz4/FramedLZ4CompressorOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/lz4/FramedLZ4CompressorOutputStream.java
@@ -24,6 +24,7 @@
import org.apache.commons.compress.compressors.CompressorOutputStream;
import org.apache.commons.compress.utils.ByteUtils;
+import org.apache.commons.io.IOUtils;
/**
* CompressorOutputStream for the LZ4 frame format.
@@ -271,6 +272,7 @@ private void flushBlock() throws IOException {
@Override
public void write(final byte[] data, int off, int len) throws IOException {
+ IOUtils.checkFromIndexSize(data, off, len);
if (params.withContentChecksum) {
contentHash.update(data, off, len);
}
diff --git a/src/main/java/org/apache/commons/compress/compressors/lz77support/AbstractLZ77CompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/lz77support/AbstractLZ77CompressorInputStream.java
index 83280732ea2..1a28ba38030 100644
--- a/src/main/java/org/apache/commons/compress/compressors/lz77support/AbstractLZ77CompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/lz77support/AbstractLZ77CompressorInputStream.java
@@ -25,8 +25,8 @@
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.utils.ByteUtils;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.InputStreamStatistics;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BoundedInputStream;
/**
@@ -332,7 +332,7 @@ private void tryToCopy(final int bytesToCopy) {
private void tryToReadLiteral(final int bytesToRead) throws IOException {
// min of "what is still inside the literal", "what does the user want" and "how much can fit into the buffer"
final int reallyTryToRead = Math.min((int) Math.min(bytesToRead, bytesRemaining), buf.length - writeIndex);
- final int bytesRead = reallyTryToRead > 0 ? IOUtils.readFully(in, buf, writeIndex, reallyTryToRead) : 0 /* happens for bytesRemaining == 0 */;
+ final int bytesRead = reallyTryToRead > 0 ? IOUtils.read(in, buf, writeIndex, reallyTryToRead) : 0 /* happens for bytesRemaining == 0 */;
count(bytesRead);
if (reallyTryToRead != bytesRead) {
throw new CompressorException("Premature end of stream reading literal");
diff --git a/src/main/java/org/apache/commons/compress/compressors/lz77support/LZ77Compressor.java b/src/main/java/org/apache/commons/compress/compressors/lz77support/LZ77Compressor.java
index 9105187ac94..8eef1691109 100644
--- a/src/main/java/org/apache/commons/compress/compressors/lz77support/LZ77Compressor.java
+++ b/src/main/java/org/apache/commons/compress/compressors/lz77support/LZ77Compressor.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.util.Objects;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayFill;
/**
@@ -178,7 +179,7 @@ public enum BlockType {
/**
* Constructs a new typeless instance.
*
- * @deprecated Use {@link #Block()}.
+ * @deprecated Use {@link #Block(BlockType)}.
*/
@Deprecated
public Block() {
@@ -414,9 +415,12 @@ public void compress(final byte[] data) throws IOException {
* @param data the data to compress - must not be null
* @param off the start offset of the data
* @param len the number of bytes to compress
+ * @throws NullPointerException if data is {@code null}
+ * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is bigger than {@code data.length}.
* @throws IOException if the callback throws an exception
*/
public void compress(final byte[] data, int off, int len) throws IOException {
+ IOUtils.checkFromIndexSize(data, off, len);
final int wSize = params.getWindowSize();
while (len > wSize) { // chop into windowSize sized chunks
doCompress(data, off, wSize);
diff --git a/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java
index 3743f4ecbeb..0df33b06905 100644
--- a/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java
@@ -24,6 +24,7 @@
import org.apache.commons.compress.MemoryLimitException;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.utils.InputStreamStatistics;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.build.AbstractStreamBuilder;
import org.apache.commons.io.input.BoundedInputStream;
import org.tukaani.xz.LZMA2Options;
@@ -179,6 +180,6 @@ public int read(final byte[] buf, final int off, final int len) throws IOExcepti
/** {@inheritDoc} */
@Override
public long skip(final long n) throws IOException {
- return org.apache.commons.io.IOUtils.skip(in, n);
+ return IOUtils.skip(in, n);
}
}
diff --git a/src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java b/src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java
index 2015b96de87..54bd8841130 100644
--- a/src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java
@@ -27,6 +27,7 @@
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.utils.BitInputStream;
import org.apache.commons.compress.utils.InputStreamStatistics;
+import org.apache.commons.io.IOUtils;
/**
*
@@ -276,6 +277,7 @@ public int read() throws IOException {
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
diff --git a/src/main/java/org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorInputStream.java
index f83f5251ea9..000c113b37c 100644
--- a/src/main/java/org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorInputStream.java
@@ -27,8 +27,8 @@
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.utils.ByteUtils;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.InputStreamStatistics;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BoundedInputStream;
/**
@@ -177,7 +177,7 @@ public int available() throws IOException {
@Override
public void close() throws IOException {
try {
- org.apache.commons.io.IOUtils.close(currentCompressedChunk);
+ IOUtils.close(currentCompressedChunk);
currentCompressedChunk = null;
} finally {
inputStream.close();
@@ -198,9 +198,9 @@ public int read() throws IOException {
return read(oneByte, 0, 1) == -1 ? -1 : oneByte[0] & 0xFF;
}
- /** {@inheritDoc} */
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
@@ -217,7 +217,7 @@ public int read(final byte[] b, final int off, final int len) throws IOException
private long readCrc() throws IOException {
final byte[] b = new byte[4];
- final int read = IOUtils.readFully(inputStream, b);
+ final int read = IOUtils.read(inputStream, b);
count(read);
if (read != 4) {
throw new CompressorException("Premature end of stream");
@@ -324,7 +324,7 @@ private int readSize() throws IOException {
private void readStreamIdentifier() throws IOException {
final byte[] b = new byte[10];
- final int read = IOUtils.readFully(inputStream, b);
+ final int read = IOUtils.read(inputStream, b);
count(read);
if (10 != read || !matches(b, 10)) {
throw new CompressorException("Not a framed Snappy stream");
@@ -336,7 +336,7 @@ private void skipBlock() throws IOException {
if (size < 0) {
throw new CompressorException("Found illegal chunk with negative size");
}
- final long read = org.apache.commons.io.IOUtils.skip(inputStream, size);
+ final long read = IOUtils.skip(inputStream, size);
count(read);
if (read != size) {
throw new CompressorException("Premature end of stream");
diff --git a/src/main/java/org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorOutputStream.java b/src/main/java/org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorOutputStream.java
index ce3be3c3c4b..b5e733b7e2f 100644
--- a/src/main/java/org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorOutputStream.java
@@ -26,6 +26,7 @@
import org.apache.commons.compress.compressors.CompressorOutputStream;
import org.apache.commons.compress.compressors.lz77support.Parameters;
import org.apache.commons.compress.utils.ByteUtils;
+import org.apache.commons.io.IOUtils;
/**
* CompressorOutputStream for the framing Snappy format.
@@ -125,6 +126,7 @@ private void flushBuffer() throws IOException {
@Override
public void write(final byte[] data, int off, int len) throws IOException {
+ IOUtils.checkFromIndexSize(data, off, len);
int blockDataRemaining = buffer.length - currentIndex;
while (len > 0) {
final int copyLen = Math.min(len, blockDataRemaining);
diff --git a/src/main/java/org/apache/commons/compress/compressors/snappy/SnappyCompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/snappy/SnappyCompressorInputStream.java
index 03790331499..ec9b31fbcd7 100644
--- a/src/main/java/org/apache/commons/compress/compressors/snappy/SnappyCompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/snappy/SnappyCompressorInputStream.java
@@ -24,6 +24,7 @@
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.lz77support.AbstractLZ77CompressorInputStream;
import org.apache.commons.compress.utils.ByteUtils;
+import org.apache.commons.io.IOUtils;
/**
* CompressorInputStream for the raw Snappy format.
@@ -192,11 +193,9 @@ public long getUncompressedSize() {
return uncompressedSize;
}
- /**
- * {@inheritDoc}
- */
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
+ IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
diff --git a/src/main/java/org/apache/commons/compress/compressors/xz/XZCompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/xz/XZCompressorInputStream.java
index 9f8e2356efc..a4623c751dc 100644
--- a/src/main/java/org/apache/commons/compress/compressors/xz/XZCompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/xz/XZCompressorInputStream.java
@@ -26,6 +26,7 @@
import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;
import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream;
import org.apache.commons.compress.utils.InputStreamStatistics;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.build.AbstractStreamBuilder;
import org.apache.commons.io.input.BoundedInputStream;
import org.tukaani.xz.LZMA2Options;
@@ -240,9 +241,6 @@ public int read() throws IOException {
@Override
public int read(final byte[] buf, final int off, final int len) throws IOException {
- if (len == 0) {
- return 0;
- }
try {
final int ret = in.read(buf, off, len);
count(ret);
@@ -256,7 +254,7 @@ public int read(final byte[] buf, final int off, final int len) throws IOExcepti
@Override
public long skip(final long n) throws IOException {
try {
- return org.apache.commons.io.IOUtils.skip(in, n);
+ return IOUtils.skip(in, n);
} catch (final org.tukaani.xz.MemoryLimitException e) {
// Convert to Commons Compress MemoryLimtException
throw newMemoryLimitException(e);
diff --git a/src/main/java/org/apache/commons/compress/compressors/zstandard/ZstdCompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/zstandard/ZstdCompressorInputStream.java
index 8b218738997..6f36f64c6f0 100644
--- a/src/main/java/org/apache/commons/compress/compressors/zstandard/ZstdCompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/zstandard/ZstdCompressorInputStream.java
@@ -24,6 +24,7 @@
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.utils.InputStreamStatistics;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BoundedInputStream;
import com.github.luben.zstd.BufferPool;
@@ -113,9 +114,6 @@ public int read(final byte[] b) throws IOException {
@Override
public int read(final byte[] buf, final int off, final int len) throws IOException {
- if (len == 0) {
- return 0;
- }
final int ret = decIS.read(buf, off, len);
count(ret);
return ret;
@@ -128,7 +126,7 @@ public synchronized void reset() throws IOException {
@Override
public long skip(final long n) throws IOException {
- return org.apache.commons.io.IOUtils.skip(decIS, n);
+ return IOUtils.skip(decIS, n);
}
@Override
diff --git a/src/main/java/org/apache/commons/compress/harmony/pack200/PackingUtils.java b/src/main/java/org/apache/commons/compress/harmony/pack200/PackingUtils.java
index 6148ee2cbf1..63d7edd0c7c 100644
--- a/src/main/java/org/apache/commons/compress/harmony/pack200/PackingUtils.java
+++ b/src/main/java/org/apache/commons/compress/harmony/pack200/PackingUtils.java
@@ -18,7 +18,6 @@
*/
package org.apache.commons.compress.harmony.pack200;
-import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -40,6 +39,8 @@
import java.util.logging.SimpleFormatter;
import org.apache.commons.compress.harmony.pack200.Archive.PackingFile;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.function.IOConsumer;
public class PackingUtils {
@@ -97,16 +98,12 @@ public static void config(final PackingOptions options) throws IOException {
public static void copyThroughJar(final JarFile jarFile, final OutputStream outputStream) throws IOException {
try (JarOutputStream jarOutputStream = new JarOutputStream(outputStream)) {
jarOutputStream.setComment("PACK200");
- final byte[] bytes = new byte[16384];
final Enumeration entries = jarFile.entries();
while (entries.hasMoreElements()) {
final JarEntry jarEntry = entries.nextElement();
jarOutputStream.putNextEntry(jarEntry);
try (InputStream inputStream = jarFile.getInputStream(jarEntry)) {
- int bytesRead;
- while ((bytesRead = inputStream.read(bytes)) != -1) {
- jarOutputStream.write(bytes, 0, bytesRead);
- }
+ IOUtils.copyLarge(inputStream, jarOutputStream);
jarOutputStream.closeEntry();
log("Packed " + jarEntry.getName());
}
@@ -128,14 +125,10 @@ public static void copyThroughJar(final JarInputStream jarInputStream, final Out
jarOutputStream.setComment("PACK200");
log("Packed " + JarFile.MANIFEST_NAME);
- final byte[] bytes = new byte[16384];
JarEntry jarEntry;
- int bytesRead;
while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
jarOutputStream.putNextEntry(jarEntry);
- while ((bytesRead = jarInputStream.read(bytes)) != -1) {
- jarOutputStream.write(bytes, 0, bytesRead);
- }
+ IOUtils.copyLarge(jarInputStream, jarOutputStream);
log("Packed " + jarEntry.getName());
}
jarInputStream.close();
@@ -144,14 +137,11 @@ public static void copyThroughJar(final JarInputStream jarInputStream, final Out
public static List getPackingFileListFromJar(final JarFile jarFile, final boolean keepFileOrder) throws IOException {
final List packingFileList = new ArrayList<>();
- final Enumeration jarEntries = jarFile.entries();
- while (jarEntries.hasMoreElements()) {
- final JarEntry jarEntry = jarEntries.nextElement();
+ IOConsumer.forEach(jarFile.stream(), jarEntry -> {
try (InputStream inputStream = jarFile.getInputStream(jarEntry)) {
- final byte[] bytes = readJarEntry(jarEntry, new BufferedInputStream(inputStream));
- packingFileList.add(new PackingFile(bytes, jarEntry));
+ packingFileList.add(new PackingFile(readJarEntry(jarEntry, inputStream), jarEntry));
}
- }
+ });
// check whether it need reorder packing file list
if (!keepFileOrder) {
@@ -173,10 +163,8 @@ public static List getPackingFileListFromJar(final JarInputStream j
// add rest of entries in the jar
JarEntry jarEntry;
- byte[] bytes;
while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
- bytes = readJarEntry(jarEntry, new BufferedInputStream(jarInputStream));
- packingFileList.add(new PackingFile(bytes, jarEntry));
+ packingFileList.add(new PackingFile(readJarEntry(jarEntry, jarInputStream), jarEntry));
}
// check whether it need reorder packing file list
@@ -191,19 +179,13 @@ public static void log(final String message) {
}
private static byte[] readJarEntry(final JarEntry jarEntry, final InputStream inputStream) throws IOException {
- long size = jarEntry.getSize();
+ final long size = jarEntry.getSize();
if (size > Integer.MAX_VALUE) {
// TODO: Should probably allow this
throw new IllegalArgumentException("Large Class!");
}
- if (size < 0) {
- size = 0;
- }
- final byte[] bytes = new byte[(int) size];
- if (inputStream.read(bytes) != size) {
- throw new IllegalArgumentException("Error reading from stream");
- }
- return bytes;
+ // Negative size means unknown size
+ return size < 0 ? IOUtils.toByteArray(inputStream) : IOUtils.toByteArray(inputStream, (int) size, IOUtils.DEFAULT_BUFFER_SIZE);
}
private static void reorderPackingFiles(final List packingFileList) {
diff --git a/src/main/java/org/apache/commons/compress/harmony/unpack200/FileBands.java b/src/main/java/org/apache/commons/compress/harmony/unpack200/FileBands.java
index 5575d4b05d1..ae01a821b2d 100644
--- a/src/main/java/org/apache/commons/compress/harmony/unpack200/FileBands.java
+++ b/src/main/java/org/apache/commons/compress/harmony/unpack200/FileBands.java
@@ -23,7 +23,6 @@
import org.apache.commons.compress.harmony.pack200.Codec;
import org.apache.commons.compress.harmony.pack200.Pack200Exception;
-import org.apache.commons.compress.utils.IOUtils;
/**
* Parses the file band headers (not including the actual bits themselves). At the end of this parse call, the input stream will be positioned at the start of
@@ -83,7 +82,7 @@ public void processFileBits() throws IOException {
fileBits = new byte[numberOfFiles][];
for (int i = 0; i < numberOfFiles; i++) {
final int size = (int) fileSize[i];
- fileBits[i] = IOUtils.readRange(in, size);
+ fileBits[i] = org.apache.commons.compress.utils.IOUtils.readRange(in, size);
final int read = fileBits[i].length;
if (size != 0 && read < size) {
throw new Pack200Exception("Expected to read " + size + " bytes but read " + read);
diff --git a/src/main/java/org/apache/commons/compress/harmony/unpack200/MetadataBandGroup.java b/src/main/java/org/apache/commons/compress/harmony/unpack200/MetadataBandGroup.java
index 43292679ffb..1811efdeea2 100644
--- a/src/main/java/org/apache/commons/compress/harmony/unpack200/MetadataBandGroup.java
+++ b/src/main/java/org/apache/commons/compress/harmony/unpack200/MetadataBandGroup.java
@@ -44,6 +44,9 @@
*/
public class MetadataBandGroup {
+ /** Size in bytes of an {@link ElementValue} instance: header, Object, int, int. */
+ private static final int ELEMENT_VALUE_BYTES = 8 + 8 + Integer.BYTES + Integer.BYTES;
+
private static CPUTF8 rvaUTF8;
private static CPUTF8 riaUTF8;
@@ -220,7 +223,7 @@ private Object getNextValue(final int t) throws Pack200Exception {
return cases_RU[cases_RU_Index++];
case '[':
final int arraySize = casearray_N[casearray_N_Index++];
- final ElementValue[] nestedArray = new ElementValue[Pack200Exception.checkObjectArray(arraySize, ElementValue.BYTES)];
+ final ElementValue[] nestedArray = new ElementValue[Pack200Exception.checkObjectArray(arraySize, ELEMENT_VALUE_BYTES)];
for (int i = 0; i < arraySize; i++) {
final int nextT = T[T_index++];
nestedArray[i] = new ElementValue(nextT, getNextValue(nextT));
diff --git a/src/main/java/org/apache/commons/compress/harmony/unpack200/SegmentHeader.java b/src/main/java/org/apache/commons/compress/harmony/unpack200/SegmentHeader.java
index 69830e63fc4..23a74d3edb7 100644
--- a/src/main/java/org/apache/commons/compress/harmony/unpack200/SegmentHeader.java
+++ b/src/main/java/org/apache/commons/compress/harmony/unpack200/SegmentHeader.java
@@ -25,7 +25,6 @@
import org.apache.commons.compress.harmony.pack200.BHSDCodec;
import org.apache.commons.compress.harmony.pack200.Codec;
import org.apache.commons.compress.harmony.pack200.Pack200Exception;
-import org.apache.commons.compress.utils.IOUtils;
/**
* SegmentHeader is the header band of a {@link Segment}.
@@ -301,7 +300,7 @@ public void read(final InputStream in) throws IOException, Error, Pack200Excepti
parseCpCounts(in);
parseClassCounts(in);
if (getBandHeadersSize() > 0) {
- setBandHeadersData(IOUtils.readRange(in, getBandHeadersSize()));
+ setBandHeadersData(org.apache.commons.compress.utils.IOUtils.readRange(in, getBandHeadersSize()));
}
archiveSizeOffset -= in.available();
}
diff --git a/src/main/java/org/apache/commons/compress/harmony/unpack200/bytecode/AnnotationsAttribute.java b/src/main/java/org/apache/commons/compress/harmony/unpack200/bytecode/AnnotationsAttribute.java
index fd09af326a0..e08110deaa7 100644
--- a/src/main/java/org/apache/commons/compress/harmony/unpack200/bytecode/AnnotationsAttribute.java
+++ b/src/main/java/org/apache/commons/compress/harmony/unpack200/bytecode/AnnotationsAttribute.java
@@ -127,9 +127,6 @@ public void writeBody(final DataOutputStream dos) throws IOException {
*/
public static class ElementValue {
- /** Size in bytes of an instance: header, Object, int, int. */
- public static final int BYTES = 8 + 8 + Integer.BYTES + Integer.BYTES;
-
private final Object value;
private final int tag;
diff --git a/src/main/java/org/apache/commons/compress/harmony/unpack200/bytecode/forms/ByteCodeForm.java b/src/main/java/org/apache/commons/compress/harmony/unpack200/bytecode/forms/ByteCodeForm.java
index 5e5693cfda1..39a7e2338e6 100644
--- a/src/main/java/org/apache/commons/compress/harmony/unpack200/bytecode/forms/ByteCodeForm.java
+++ b/src/main/java/org/apache/commons/compress/harmony/unpack200/bytecode/forms/ByteCodeForm.java
@@ -378,7 +378,7 @@ protected void calculateOperandPosition() {
// If last < first, something is wrong.
if (difference < 0) {
- throw new IllegalStateException("Logic error: not finding rewrite operands correctly");
+ throw new IllegalStateException("Logic error: Not finding rewrite operands correctly");
}
operandLength = difference + 1;
}
diff --git a/src/main/java/org/apache/commons/compress/utils/ArchiveUtils.java b/src/main/java/org/apache/commons/compress/utils/ArchiveUtils.java
index 1b3c827b7ce..711f20054bb 100644
--- a/src/main/java/org/apache/commons/compress/utils/ArchiveUtils.java
+++ b/src/main/java/org/apache/commons/compress/utils/ArchiveUtils.java
@@ -22,7 +22,9 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import org.apache.commons.compress.MemoryLimitException;
import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveException;
/**
* Generic Archive utilities.
@@ -31,6 +33,29 @@ public class ArchiveUtils {
private static final int MAX_SANITIZED_NAME_LENGTH = 255;
+ private static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
+
+ /**
+ * Checks that the given entry name length is valid.
+ *
+ * @param length The length of the entry name.
+ * @param maxEntryNameLength The maximum allowed length of the entry name.
+ * @param archiveType The type of the archive (for error messages).
+ * @return The length, if valid.
+ * @throws ArchiveException If the length is not valid.
+ * @throws MemoryLimitException If the length is valid, but too large for the current JVM to handle.
+ * @since 1.29.0
+ */
+ public static int checkEntryNameLength(long length, int maxEntryNameLength, String archiveType)
+ throws ArchiveException, MemoryLimitException {
+ if (length > maxEntryNameLength) {
+ throw new ArchiveException("Invalid %s archive entry: Invalid file name length %,d (must be between 1 and %,d). If the file is not corrupt, " +
+ "consider increasing the `maxEntryNameLength` limit.", archiveType, length, maxEntryNameLength);
+ }
+ MemoryLimitException.checkBytes(length, SOFT_MAX_ARRAY_LENGTH);
+ return (int) length;
+ }
+
/**
* Tests whether true if the first N bytes of an array are all zero.
*
diff --git a/src/main/java/org/apache/commons/compress/utils/BoundedArchiveInputStream.java b/src/main/java/org/apache/commons/compress/utils/BoundedArchiveInputStream.java
index 2808964d938..e0d7f7d033a 100644
--- a/src/main/java/org/apache/commons/compress/utils/BoundedArchiveInputStream.java
+++ b/src/main/java/org/apache/commons/compress/utils/BoundedArchiveInputStream.java
@@ -22,6 +22,8 @@
import java.io.InputStream;
import java.nio.ByteBuffer;
+import org.apache.commons.io.IOUtils;
+
/**
* NIO backed bounded input stream for reading a predefined amount of data.
*
@@ -69,18 +71,16 @@ public synchronized int read() throws IOException {
@Override
public synchronized int read(final byte[] b, final int off, final int len) throws IOException {
- if (loc >= end) {
- return -1;
- }
- final long maxLen = Math.min(len, end - loc);
- if (maxLen <= 0) {
+ IOUtils.checkFromIndexSize(b, off, len);
+ if (len == 0) {
return 0;
}
- if (off < 0 || off > b.length || maxLen > b.length - off) {
- throw new IndexOutOfBoundsException("offset or len are out of bounds");
+ if (loc >= end) {
+ return -1;
}
-
- final ByteBuffer buf = ByteBuffer.wrap(b, off, (int) maxLen);
+ // Both len and end - loc are guaranteed to be > 0 here and at least len is <= Integer.MAX_VALUE.
+ final int maxLen = (int) Math.min(len, end - loc);
+ final ByteBuffer buf = ByteBuffer.wrap(b, off, maxLen);
final int ret = read(loc, buf);
if (ret > 0) {
loc += ret;
@@ -89,12 +89,15 @@ public synchronized int read(final byte[] b, final int off, final int len) throw
}
/**
- * Reads content of the stream into a {@link ByteBuffer}.
+ * Reads bytes from this stream into the given {@link ByteBuffer}, starting at the specified position.
+ *
+ * The caller is responsible for ensuring that the requested range
+ * {@code [pos, pos + buf.remaining())} lies within the valid bounds of the stream.
*
- * @param pos position to start the read.
- * @param buf buffer to add the read content.
- * @return number of read bytes.
- * @throws IOException if I/O fails.
+ * @param pos the position within the stream at which to begin reading.
+ * @param buf the buffer into which bytes are read; bytes are written starting at the buffer’s current position.
+ * @return the number of bytes read into the buffer.
+ * @throws IOException if an I/O error occurs while reading.
*/
protected abstract int read(long pos, ByteBuffer buf) throws IOException;
}
diff --git a/src/main/java/org/apache/commons/compress/utils/FixedLengthBlockOutputStream.java b/src/main/java/org/apache/commons/compress/utils/FixedLengthBlockOutputStream.java
index 805b61413c9..ce7d341d1bd 100644
--- a/src/main/java/org/apache/commons/compress/utils/FixedLengthBlockOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/utils/FixedLengthBlockOutputStream.java
@@ -201,6 +201,7 @@ private void padBlock() {
@Override
public void write(final byte[] b, final int offset, final int length) throws IOException {
+ IOUtils.checkFromIndexSize(b, offset, length);
if (!isOpen()) {
throw new ClosedChannelException();
}
diff --git a/src/main/java/org/apache/commons/compress/utils/IOUtils.java b/src/main/java/org/apache/commons/compress/utils/IOUtils.java
index 1eedd52b7c5..2c9da310744 100644
--- a/src/main/java/org/apache/commons/compress/utils/IOUtils.java
+++ b/src/main/java/org/apache/commons/compress/utils/IOUtils.java
@@ -48,11 +48,6 @@ public final class IOUtils {
*/
public static final LinkOption[] EMPTY_LINK_OPTIONS = {};
- /**
- * The {@code SOFT_MAX_ARRAY_LENGTH} constant from Java's internal ArraySupport class.
- */
- private static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
-
/**
* Closes the given Closeable and swallows any IOException that may occur.
*
@@ -164,7 +159,7 @@ public static long copyRange(final InputStream input, final long length, final O
@Deprecated
public static int read(final File file, final byte[] array) throws IOException {
try (InputStream inputStream = Files.newInputStream(file.toPath())) {
- return readFully(inputStream, array, 0, array.length);
+ return org.apache.commons.io.IOUtils.read(inputStream, array, 0, array.length);
}
}
@@ -178,9 +173,11 @@ public static int read(final File file, final byte[] array) throws IOException {
* @param array buffer to fill.
* @return the number of bytes actually read.
* @throws IOException Thrown if an I/O error occurs.
+ * @deprecated Since 1.29.0, use {@link org.apache.commons.io.IOUtils#read(InputStream, byte[])} instead.
*/
+ @Deprecated
public static int readFully(final InputStream input, final byte[] array) throws IOException {
- return readFully(input, array, 0, array.length);
+ return org.apache.commons.io.IOUtils.read(input, array);
}
/**
@@ -195,11 +192,10 @@ public static int readFully(final InputStream input, final byte[] array) throws
* @param length of bytes to read.
* @return the number of bytes actually read.
* @throws IOException Thrown if an I/O error occurs.
+ * @deprecated Since 1.29.0, use {@link org.apache.commons.io.IOUtils#read(InputStream, byte[], int, int)} instead.
*/
+ @Deprecated
public static int readFully(final InputStream input, final byte[] array, final int offset, final int length) throws IOException {
- if (length < 0 || offset < 0 || length + offset > array.length || length + offset < 0) {
- throw new IndexOutOfBoundsException();
- }
return org.apache.commons.io.IOUtils.read(input, array, offset, length);
}
@@ -214,13 +210,11 @@ public static int readFully(final InputStream input, final byte[] array, final i
* @param byteBuffer the buffer into which the data is read.
* @throws IOException Thrown if an I/O error occurs.
* @throws EOFException if the channel reaches the end before reading all the bytes.
+ * @deprecated Since 1.29.0, use {@link org.apache.commons.io.IOUtils#readFully(ReadableByteChannel, ByteBuffer)} instead.
*/
+ @Deprecated
public static void readFully(final ReadableByteChannel channel, final ByteBuffer byteBuffer) throws IOException {
- final int expectedLength = byteBuffer.remaining();
- final int read = org.apache.commons.io.IOUtils.read(channel, byteBuffer);
- if (read < expectedLength) {
- throw new EOFException();
- }
+ org.apache.commons.io.IOUtils.readFully(channel, byteBuffer);
}
/**
@@ -235,7 +229,7 @@ public static void readFully(final ReadableByteChannel channel, final ByteBuffer
*/
public static byte[] readRange(final InputStream input, final int length) throws IOException {
final ByteArrayOutputStream output = new ByteArrayOutputStream();
- org.apache.commons.io.IOUtils.copyLarge(input, output, 0, MemoryLimitException.checkBytes(length, SOFT_MAX_ARRAY_LENGTH));
+ org.apache.commons.io.IOUtils.copyLarge(input, output, 0, MemoryLimitException.checkBytes(length, org.apache.commons.io.IOUtils.SOFT_MAX_ARRAY_LENGTH));
return output.toByteArray();
}
@@ -277,9 +271,11 @@ public static byte[] readRange(final ReadableByteChannel input, final int length
* @param toSkip the number of bytes to skip.
* @return the number of bytes actually skipped.
* @throws IOException Thrown if an I/O error occurs.
+ * @deprecated Since 1.29.0, use {@link org.apache.commons.io.IOUtils#skip(InputStream, long)} instead.
*/
+ @Deprecated
public static long skip(final InputStream input, final long toSkip) throws IOException {
- return org.apache.commons.io.IOUtils.skip(input, toSkip, org.apache.commons.io.IOUtils::byteArray);
+ return org.apache.commons.io.IOUtils.skip(input, toSkip);
}
/**
diff --git a/src/main/java/org/apache/commons/compress/utils/SeekableInMemoryByteChannel.java b/src/main/java/org/apache/commons/compress/utils/SeekableInMemoryByteChannel.java
index 7c6a8e542e8..2f998c0e26e 100644
--- a/src/main/java/org/apache/commons/compress/utils/SeekableInMemoryByteChannel.java
+++ b/src/main/java/org/apache/commons/compress/utils/SeekableInMemoryByteChannel.java
@@ -99,15 +99,6 @@ private void ensureOpen() throws ClosedChannelException {
}
}
- /**
- * Like {@link #size()} but never throws {@link ClosedChannelException}.
- *
- * @return See {@link #size()}.
- */
- public long getSize() {
- return size;
- }
-
@Override
public boolean isOpen() {
return !closed.get();
diff --git a/src/site/xdoc/conventions.xml b/src/site/xdoc/conventions.xml
index e18561230b5..4457a8639b6 100644
--- a/src/site/xdoc/conventions.xml
+++ b/src/site/xdoc/conventions.xml
@@ -36,7 +36,7 @@
We use some of the annotations from
- JCIP
+ JCIP
as Javadoc tags. The used tags are:
diff --git a/src/test/java/org/apache/commons/compress/AbstractTest.java b/src/test/java/org/apache/commons/compress/AbstractTest.java
index 1a72bb2985c..ba6489a6f4a 100644
--- a/src/test/java/org/apache/commons/compress/AbstractTest.java
+++ b/src/test/java/org/apache/commons/compress/AbstractTest.java
@@ -19,6 +19,7 @@
package org.apache.commons.compress;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.BufferedInputStream;
@@ -224,6 +225,16 @@ protected void closeQuietly(final Closeable closeable) {
IOUtils.closeQuietly(closeable);
}
+ protected long consumeEntries(final ArchiveInputStream in) throws IOException {
+ long count = 0;
+ E entry;
+ while ((entry = in.getNextEntry()) != null) {
+ count++;
+ assertNotNull(entry);
+ }
+ return count;
+ }
+
/**
* Creates an archive of text-based files in several directories. The archive name is the factory identifier for the archiver, for example zip, tar, cpio,
* jar, ar. The archive is created as a temp file.
diff --git a/src/test/java/org/apache/commons/compress/LegacyConstructorsTest.java b/src/test/java/org/apache/commons/compress/LegacyConstructorsTest.java
index 2a3d4122f59..40c11a81bf0 100644
--- a/src/test/java/org/apache/commons/compress/LegacyConstructorsTest.java
+++ b/src/test/java/org/apache/commons/compress/LegacyConstructorsTest.java
@@ -21,20 +21,33 @@
import static java.nio.charset.StandardCharsets.US_ASCII;
import static org.apache.commons.lang3.reflect.FieldUtils.readDeclaredField;
import static org.apache.commons.lang3.reflect.FieldUtils.readField;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
+import java.io.File;
+import java.io.IOException;
import java.io.InputStream;
+import java.nio.channels.SeekableByteChannel;
import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.util.stream.Stream;
import org.apache.commons.compress.archivers.arj.ArjArchiveInputStream;
import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream;
import org.apache.commons.compress.archivers.dump.DumpArchiveInputStream;
import org.apache.commons.compress.archivers.jar.JarArchiveInputStream;
+import org.apache.commons.compress.archivers.sevenz.SevenZFile;
+import org.apache.commons.compress.archivers.sevenz.SevenZFileOptions;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarFile;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+import org.apache.commons.compress.archivers.zip.ZipEncoding;
+import org.apache.commons.compress.archivers.zip.ZipFile;
+import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -52,7 +65,7 @@ private static InputStream getNestedInputStream(final InputStream is) throws Ref
return (InputStream) readField(is, "in", true);
}
- static Stream testCpioConstructors() {
+ static Stream testCpioConstructors() throws IOException {
final InputStream inputStream = mock(InputStream.class);
return Stream.of(
Arguments.of(new CpioArchiveInputStream(inputStream, 1024), inputStream, "US-ASCII", 1024),
@@ -60,7 +73,7 @@ static Stream testCpioConstructors() {
Arguments.of(new CpioArchiveInputStream(inputStream, "UTF-8"), inputStream, "UTF-8", 512));
}
- static Stream testTarConstructors() {
+ static Stream testTarConstructors() throws IOException {
final InputStream inputStream = mock(InputStream.class);
final String defaultEncoding = Charset.defaultCharset().name();
final String otherEncoding = "UTF-8".equals(defaultEncoding) ? "US-ASCII" : "UTF-8";
@@ -74,7 +87,7 @@ static Stream testTarConstructors() {
Arguments.of(new TarArchiveInputStream(inputStream, otherEncoding), inputStream, 10240, 512, otherEncoding, false));
}
- static Stream testZipConstructors() {
+ static Stream testZipConstructors() throws IOException {
final InputStream inputStream = mock(InputStream.class);
return Stream.of(
Arguments.of(new ZipArchiveInputStream(inputStream, "US-ASCII"), inputStream, "US-ASCII", true, false, false),
@@ -87,8 +100,7 @@ static Stream testZipConstructors() {
void testArjConstructor() throws Exception {
try (InputStream inputStream = Files.newInputStream(getPath("bla.arj"));
ArjArchiveInputStream archiveInputStream = new ArjArchiveInputStream(inputStream, "US-ASCII")) {
- // Arj wraps the input stream in a DataInputStream
- assertEquals(inputStream, getNestedInputStream(getNestedInputStream(archiveInputStream)));
+ assertEquals(inputStream, getNestedInputStream(archiveInputStream));
assertEquals(US_ASCII, archiveInputStream.getCharset());
}
}
@@ -120,6 +132,63 @@ void testJarConstructor() throws Exception {
}
}
+ static Stream testSevenZFileContructors() throws IOException {
+ final Path path = getPath("bla.7z");
+ final String defaultName = "unknown archive";
+ final String otherName = path.toAbsolutePath().toString();
+ final String customName = "customName";
+ final int defaultMemoryLimit = SevenZFileOptions.DEFAULT.getMaxMemoryLimitInKb();
+ final boolean defaultUseDefaultNameForUnnamedEntries = SevenZFileOptions.DEFAULT.getUseDefaultNameForUnnamedEntries();
+ final boolean defaultTryToRecoverBrokenArchives = SevenZFileOptions.DEFAULT.getTryToRecoverBrokenArchives();
+ final SevenZFileOptions otherOptions =
+ SevenZFileOptions.builder().withMaxMemoryLimitInKb(42).withTryToRecoverBrokenArchives(true).withUseDefaultNameForUnnamedEntries(true).build();
+ final char[] otherPassword = "password".toCharArray();
+ final byte[] otherPasswordBytes = "password".getBytes(StandardCharsets.UTF_16LE);
+ return Stream.of(
+ // From File
+ Arguments.of(new SevenZFile(path.toFile()), otherName, defaultMemoryLimit, defaultUseDefaultNameForUnnamedEntries,
+ defaultTryToRecoverBrokenArchives, null),
+ Arguments.of(new SevenZFile(path.toFile(), otherPasswordBytes), otherName, defaultMemoryLimit, defaultUseDefaultNameForUnnamedEntries,
+ defaultTryToRecoverBrokenArchives, otherPasswordBytes),
+ Arguments.of(new SevenZFile(path.toFile(), otherPassword), otherName, defaultMemoryLimit, defaultUseDefaultNameForUnnamedEntries,
+ defaultTryToRecoverBrokenArchives, otherPasswordBytes),
+ Arguments.of(new SevenZFile(path.toFile(), otherPassword, otherOptions), otherName, 42, true, true, otherPasswordBytes),
+ Arguments.of(new SevenZFile(path.toFile(), otherOptions), otherName, 42, true, true, null),
+ // From SeekableByteChannel
+ Arguments.of(new SevenZFile(Files.newByteChannel(path, StandardOpenOption.READ)), defaultName, defaultMemoryLimit,
+ defaultUseDefaultNameForUnnamedEntries, defaultTryToRecoverBrokenArchives, null),
+ Arguments.of(new SevenZFile(Files.newByteChannel(path, StandardOpenOption.READ), otherPasswordBytes), defaultName, defaultMemoryLimit,
+ defaultUseDefaultNameForUnnamedEntries, defaultTryToRecoverBrokenArchives, otherPasswordBytes),
+ Arguments.of(new SevenZFile(Files.newByteChannel(path, StandardOpenOption.READ), otherPassword), defaultName, defaultMemoryLimit,
+ defaultUseDefaultNameForUnnamedEntries, defaultTryToRecoverBrokenArchives, otherPasswordBytes),
+ Arguments.of(new SevenZFile(Files.newByteChannel(path, StandardOpenOption.READ), otherPassword, otherOptions), defaultName, 42, true, true,
+ otherPasswordBytes),
+ Arguments.of(new SevenZFile(Files.newByteChannel(path, StandardOpenOption.READ), otherOptions), defaultName, 42, true, true, null),
+ // From SeekableByteChannel with custom name
+ Arguments.of(new SevenZFile(Files.newByteChannel(path, StandardOpenOption.READ), customName), customName, defaultMemoryLimit,
+ defaultUseDefaultNameForUnnamedEntries, defaultTryToRecoverBrokenArchives, null),
+ Arguments.of(new SevenZFile(Files.newByteChannel(path, StandardOpenOption.READ), customName, otherPasswordBytes), customName,
+ defaultMemoryLimit, defaultUseDefaultNameForUnnamedEntries, defaultTryToRecoverBrokenArchives, otherPasswordBytes),
+ Arguments.of(new SevenZFile(Files.newByteChannel(path, StandardOpenOption.READ), customName, otherPassword), customName, defaultMemoryLimit,
+ defaultUseDefaultNameForUnnamedEntries, defaultTryToRecoverBrokenArchives, otherPasswordBytes),
+ Arguments.of(new SevenZFile(Files.newByteChannel(path, StandardOpenOption.READ), customName, otherPassword, otherOptions), customName, 42,
+ true, true, otherPasswordBytes),
+ Arguments.of(new SevenZFile(Files.newByteChannel(path, StandardOpenOption.READ), customName, otherOptions), customName, 42, true,
+ true, null));
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testSevenZFileContructors(final SevenZFile archiveFile, final String expectedName, final int expectedMemoryLimit,
+ final boolean expectedUseDefaultNameForUnnamedEntries, final boolean expectedTryToRecoverBrokenArchives,
+ final byte[] expectedPassword) throws Exception {
+ assertEquals(expectedName, readDeclaredField(archiveFile, "fileName", true));
+ assertEquals(expectedMemoryLimit, readDeclaredField(archiveFile, "maxMemoryLimitKiB", true));
+ assertEquals(expectedUseDefaultNameForUnnamedEntries, readDeclaredField(archiveFile, "useDefaultNameForUnnamedEntries", true));
+ assertEquals(expectedTryToRecoverBrokenArchives, readDeclaredField(archiveFile, "tryToRecoverBrokenArchives", true));
+ assertArrayEquals(expectedPassword, (byte[]) readDeclaredField(archiveFile, "password", true));
+ }
+
@ParameterizedTest
@MethodSource
void testTarConstructors(final TarArchiveInputStream archiveStream, final InputStream expectedInput, final int expectedBlockSize,
@@ -132,6 +201,35 @@ void testTarConstructors(final TarArchiveInputStream archiveStream, final InputS
assertEquals(expectedLenient, readField(archiveStream, "lenient", true));
}
+ static Stream testTarFileConstructors() throws IOException {
+ final Path path = getPath("bla.tar");
+ final File file = getFile("bla.tar");
+ final SeekableByteChannel channel = mock(SeekableByteChannel.class);
+ final String defaultEncoding = Charset.defaultCharset().name();
+ final String otherEncoding = "UTF-8".equals(defaultEncoding) ? "US-ASCII" : "UTF-8";
+ return Stream.of(
+ Arguments.of(new TarFile(IOUtils.EMPTY_BYTE_ARRAY), defaultEncoding, false),
+ Arguments.of(new TarFile(IOUtils.EMPTY_BYTE_ARRAY, true), defaultEncoding, true),
+ Arguments.of(new TarFile(IOUtils.EMPTY_BYTE_ARRAY, otherEncoding), otherEncoding, false),
+ Arguments.of(new TarFile(file), defaultEncoding, false),
+ Arguments.of(new TarFile(file, true), defaultEncoding, true),
+ Arguments.of(new TarFile(file, otherEncoding), otherEncoding, false),
+ Arguments.of(new TarFile(path), defaultEncoding, false),
+ Arguments.of(new TarFile(path, true), defaultEncoding, true),
+ Arguments.of(new TarFile(path, otherEncoding), otherEncoding, false),
+ Arguments.of(new TarFile(channel), defaultEncoding, false),
+ Arguments.of(new TarFile(channel, 1024, 1024, otherEncoding, true), otherEncoding, true));
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testTarFileConstructors(final TarFile tarFile, final String expectedEncoding, final boolean expectedLenient) throws Exception {
+ final ZipEncoding encoding = (ZipEncoding) readDeclaredField(tarFile, "zipEncoding", true);
+ final Charset charset = (Charset) readDeclaredField(encoding, "charset", true);
+ assertEquals(Charset.forName(expectedEncoding), charset);
+ assertEquals(expectedLenient, readDeclaredField(tarFile, "lenient", true));
+ }
+
@ParameterizedTest
@MethodSource
void testZipConstructors(final ZipArchiveInputStream archiveStream, final InputStream expectedInput, final String expectedEncoding,
@@ -144,4 +242,34 @@ void testZipConstructors(final ZipArchiveInputStream archiveStream, final InputS
assertEquals(expectedSupportStoredEntryDataDescriptor, readDeclaredField(archiveStream, "supportStoredEntryDataDescriptor", true));
assertEquals(expectedSkipSplitSignature, readDeclaredField(archiveStream, "skipSplitSignature", true));
}
+
+ static Stream testZipFileConstructors() throws IOException {
+ final Path path = getPath("bla.zip");
+ final String defaultEncoding = StandardCharsets.UTF_8.name();
+ final String otherEncoding = "UTF-8".equals(defaultEncoding) ? "US-ASCII" : "UTF-8";
+ return Stream.of(
+ Arguments.of(new ZipFile(path.toFile()), defaultEncoding, true),
+ Arguments.of(new ZipFile(path.toFile(), otherEncoding), otherEncoding, true),
+ Arguments.of(new ZipFile(path.toFile(), otherEncoding, false), otherEncoding, false),
+ Arguments.of(new ZipFile(path.toFile(), otherEncoding, false, true), otherEncoding, false),
+ Arguments.of(new ZipFile(path), defaultEncoding, true),
+ Arguments.of(new ZipFile(path, otherEncoding), otherEncoding, true),
+ Arguments.of(new ZipFile(path, otherEncoding, false), otherEncoding, false),
+ Arguments.of(new ZipFile(path, otherEncoding, false, true), otherEncoding, false),
+ Arguments.of(new ZipFile(Files.newByteChannel(path, StandardOpenOption.READ)), defaultEncoding, true),
+ Arguments.of(new ZipFile(Files.newByteChannel(path, StandardOpenOption.READ), otherEncoding), otherEncoding, true),
+ Arguments.of(new ZipFile(Files.newByteChannel(path, StandardOpenOption.READ), null, otherEncoding, false),
+ otherEncoding, false),
+ Arguments.of(new ZipFile(Files.newByteChannel(path, StandardOpenOption.READ), null, otherEncoding, false, true),
+ otherEncoding, false),
+ Arguments.of(new ZipFile(path.toAbsolutePath().toString()), defaultEncoding, true),
+ Arguments.of(new ZipFile(path.toAbsolutePath().toString(), otherEncoding), otherEncoding, true));
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testZipFileConstructors(final ZipFile zipFile, final String expectedEncoding, final boolean expectedUseUnicodeExtraFields) throws Exception {
+ assertEquals(Charset.forName(expectedEncoding), readDeclaredField(zipFile, "encoding", true));
+ assertEquals(expectedUseUnicodeExtraFields, readDeclaredField(zipFile, "useUnicodeExtraFields", true));
+ }
}
diff --git a/src/test/java/org/apache/commons/compress/archivers/AbstractArchiveFileTest.java b/src/test/java/org/apache/commons/compress/archivers/AbstractArchiveFileTest.java
new file mode 100644
index 00000000000..614faf09cbc
--- /dev/null
+++ b/src/test/java/org/apache/commons/compress/archivers/AbstractArchiveFileTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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
+ *
+ * https://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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.commons.compress.AbstractTest;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.function.IOIterator;
+import org.apache.commons.io.function.IOStream;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Abstracts tests for {@link ArchiveFile} implementations.
+ *
+ * @param The type of {@link ArchiveEntry} produced.
+ */
+public abstract class AbstractArchiveFileTest extends AbstractTest {
+
+ private static ArchiveEntry newEntry(final String name, final long size, final Instant lastModified) {
+ return new ArchiveEntry() {
+
+ @Override
+ public Date getLastModifiedDate() {
+ return Date.from(lastModified);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public long getSize() {
+ return size;
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return false;
+ }
+ };
+ }
+
+ private static ArchiveEntry newEntryUtc(final String name, final long size, final LocalDateTime lastModified) {
+ return newEntry(name, size, lastModified.toInstant(ZoneOffset.UTC));
+ }
+
+ /**
+ * Gets an {@link ArchiveFile} to be tested.
+ *
+ * @return The archive file to be tested.
+ * @throws IOException Indicates a test failure.
+ */
+ protected abstract ArchiveFile getArchiveFile() throws IOException;
+
+ /**
+ * Gets the expected entries in the test archive.
+ *
+ * @return The expected entries.
+ */
+ private List extends ArchiveEntry> getExpectedEntries() {
+ return Arrays.asList(
+ newEntryUtc("test1.xml", 610, LocalDateTime.of(2007, 11, 14, 10, 19, 2)),
+ newEntryUtc("test2.xml", 82, LocalDateTime.of(2007, 11, 14, 10, 19, 2)));
+ }
+
+ private T getMatchingEntry(final ArchiveFile extends T> archiveFile, final String name) throws Exception {
+ try (IOStream extends T> stream = archiveFile.stream()) {
+ return stream.filter(e -> e.getName().equals(name)).findFirst().orElse(null);
+ }
+ }
+
+ /**
+ * Tests that the entries returned by {@link ArchiveFile#entries()} match the expected entries.
+ */
+ @Test
+ void testEntries() throws Exception {
+ try (ArchiveFile archiveFile = getArchiveFile()) {
+ final List extends T> entries = archiveFile.entries();
+ final List extends ArchiveEntry> expectedEntries = getExpectedEntries();
+ assertEquals(expectedEntries.size(), entries.size(), "Number of entries");
+ for (int i = 0; i < expectedEntries.size(); i++) {
+ final ArchiveEntry expected = expectedEntries.get(i);
+ final ArchiveEntry actual = entries.get(i);
+ assertEquals(expected.getName(), actual.getName(), "Entry name at index " + i);
+ assertEquals(expected.getSize(), actual.getSize(), "Size of entry " + expected.getName());
+ assertEquals(expected.getLastModifiedDate(), actual.getLastModifiedDate(), "Last modified date of entry " + expected.getName());
+ }
+ }
+ }
+
+ /**
+ * Tests that the input streams returned by {@link ArchiveFile#getInputStream(ArchiveEntry)} match the expected
+ * entries.
+ */
+ @Test
+ void testGetInputStream() throws Exception {
+ try (ArchiveFile archiveFile = getArchiveFile()) {
+ final List extends ArchiveEntry> expectedEntries = getExpectedEntries();
+ for (final ArchiveEntry expected : expectedEntries) {
+ final T actual = getMatchingEntry(archiveFile, expected.getName());
+ assertNotNull(actual, "Entry " + expected.getName() + " not found");
+ try (InputStream inputStream = archiveFile.getInputStream(actual)) {
+ assertNotNull(inputStream, "Input stream for entry " + expected.getName());
+ final byte[] content = IOUtils.toByteArray(inputStream);
+ assertEquals(expected.getSize(), content.length, "Size of entry " + expected.getName());
+ }
+ }
+ }
+ }
+
+ /**
+ * Tests that the iterator returned by {@link ArchiveFile#iterator()} matches the expected entries.
+ */
+ @Test
+ void testIterator() throws Exception {
+ try (ArchiveFile archiveFile = getArchiveFile()) {
+ final IOIterator iterator = archiveFile.iterator();
+ final List extends ArchiveEntry> entries = getExpectedEntries();
+ int count = 0;
+ while (iterator.hasNext()) {
+ final ArchiveEntry expected = entries.get(count);
+ final ArchiveEntry actual = iterator.next();
+ assertEquals(expected.getName(), actual.getName(), "Entry name at index " + count);
+ assertEquals(expected.getSize(), actual.getSize(), "Size of entry " + expected.getName());
+ assertEquals(
+ expected.getLastModifiedDate(),
+ actual.getLastModifiedDate(),
+ "Last modified date of entry " + expected.getName());
+ count++;
+ }
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/compress/archivers/ArchiveStreamFactoryTest.java b/src/test/java/org/apache/commons/compress/archivers/ArchiveStreamFactoryTest.java
index abba17db4a1..6616469c038 100644
--- a/src/test/java/org/apache/commons/compress/archivers/ArchiveStreamFactoryTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/ArchiveStreamFactoryTest.java
@@ -96,10 +96,10 @@ public String toString() {
*/
private static final String ARJ_DEFAULT;
private static final String DUMP_DEFAULT;
- private static final String ZIP_DEFAULT = getCharsetName(new ZipArchiveInputStream(null));
- private static final String CPIO_DEFAULT = getCharsetName(new CpioArchiveInputStream(null));
- private static final String TAR_DEFAULT = getCharsetName(new TarArchiveInputStream(null));
- private static final String JAR_DEFAULT = getCharsetName(new JarArchiveInputStream(null));
+ private static final String ZIP_DEFAULT = getCharsetName(ZipArchiveInputStream.builder());
+ private static final String CPIO_DEFAULT = getCharsetName(CpioArchiveInputStream.builder());
+ private static final String TAR_DEFAULT = getCharsetName(TarArchiveInputStream.builder());
+ private static final String JAR_DEFAULT = getCharsetName(JarArchiveInputStream.builder());
static {
String dflt;
@@ -156,6 +156,10 @@ public String toString() {
new TestData("bla.zip", ArchiveStreamFactory.ZIP, true, StandardCharsets.UTF_8.name(), FACTORY_SET_UTF8, "charset"),
new TestData("bla.zip", ArchiveStreamFactory.ZIP, true, StandardCharsets.US_ASCII.name(), FACTORY_SET_ASCII, "charset"), };
+ private static String getCharsetName(final AbstractArchiveBuilder, ?> builder) {
+ return builder.getCharset().name();
+ }
+
private static String getCharsetName(final ArchiveInputStream> inputStream) {
return inputStream.getCharset().name();
}
diff --git a/src/test/java/org/apache/commons/compress/archivers/MaxNameEntryLengthTest.java b/src/test/java/org/apache/commons/compress/archivers/MaxNameEntryLengthTest.java
new file mode 100644
index 00000000000..10fef8f1488
--- /dev/null
+++ b/src/test/java/org/apache/commons/compress/archivers/MaxNameEntryLengthTest.java
@@ -0,0 +1,339 @@
+/*
+ * 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
+ *
+ * https://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;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import org.apache.commons.compress.AbstractTest;
+import org.apache.commons.compress.MemoryLimitException;
+import org.apache.commons.compress.archivers.ar.ArArchiveInputStream;
+import org.apache.commons.compress.archivers.arj.ArjArchiveInputStream;
+import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream;
+import org.apache.commons.compress.archivers.dump.DumpArchiveEntry;
+import org.apache.commons.compress.archivers.dump.DumpArchiveInputStream;
+import org.apache.commons.compress.archivers.sevenz.SevenZFile;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarFile;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+import org.apache.commons.compress.archivers.zip.ZipFile;
+import org.apache.commons.io.function.IOStream;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Tests handling of file names limits in various archive formats.
+ */
+public class MaxNameEntryLengthTest extends AbstractTest {
+
+ private static final int PORTABLE_NAME_LIMIT = 1023;
+ private static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
+
+ @SuppressWarnings("OctalInteger")
+ private static final int CPIO_OLD_ASCII_NAME_LIMIT = 0777_776;
+
+ static Stream testTruncatedStreams() throws IOException {
+ return Stream.of(
+ Arguments.of(
+ ArArchiveInputStream.builder()
+ .setMaxEntryNameLength(Integer.MAX_VALUE)
+ .setURI(getURI("synthetic/long-name/bsd-fail.ar"))
+ .get(),
+ SOFT_MAX_ARRAY_LENGTH),
+ Arguments.of(
+ ArArchiveInputStream.builder()
+ .setMaxEntryNameLength(Integer.MAX_VALUE)
+ .setURI(getURI("synthetic/long-name/gnu-fail.ar"))
+ .get(),
+ SOFT_MAX_ARRAY_LENGTH),
+ Arguments.of(
+ CpioArchiveInputStream.builder()
+ .setMaxEntryNameLength(Integer.MAX_VALUE)
+ .setURI(getURI("synthetic/long-name/odc-fail.cpio"))
+ .get(),
+ CPIO_OLD_ASCII_NAME_LIMIT),
+ Arguments.of(
+ CpioArchiveInputStream.builder()
+ .setMaxEntryNameLength(Integer.MAX_VALUE)
+ .setURI(getURI("synthetic/long-name/newc-fail.cpio"))
+ .get(),
+ SOFT_MAX_ARRAY_LENGTH),
+ Arguments.of(
+ CpioArchiveInputStream.builder()
+ .setMaxEntryNameLength(Integer.MAX_VALUE)
+ .setURI(getURI("synthetic/long-name/crc-fail.cpio"))
+ .get(),
+ SOFT_MAX_ARRAY_LENGTH),
+ Arguments.of(
+ TarArchiveInputStream.builder()
+ .setMaxEntryNameLength(Integer.MAX_VALUE)
+ .setURI(getURI("synthetic/long-name/pax-fail.tar"))
+ .get(),
+ // The PAX entry length is the limiting factor: "2147483647 path=...\n"
+ Integer.MAX_VALUE),
+ Arguments.of(
+ TarArchiveInputStream.builder()
+ .setMaxEntryNameLength(Integer.MAX_VALUE)
+ .setURI(getURI("synthetic/long-name/gnu-fail.tar"))
+ .get(),
+ SOFT_MAX_ARRAY_LENGTH));
+ }
+
+ static Stream testTruncatedTarFiles() throws IOException {
+ return Stream.of(
+ Arguments.of(
+ TarFile.builder().setMaxEntryNameLength(Integer.MAX_VALUE).setURI(getURI("synthetic/long-name/pax-fail.tar")),
+ Integer.MAX_VALUE
+ ),
+ Arguments.of(
+ TarFile.builder().setMaxEntryNameLength(Integer.MAX_VALUE).setURI(getURI("synthetic/long-name/gnu-fail.tar")),
+ SOFT_MAX_ARRAY_LENGTH
+ )
+ );
+ }
+
+ static Stream testValidStreams() throws IOException {
+ return Stream.of(
+ Arguments.of(
+ ArArchiveInputStream.builder().setURI(getURI("synthetic/long-name/bsd-short-max-value.ar")),
+ Short.MAX_VALUE),
+ Arguments.of(
+ ArArchiveInputStream.builder().setURI(getURI("synthetic/long-name/gnu-short-max-value.ar")),
+ Short.MAX_VALUE),
+ Arguments.of(ArjArchiveInputStream.builder().setURI(getURI("synthetic/long-name/long-name.arj")), 2568),
+ Arguments.of(
+ CpioArchiveInputStream.builder().setURI(getURI("synthetic/long-name/bin-big-endian.cpio")),
+ Short.MAX_VALUE - 1),
+ Arguments.of(
+ CpioArchiveInputStream.builder().setURI(getURI("synthetic/long-name/bin-little-endian.cpio")),
+ Short.MAX_VALUE - 1),
+ Arguments.of(
+ CpioArchiveInputStream.builder().setURI(getURI("synthetic/long-name/odc.cpio")),
+ Short.MAX_VALUE),
+ Arguments.of(
+ CpioArchiveInputStream.builder().setURI(getURI("synthetic/long-name/newc.cpio")),
+ Short.MAX_VALUE),
+ Arguments.of(
+ CpioArchiveInputStream.builder().setURI(getURI("synthetic/long-name/crc.cpio")),
+ Short.MAX_VALUE),
+ Arguments.of(
+ TarArchiveInputStream.builder().setURI(getURI("synthetic/long-name/pax.tar")), Short.MAX_VALUE),
+ Arguments.of(
+ TarArchiveInputStream.builder().setURI(getURI("synthetic/long-name/gnu.tar")), Short.MAX_VALUE),
+ Arguments.of(
+ ZipArchiveInputStream.builder().setURI(getURI("synthetic/long-name/long-name.zip")),
+ Short.MAX_VALUE));
+ }
+
+ static Stream testValidTarFiles() throws IOException {
+ return Stream.of(
+ Arguments.of(TarFile.builder().setURI(getURI("synthetic/long-name/pax.tar")), Short.MAX_VALUE),
+ Arguments.of(TarFile.builder().setURI(getURI("synthetic/long-name/gnu.tar")), Short.MAX_VALUE));
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testTruncatedStreams(final ArchiveInputStream> archiveInputStream, final long expectedLength) {
+ // If the file name length exceeds available memory, the stream fails fast with MemoryLimitException.
+ // Otherwise, it fails with EOFException when the stream ends unexpectedly.
+ if (Runtime.getRuntime().totalMemory() < expectedLength) {
+ final MemoryLimitException exception = assertThrows(MemoryLimitException.class, archiveInputStream::getNextEntry);
+ final String message = exception.getMessage();
+ assertNotNull(message);
+ assertTrue(message.contains(String.format("%,d", expectedLength)), "Message mentions expected length (" + expectedLength + "): " + message);
+ } else {
+ assertThrows(EOFException.class, archiveInputStream::getNextEntry);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testTruncatedTarFiles(final TarFile.Builder tarFileBuilder, final long expectedLength) {
+ // If the file name length exceeds available memory, the stream fails fast with MemoryLimitException.
+ // Otherwise, it fails with EOFException when the stream ends unexpectedly.
+ final Executable action = () -> tarFileBuilder.get().entries();
+ if (Runtime.getRuntime().totalMemory() < expectedLength) {
+ final MemoryLimitException exception = assertThrows(MemoryLimitException.class, action);
+ final String message = exception.getMessage();
+ assertNotNull(message);
+ assertTrue(message.contains(String.format("%,d", expectedLength)), "Message mentions expected length (" + expectedLength + "): " + message);
+ } else {
+ assertThrows(EOFException.class, action);
+ }
+ }
+
+ @Test
+ void testValid7ZipFile() throws IOException {
+ final SevenZFile.Builder builder = SevenZFile.builder().setURI(getURI("synthetic/long-name/long-name.7z"));
+ final int expectedLength = Short.MAX_VALUE;
+ try (SevenZFile sevenZFile = builder.get()) {
+ final ArchiveEntry entry = sevenZFile.getNextEntry();
+ assertNotNull(entry);
+ final String name = entry.getName();
+ assertEquals(expectedLength, name.length(), "Unexpected name length");
+ final String expected = StringUtils.repeat("a", expectedLength);
+ assertEquals(expected, name);
+ }
+ // SevenZFile parses the whole archive at once, so the builder throws the exception.
+ final ArchiveException exception = assertThrows(ArchiveException.class, () -> builder.setMaxEntryNameLength(PORTABLE_NAME_LIMIT).get());
+ final String message = exception.getMessage();
+ assertNotNull(message);
+ assertTrue(message.contains("file name length"));
+ assertTrue(message.contains(String.format("%,d", expectedLength)));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"synthetic/long-name/long-name.dump", "synthetic/long-name/long-name-reversed.dump"})
+ void testValidDumpStreams(final String resourceName) throws IOException {
+ final int rootInode = 2;
+ final int expectedDepth = 127; // number of nested directories
+ final int nameSegmentLength = 255; // length of each segment
+ final int totalEntries = 1 + expectedDepth + 1; // root + 127 dirs + 1 file
+ final int maxInode = rootInode + totalEntries - 1;
+ final String nameSegment = StringUtils.repeat('a', nameSegmentLength);
+ final DumpArchiveInputStream.Builder builder = DumpArchiveInputStream.builder().setURI(getURI(resourceName));
+ try (DumpArchiveInputStream in = builder.get()) {
+ for (int expectedInode = rootInode; expectedInode <= maxInode; expectedInode++) {
+ final boolean isRegularFile = expectedInode == maxInode;
+ final DumpArchiveEntry entry = in.getNextEntry();
+ assertNotNull(entry, "Entry " + expectedInode + " should exist");
+ // Type checks: root + 127 are directories, last is a regular file.
+ assertEquals(!isRegularFile, entry.isDirectory(), "isDirectory() mismatch");
+ final int depth = expectedInode - rootInode; // 0 for root, 1..127 for dirs, 128 for file’s dir count
+ final String expectedNameDirs = StringUtils.repeat(nameSegment + "/", depth);
+ final int expectedLength = (nameSegmentLength + 1) * depth - (isRegularFile ? 1 : 0);
+ final String actualName = entry.getName();
+ assertEquals(expectedInode, entry.getIno(), "inode");
+ assertEquals(expectedLength, actualName.length(), "name length");
+ assertEquals(expectedNameDirs.substring(0, expectedLength), actualName, "full name");
+ // Structure checks: every path component is exactly 255×'a'
+ String[] parts = actualName.split("/");
+ if (parts.length > 0 && parts[parts.length - 1].isEmpty()) {
+ // Trailing slash yields an empty final component; ignore it.
+ parts = Arrays.copyOf(parts, parts.length - 1);
+ }
+ // For directories: depth components; for file: depth components (including file itself)
+ assertEquals(depth, parts.length, "component count");
+ for (int i = 0; i < parts.length; i++) {
+ assertEquals(nameSegmentLength, parts[i].length(), "segment[" + i + "] length");
+ assertEquals(nameSegment, parts[i], "segment[" + i + "] content");
+ }
+ }
+ // Stream should now be exhausted.
+ assertNull(in.getNextEntry(), "No more entries expected after " + totalEntries);
+ }
+ try (DumpArchiveInputStream in = builder.setMaxEntryNameLength(PORTABLE_NAME_LIMIT).get()) {
+ int expectedLength;
+ for (int depth = 0;; depth++) {
+ expectedLength = depth * (nameSegmentLength + 1);
+ if (expectedLength > PORTABLE_NAME_LIMIT) {
+ break;
+ }
+ assertDoesNotThrow(in::getNextEntry, "Entry " + (rootInode + depth) + " should be readable");
+ }
+ final ArchiveException exception = assertThrows(ArchiveException.class, in::getNextEntry);
+ final String message = exception.getMessage();
+ assertNotNull(message);
+ assertTrue(message.contains("file name length"));
+ assertTrue(message.contains(String.format("%,d", expectedLength)));
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testValidStreams(final AbstractArchiveBuilder>, ?> builder, final int expectedLength)
+ throws IOException {
+ try (ArchiveInputStream> archiveInputStream = builder.get()) {
+ final ArchiveEntry entry = archiveInputStream.getNextEntry();
+ assertNotNull(entry);
+ final String name = entry.getName();
+ assertEquals(expectedLength, name.length(), "Unexpected name length");
+ final String expected = StringUtils.repeat("a", expectedLength);
+ assertEquals(expected, name);
+ }
+ // Impose a file name length limit and verify that it is enforced.
+ builder.setMaxEntryNameLength(PORTABLE_NAME_LIMIT);
+ try (ArchiveInputStream> archiveInputStream = builder.get()) {
+ final ArchiveException exception = assertThrows(ArchiveException.class, archiveInputStream::getNextEntry);
+ final String message = exception.getMessage();
+ assertNotNull(message);
+ assertTrue(message.contains("file name length"));
+ assertTrue(message.contains(String.format("%,d", expectedLength)));
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testValidTarFiles(final TarFile.Builder tarFileBuilder, final int expectedLength) throws IOException {
+ try (TarFile tarFile = tarFileBuilder.get()) {
+ for (final ArchiveEntry entry : tarFile.getEntries()) {
+ assertNotNull(entry);
+ final String name = entry.getName();
+ assertEquals(expectedLength, name.length(), "Unexpected name length");
+ final String expected = StringUtils.repeat("a", expectedLength);
+ assertEquals(expected, name);
+ }
+ }
+ // Impose a file name length limit and verify that it is enforced.
+ tarFileBuilder.setMaxEntryNameLength(PORTABLE_NAME_LIMIT);
+ final ArchiveException exception = assertThrows(ArchiveException.class, () -> tarFileBuilder.get());
+ final String message = exception.getMessage();
+ assertNotNull(message);
+ assertTrue(message.contains("file name length"));
+ assertTrue(message.contains(String.format("%,d", expectedLength)));
+ }
+
+ @Test
+ void testValidZipFile() throws IOException {
+ final ZipFile.Builder builder = ZipFile.builder().setURI(getURI("synthetic/long-name/long-name.zip"));
+ final int expectedLength = Short.MAX_VALUE;
+ try (ZipFile zipFile = builder.get();
+ IOStream extends ZipArchiveEntry> entries = zipFile.stream()) {
+ entries.forEach(entry -> {
+ assertNotNull(entry);
+ final String name = entry.getName();
+ assertEquals(expectedLength, name.length(), "Unexpected name length");
+ final String expected = StringUtils.repeat("a", expectedLength);
+ assertEquals(expected, name);
+ });
+ }
+ // Impose a file name length limit and verify that it is enforced.
+ builder.setMaxEntryNameLength(PORTABLE_NAME_LIMIT);
+ final ArchiveException exception = assertThrows(ArchiveException.class, builder::get);
+ final String message = exception.getMessage();
+ assertNotNull(message);
+ assertTrue(message.contains("file name length"), "Message mentions file name length: " + message);
+ assertTrue(message.contains(String.format("%,d", expectedLength)));
+ }
+}
diff --git a/src/test/java/org/apache/commons/compress/archivers/TarTest.java b/src/test/java/org/apache/commons/compress/archivers/TarTest.java
index 09116d76c2c..f80b05f32ba 100644
--- a/src/test/java/org/apache/commons/compress/archivers/TarTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/TarTest.java
@@ -289,7 +289,7 @@ void testTarArchiveLongNameCreation() throws Exception {
@Test
void testTarFileCOMPRESS114() throws Exception {
final File input = getFile("COMPRESS-114.tar");
- try (TarFile tarFile = new TarFile(input, StandardCharsets.ISO_8859_1.name())) {
+ try (TarFile tarFile = TarFile.builder().setURI(getURI("COMPRESS-114.tar")).setCharset(StandardCharsets.ISO_8859_1).get()) {
final List entries = tarFile.getEntries();
TarArchiveEntry entry = entries.get(0);
assertEquals("3\u00b1\u00b1\u00b1F06\u00b1W2345\u00b1ZB\u00b1la\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1BLA", entry.getName());
@@ -304,7 +304,7 @@ void testTarFileCOMPRESS114() throws Exception {
void testTarFileCOMPRESS178() throws Exception {
final File input = getFile("COMPRESS-178-fail.tar");
final IOException e = assertThrows(ArchiveException.class, () -> {
- try (TarFile tarFile = new TarFile(input)) {
+ try (TarFile tarFile = TarFile.builder().setFile(input).get()) {
// Compared to the TarArchiveInputStream all entries are read when instantiating the tar file
}
}, "Expected IOException");
@@ -315,7 +315,7 @@ void testTarFileCOMPRESS178() throws Exception {
@Test
void testTarFileCOMPRESS178Lenient() throws Exception {
final File input = getFile("COMPRESS-178-fail.tar");
- try (TarFile tarFile = new TarFile(input, true)) {
+ try (TarFile tarFile = TarFile.builder().setFile(input).setLenient(true).get()) {
// Compared to the TarArchiveInputStream all entries are read when instantiating the tar file
}
}
@@ -330,7 +330,7 @@ void testTarFileDirectoryEntryFromFile() throws Exception {
tos.putArchiveEntry(in);
tos.closeArchiveEntry();
tos.close();
- try (TarFile tarFile = new TarFile(archive)) {
+ try (TarFile tarFile = TarFile.builder().setFile(archive).get()) {
final TarArchiveEntry entry = tarFile.getEntries().get(0);
assertNotNull(entry);
assertEquals("foo/", entry.getName());
@@ -346,7 +346,7 @@ void testTarFileDirectoryEntryFromFile() throws Exception {
@Test
void testTarFileDirectoryRead() throws IOException {
final File input = getFile("directory.tar");
- try (TarFile tarFile = new TarFile(input)) {
+ try (TarFile tarFile = TarFile.builder().setFile(input).get()) {
final TarArchiveEntry directoryEntry = tarFile.getEntries().get(0);
assertEquals("directory/", directoryEntry.getName());
assertEquals(TarConstants.LF_DIR, directoryEntry.getLinkFlag());
@@ -368,7 +368,7 @@ void testTarFileEntryFromFile() throws Exception {
outputStream.write(file);
outputStream.closeArchiveEntry();
outputStream.close();
- try (TarFile tarFile = new TarFile(archive)) {
+ try (TarFile tarFile = TarFile.builder().setFile(archive).get()) {
final TarArchiveEntry entry = tarFile.getEntries().get(0);
assertNotNull(entry);
assertEquals("foo", entry.getName());
@@ -390,7 +390,7 @@ void testTarFileExplicitDirectoryEntry() throws Exception {
tos.putArchiveEntry(in);
tos.closeArchiveEntry();
tos.close();
- try (TarFile tarFile = new TarFile(archive)) {
+ try (TarFile tarFile = TarFile.builder().setFile(archive).get()) {
final TarArchiveEntry entry = tarFile.getEntries().get(0);
assertNotNull(entry);
assertEquals("foo/", entry.getName());
@@ -413,7 +413,7 @@ void testTarFileExplicitFileEntry() throws Exception {
outputStream.putArchiveEntry(in);
outputStream.write(file);
outputStream.closeArchiveEntry();
- try (TarFile tarFile = new TarFile(archive)) {
+ try (TarFile tarFile = TarFile.builder().setFile(archive).get()) {
final TarArchiveEntry entry = tarFile.getEntries().get(0);
assertNotNull(entry);
assertEquals("foo", entry.getName());
@@ -433,7 +433,7 @@ void testTarFileLongNameLargerThanBuffer() throws IOException {
final String fileName = createLongName(length);
assertEquals(length.intValue(), fileName.length());
final byte[] data = createTarWithOneLongNameEntry(fileName);
- try (TarFile tarFile = new TarFile(data)) {
+ try (TarFile tarFile = TarFile.builder().setByteArray(data).get()) {
final List entries = tarFile.getEntries();
assertEquals(fileName, entries.get(0).getName());
assertEquals(TarConstants.LF_NORMAL, entries.get(0).getLinkFlag());
@@ -444,7 +444,7 @@ void testTarFileLongNameLargerThanBuffer() throws IOException {
@Test
void testTarFileUnarchive() throws Exception {
final File file = getFile("bla.tar");
- try (TarFile tarFile = new TarFile(file)) {
+ try (TarFile tarFile = TarFile.builder().setFile(file).get()) {
final TarArchiveEntry entry = tarFile.getEntries().get(0);
try (InputStream inputStream = tarFile.getInputStream(entry)) {
Files.copy(inputStream, newTempFile(entry.getName()).toPath());
diff --git a/src/test/java/org/apache/commons/compress/archivers/TestArchiveGenerator.java b/src/test/java/org/apache/commons/compress/archivers/TestArchiveGenerator.java
new file mode 100644
index 00000000000..a7c4fde0240
--- /dev/null
+++ b/src/test/java/org/apache/commons/compress/archivers/TestArchiveGenerator.java
@@ -0,0 +1,386 @@
+/*
+ * 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
+ *
+ * https://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;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+
+public final class TestArchiveGenerator {
+
+ private static final byte[] USTAR_TRAILER = new byte[1024];
+ private static final int FILE_MODE = 0100644;
+ private static final int GROUP_ID = 0;
+ private static final String GROUP_NAME = "group";
+ // TAR
+ private static final String OLD_GNU_MAGIC = "ustar ";
+ private static final int OWNER_ID = 0;
+ private static final String OWNER_NAME = "owner";
+ private static final String PAX_MAGIC = "ustar\u000000";
+ private static final int TIMESTAMP = 0;
+
+ private static byte[] createData(final int size) {
+ final byte[] data = new byte[size];
+ for (int i = 0; i < size; i++) {
+ data[i] = (byte) (i % 256);
+ }
+ return data;
+ }
+
+ // Very fragmented sparse file
+ private static List> createFragmentedSparseEntries(final int realSize) {
+ final List> sparseEntries = new ArrayList<>();
+ for (int offset = 0; offset < realSize; offset++) {
+ sparseEntries.add(Pair.of(offset, 1));
+ }
+ return sparseEntries;
+ }
+
+ private static byte[] createGnuSparse00PaxData(
+ final Collection extends Pair> sparseEntries, final int realSize) {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos, US_ASCII))) {
+ writePaxKeyValue("GNU.sparse.size", realSize, writer);
+ writePaxKeyValue("GNU.sparse.numblocks", sparseEntries.size(), writer);
+ for (final Pair entry : sparseEntries) {
+ writePaxKeyValue("GNU.sparse.offset", entry.getLeft(), writer);
+ writePaxKeyValue("GNU.sparse.numbytes", entry.getRight(), writer);
+ }
+ }
+ return baos.toByteArray();
+ }
+
+ private static byte[] createGnuSparse01PaxData(
+ final Collection extends Pair> sparseEntries, final int realSize) {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos, US_ASCII))) {
+ writePaxKeyValue("GNU.sparse.size", realSize, writer);
+ writePaxKeyValue("GNU.sparse.numblocks", sparseEntries.size(), writer);
+ final String map = sparseEntries.stream()
+ .map(e -> e.getLeft() + "," + e.getRight())
+ .collect(Collectors.joining(","));
+ writePaxKeyValue("GNU.sparse.map", map, writer);
+ }
+ return baos.toByteArray();
+ }
+
+ private static byte[] createGnuSparse1EntriesData(final Collection extends Pair> sparseEntries)
+ throws IOException {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos, US_ASCII))) {
+ writer.printf("%d\n", sparseEntries.size());
+ for (final Pair entry : sparseEntries) {
+ writer.printf("%d\n", entry.getLeft());
+ writer.printf("%d\n", entry.getRight());
+ }
+ }
+ padTo512Bytes(baos.size(), baos);
+ return baos.toByteArray();
+ }
+
+ private static byte[] createGnuSparse1PaxData(
+ final Collection> sparseEntries, final int realSize) {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos, US_ASCII))) {
+ writePaxKeyValue("GNU.sparse.realsize", realSize, writer);
+ writePaxKeyValue("GNU.sparse.numblocks", sparseEntries.size(), writer);
+ writePaxKeyValue("GNU.sparse.major", 1, writer);
+ writePaxKeyValue("GNU.sparse.minor", 0, writer);
+ }
+ return baos.toByteArray();
+ }
+
+ public static void createSparseFileTestCases(final Path path) throws IOException {
+ if (!Files.isDirectory(path)) {
+ throw new IllegalArgumentException("Not a directory: " + path);
+ }
+ oldGnuSparse(path);
+ gnuSparse00(path);
+ gnuSparse01(path);
+ gnuSparse1X(path);
+ }
+
+ private static void gnuSparse00(final Path path) throws IOException {
+ final Path file = path.resolve("gnu-sparse-00.tar");
+ try (OutputStream out = Files.newOutputStream(file)) {
+ final byte[] data = createData(8 * 1024);
+ final List> sparseEntries = createFragmentedSparseEntries(data.length);
+ final byte[] paxData = createGnuSparse00PaxData(sparseEntries, data.length);
+ writeGnuSparse0File(data, paxData, out);
+ writeUstarTrailer(out);
+ }
+ }
+
+ private static void gnuSparse01(final Path path) throws IOException {
+ final Path file = path.resolve("gnu-sparse-01.tar");
+ try (OutputStream out = Files.newOutputStream(file)) {
+ final byte[] data = createData(8 * 1024);
+ final List> sparseEntries = createFragmentedSparseEntries(data.length);
+ final byte[] paxData = createGnuSparse01PaxData(sparseEntries, data.length);
+ writeGnuSparse0File(data, paxData, out);
+ writeUstarTrailer(out);
+ }
+ }
+
+ private static void gnuSparse1X(final Path path) throws IOException {
+ final Path file = path.resolve("gnu-sparse-1.tar");
+ try (OutputStream out = Files.newOutputStream(file)) {
+ final byte[] data = createData(8 * 1024);
+ final List> sparseEntries = createFragmentedSparseEntries(data.length);
+ writeGnuSparse1File(sparseEntries, data, out);
+ writeUstarTrailer(out);
+ }
+ }
+
+ public static void main(final String[] args) throws IOException {
+ if (args.length != 1) {
+ System.err.println("Expected one argument: output directory");
+ System.exit(1);
+ }
+ final Path path = Paths.get(args[0]);
+ if (!Files.isDirectory(path)) {
+ System.err.println("Not a directory: " + path);
+ System.exit(1);
+ }
+ // Sparse file examples
+ final Path sparsePath = path.resolve("sparse");
+ Files.createDirectories(sparsePath);
+ createSparseFileTestCases(sparsePath);
+ }
+
+ private static void oldGnuSparse(final Path path) throws IOException {
+ final Path file = path.resolve("old-gnu-sparse.tar");
+ try (OutputStream out = Files.newOutputStream(file)) {
+ final byte[] data = createData(8 * 1024);
+ final List> sparseEntries = createFragmentedSparseEntries(data.length);
+ writeOldGnuSparseFile(sparseEntries, data, data.length, out);
+ writeUstarTrailer(out);
+ }
+ }
+
+ private static int padTo512Bytes(final int offset, final OutputStream out) throws IOException {
+ int count = offset;
+ while (count % 512 != 0) {
+ out.write(0);
+ count++;
+ }
+ return count;
+ }
+
+ private static void writeGnuSparse0File(final byte[] data, final byte[] paxData, final OutputStream out)
+ throws IOException {
+ // PAX entry
+ int offset = writeTarUstarHeader("./GNUSparseFile.1/" + "sparse-file.txt", paxData.length, PAX_MAGIC, 'x', out);
+ offset = padTo512Bytes(offset, out);
+ // PAX data
+ out.write(paxData);
+ offset += paxData.length;
+ offset = padTo512Bytes(offset, out);
+ // File entry
+ offset += writeTarUstarHeader("sparse-file.txt", data.length, PAX_MAGIC, '0', out);
+ offset = padTo512Bytes(offset, out);
+ // File data
+ out.write(data);
+ offset += data.length;
+ padTo512Bytes(offset, out);
+ }
+
+ private static void writeGnuSparse1File(
+ final Collection> sparseEntries, final byte[] data, final OutputStream out)
+ throws IOException {
+ // PAX entry
+ final byte[] paxData = createGnuSparse1PaxData(sparseEntries, data.length);
+ int offset = writeTarUstarHeader("./GNUSparseFile.1/sparse-file.txt", paxData.length, PAX_MAGIC, 'x', out);
+ offset = padTo512Bytes(offset, out);
+ // PAX data
+ out.write(paxData);
+ offset += paxData.length;
+ offset = padTo512Bytes(offset, out);
+ // File entry
+ final byte[] sparseEntriesData = createGnuSparse1EntriesData(sparseEntries);
+ offset += writeTarUstarHeader("sparse-file.txt", sparseEntriesData.length + data.length, PAX_MAGIC, '0', out);
+ offset = padTo512Bytes(offset, out);
+ // File data
+ out.write(sparseEntriesData);
+ offset += sparseEntriesData.length;
+ out.write(data);
+ offset += data.length;
+ padTo512Bytes(offset, out);
+ }
+
+ private static int writeOctalString(final long value, final int length, final OutputStream out) throws IOException {
+ int count = 0;
+ final String s = Long.toOctalString(value);
+ count += writeString(s, length - 1, out);
+ out.write('\0');
+ return ++count;
+ }
+
+ private static int writeOldGnuSparseEntries(
+ final Iterable> sparseEntries, final int limit, final OutputStream out)
+ throws IOException {
+ int offset = 0;
+ int count = 0;
+ final Iterator> it = sparseEntries.iterator();
+ while (it.hasNext()) {
+ if (count >= limit) {
+ out.write(1); // more entries follow
+ return ++offset;
+ }
+ final Pair entry = it.next();
+ it.remove();
+ count++;
+ offset += writeOldGnuSparseEntry(entry.getLeft(), entry.getRight(), out);
+ }
+ while (count < limit) {
+ // pad with empty entries
+ offset += writeOldGnuSparseEntry(0, 0, out);
+ count++;
+ }
+ out.write(0); // no more entries
+ return ++offset;
+ }
+
+ private static int writeOldGnuSparseEntry(final int offset, final int length, final OutputStream out)
+ throws IOException {
+ int count = 0;
+ count += writeOctalString(offset, 12, out);
+ count += writeOctalString(length, 12, out);
+ return count;
+ }
+
+ private static int writeOldGnuSparseExtendedHeader(
+ final Iterable> sparseEntries, final OutputStream out) throws IOException {
+ int offset = 0;
+ offset += writeOldGnuSparseEntries(sparseEntries, 21, out);
+ offset = padTo512Bytes(offset, out);
+ return offset;
+ }
+
+ private static void writeOldGnuSparseFile(
+ final Collection> sparseEntries,
+ final byte[] data,
+ final int realSize,
+ final OutputStream out)
+ throws IOException {
+ int offset = writeTarUstarHeader("sparse-file.txt", data.length, OLD_GNU_MAGIC, 'S', out);
+ while (offset < 386) {
+ out.write(0);
+ offset++;
+ }
+ // Sparse entries (24 bytes each)
+ offset += writeOldGnuSparseEntries(sparseEntries, 4, out);
+ // Real size (12 bytes)
+ offset += writeOctalString(realSize, 12, out);
+ offset = padTo512Bytes(offset, out);
+ // Write extended headers
+ while (!sparseEntries.isEmpty()) {
+ offset += writeOldGnuSparseExtendedHeader(sparseEntries, out);
+ }
+ // Write file data
+ out.write(data);
+ offset += data.length;
+ padTo512Bytes(offset, out);
+ }
+
+ private static void writePaxKeyValue(final String key, final int value, final PrintWriter out) {
+ writePaxKeyValue(key, Integer.toString(value), out);
+ }
+
+ private static void writePaxKeyValue(final String key, final String value, final PrintWriter out) {
+ final String entry = ' ' + key + "=" + value + "\n";
+ // Guess length: length of length + space + entry
+ final int length = String.valueOf(entry.length()).length() + entry.length();
+ // Recompute if number of digits changes
+ out.print(String.valueOf(length).length() + entry.length());
+ out.print(entry);
+ }
+
+ private static int writeString(final String s, final int length, final OutputStream out) throws IOException {
+ final byte[] bytes = s.getBytes(US_ASCII);
+ out.write(bytes);
+ for (int i = bytes.length; i < length; i++) {
+ out.write('\0');
+ }
+ return length;
+ }
+
+ private static int writeTarUstarHeader(
+ final String fileName,
+ final long fileSize,
+ final String magicAndVersion,
+ final char typeFlag,
+ final OutputStream out)
+ throws IOException {
+ int count = 0;
+ // File name (100 bytes)
+ count += writeString(fileName, 100, out);
+ // File mode (8 bytes)
+ count += writeOctalString(FILE_MODE, 8, out);
+ // Owner ID (8 bytes)
+ count += writeOctalString(OWNER_ID, 8, out);
+ // Group ID (8 bytes)
+ count += writeOctalString(GROUP_ID, 8, out);
+ // File size (12 bytes)
+ count += writeOctalString(fileSize, 12, out);
+ // Modification timestamp (12 bytes)
+ count += writeOctalString(TIMESTAMP, 12, out);
+ // Checksum (8 bytes), filled with spaces for now
+ count += writeString(StringUtils.repeat(' ', 7), 8, out);
+ // Link indicator (1 byte)
+ out.write(typeFlag);
+ count++;
+ // Name of linked file (100 bytes)
+ count += writeString("", 100, out);
+ // Magic (6 bytes) + Version (2 bytes)
+ count += writeString(magicAndVersion, 8, out);
+ // Owner user name (32 bytes)
+ count += writeString(OWNER_NAME, 32, out);
+ // Owner group name (32 bytes)
+ count += writeString(GROUP_NAME, 32, out);
+ // Device major number (8 bytes)
+ count += writeString("", 8, out);
+ // Device minor number (8 bytes)
+ count += writeString("", 8, out);
+ return count;
+ }
+
+ private static void writeUstarTrailer(final OutputStream out) throws IOException {
+ out.write(USTAR_TRAILER);
+ }
+
+ private TestArchiveGenerator() {
+ // hide constructor
+ }
+}
diff --git a/src/test/java/org/apache/commons/compress/archivers/ZipTest.java b/src/test/java/org/apache/commons/compress/archivers/ZipTest.java
index d1cebc36f25..9e39f930578 100644
--- a/src/test/java/org/apache/commons/compress/archivers/ZipTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/ZipTest.java
@@ -113,8 +113,8 @@ private void assertSameFileContents(final File expectedFile, final File actualFi
try (InputStream actualIs = actual.getInputStream(actualElement);
InputStream expectedIs = expected.getInputStream(expectedElement)) {
- org.apache.commons.compress.utils.IOUtils.readFully(expectedIs, expectedBuf);
- org.apache.commons.compress.utils.IOUtils.readFully(actualIs, actualBuf);
+ IOUtils.read(expectedIs, expectedBuf);
+ IOUtils.read(actualIs, actualBuf);
}
assertArrayEquals(expectedBuf, actualBuf); // Buffers are larger than payload. don't care
}
diff --git a/src/test/java/org/apache/commons/compress/archivers/ar/ArArchiveInputStreamTest.java b/src/test/java/org/apache/commons/compress/archivers/ar/ArArchiveInputStreamTest.java
index 2504eeb207e..46345b7829a 100644
--- a/src/test/java/org/apache/commons/compress/archivers/ar/ArArchiveInputStreamTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/ar/ArArchiveInputStreamTest.java
@@ -24,7 +24,6 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.Mockito.mock;
import java.io.BufferedInputStream;
import java.io.EOFException;
@@ -221,8 +220,7 @@ public int read() throws IOException {
@Test
void testSingleArgumentConstructor() throws Exception {
- final InputStream inputStream = mock(InputStream.class);
- try (ArArchiveInputStream archiveStream = new ArArchiveInputStream(inputStream)) {
+ try (ArArchiveInputStream archiveStream = ArArchiveInputStream.builder().setURI(getURI("bla.ar")).get()) {
assertEquals(US_ASCII, archiveStream.getCharset());
}
}
diff --git a/src/test/java/org/apache/commons/compress/archivers/arj/ArjArchiveInputStreamTest.java b/src/test/java/org/apache/commons/compress/archivers/arj/ArjArchiveInputStreamTest.java
index d076c8a3db5..7fdca647836 100644
--- a/src/test/java/org/apache/commons/compress/archivers/arj/ArjArchiveInputStreamTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/arj/ArjArchiveInputStreamTest.java
@@ -23,28 +23,71 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.SequenceInputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Calendar;
import java.util.TimeZone;
+import java.util.stream.Stream;
+import java.util.zip.CRC32;
import org.apache.commons.compress.AbstractTest;
import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.io.EndianUtils;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.BoundedInputStream;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.junitpioneer.jupiter.cartesian.CartesianTest;
/**
* Tests {@link ArjArchiveInputStream}.
*/
class ArjArchiveInputStreamTest extends AbstractTest {
+ private static byte[] createArjArchiveHeader(final int size, final boolean computeCrc) {
+ // Enough space for the fixed-size portion of the header plus:
+ // - signature (2 bytes)
+ // - the 2-byte basic header size field itself (2 bytes)
+ // - at least one byte each for the filename and comment C-strings (2 bytes)
+ // - the 4-byte CRC-32 that follows the basic header
+ final byte[] bytes = new byte[4 + size + 10];
+ bytes[0] = (byte) 0x60; // ARJ signature
+ bytes[1] = (byte) 0xEA;
+ // Basic header size (little-endian)
+ EndianUtils.writeSwappedShort(bytes, 2, (short) (size + 2));
+ // First header size
+ bytes[4] = (byte) size;
+ // Compute valid CRC-32 for the basic header
+ if (computeCrc) {
+ final CRC32 crc32 = new CRC32();
+ crc32.update(bytes, 4, size + 2);
+ EndianUtils.writeSwappedInteger(bytes, 4 + size + 2, (int) crc32.getValue());
+ }
+ return bytes;
+ }
+
+ static Stream testSelfExtractingArchive() {
+ return Stream.of(
+ new byte[] { 0x10, 0x11, 0x12, 0x13, 0x14 },
+ // In a normal context: an archive trailer.
+ new byte[] { 0x60, (byte) 0xEA, 0x00, 0x00 },
+ // Header of valid size, but with an invalid CRC-32.
+ createArjArchiveHeader(30, false)
+ );
+ }
+
private void assertArjArchiveEntry(final ArjArchiveEntry entry) {
assertNotNull(entry.getName());
assertNotNull(entry.getLastModifiedDate());
@@ -81,19 +124,6 @@ private void assertForEach(final ArjArchiveInputStream archive) throws IOExcepti
});
}
- @Test
- void testFirstHeaderSizeSetToZero() {
- final ArchiveException ex = assertThrows(ArchiveException.class, () -> {
- try (ArjArchiveInputStream archive = ArjArchiveInputStream.builder()
- .setURI(getURI("org/apache/commons/compress/arj/zero_sized_headers-fail.arj"))
- .get()) {
- // Do nothing, ArchiveException already thrown
- fail("ArchiveException not thrown.");
- }
- });
- assertTrue(ex.getCause() instanceof IOException);
- }
-
@Test
void testForEach() throws Exception {
final StringBuilder expected = new StringBuilder();
@@ -116,6 +146,17 @@ void testForEach() throws Exception {
assertEquals(expected.toString(), result.toString());
}
+ @ParameterizedTest
+ @ValueSource(strings = { "bla.arj", "bla.unix.arj" })
+ void testGetBytesRead(final String resource) throws IOException {
+ final Path path = getPath(resource);
+ try (ArjArchiveInputStream in = ArjArchiveInputStream.builder().setPath(path).get()) {
+ consumeEntries(in);
+ final long expected = Files.size(path);
+ assertEquals(expected, in.getBytesRead(), "getBytesRead() did not return the expected value");
+ }
+ }
+
@Test
void testGetNextEntry() throws Exception {
final StringBuilder expected = new StringBuilder();
@@ -258,6 +299,25 @@ void testReadingOfAttributesUnixVersion() throws Exception {
}
}
+ @ParameterizedTest
+ @MethodSource
+ void testSelfExtractingArchive(final byte[] junk) throws Exception {
+ final Path validArj = getPath("bla.arj");
+ try (InputStream first = new ByteArrayInputStream(junk);
+ InputStream second = Files.newInputStream(validArj);
+ SequenceInputStream seq = new SequenceInputStream(first, second);
+ ArjArchiveInputStream in = ArjArchiveInputStream.builder().setInputStream(seq).setSelfExtracting(true).get()) {
+ ArjArchiveEntry entry = in.getNextEntry();
+ assertNotNull(entry);
+ assertEquals("test1.xml", entry.getName());
+ entry = in.getNextEntry();
+ assertNotNull(entry);
+ assertEquals("test2.xml", entry.getName());
+ entry = in.getNextEntry();
+ assertNull(entry);
+ }
+ }
+
@Test
void testSingleArgumentConstructor() throws Exception {
try (InputStream inputStream = Files.newInputStream(getPath("bla.arj"));
@@ -276,4 +336,99 @@ void testSingleByteReadConsistentlyReturnsMinusOneAtEof() throws Exception {
assertForEach(archive);
}
}
+
+ @CartesianTest
+ void testSmallFirstHeaderSize(
+ // 30 is the minimum valid size
+ @CartesianTest.Values(ints = {0, 1, 10, 29}) final int size, @CartesianTest.Values(booleans = {false, true}) final boolean selfExtracting) {
+ final byte[] bytes = createArjArchiveHeader(size, true);
+ assertThrows(ArchiveException.class, () -> ArjArchiveInputStream.builder().setByteArray(bytes).setSelfExtracting(selfExtracting).get());
+ }
+
+ /**
+ * Verifies that reading an ARJ header record cut short at various boundaries
+ * results in an {@link EOFException}.
+ *
+ * The test archive is crafted so that the local file header of the first entry begins at
+ * byte offset {@code 0x0035}. Within that header:
+ *
+ * - Basic header size (2 bytes at offsets 0x02–0x03) = {@code 0x0039}.
+ * - Fixed header size (aka {@code first_hdr_size}, 1 byte at 0x04) = {@code 0x2E}.
+ * - The filename and comment C-strings follow the fixed header and complete the basic header.
+ * - A 4-byte basic header CRC-32 follows the basic header.
+ *
+ *
+ * @param maxCount absolute truncation point (number of readable bytes from the start of the file)
+ */
+ @ParameterizedTest
+ @ValueSource(longs = {
+ // Before the local file header signature
+ 0x35,
+ // Immediately after the 2-byte signature
+ 0x35 + 0x02,
+ // Inside / after the basic-header size (2 bytes at 0x02–0x03)
+ 0x35 + 0x03, 0x35 + 0x04,
+ // Just after the fixed-header size (1 byte at 0x04)
+ 0x35 + 0x05,
+ // End of fixed header (0x04 + first_hdr_size == 0x32)
+ 0x35 + 0x32,
+ // End of basic header after filename/comment (0x04 + basic_hdr_size == 0x3d)
+ 0x35 + 0x3d,
+ // Inside / after the basic-header CRC-32 (4 bytes)
+ 0x35 + 0x3e, 0x35 + 0x41,
+ // Inside / after the extended-header length (2 bytes)
+ 0x35 + 0x42, 0x35 + 0x43,
+ // One byte before the first file’s data
+ 0x95
+ })
+ void testTruncatedLocalHeader(final long maxCount) throws Exception {
+ try (InputStream input = BoundedInputStream.builder().setURI(getURI("bla.arj")).setMaxCount(maxCount).get();
+ ArjArchiveInputStream archive = ArjArchiveInputStream.builder().setInputStream(input).get()) {
+ assertThrows(EOFException.class, () -> {
+ archive.getNextEntry();
+ IOUtils.skip(archive, Long.MAX_VALUE);
+ });
+ }
+ }
+
+ /**
+ * Verifies that reading an ARJ header record cut short at various boundaries
+ * results in an {@link EOFException}.
+ *
+ * The main archive header is at the beginning of the file. Within that header:
+ *
+ * - Basic header size (2 bytes at offsets 0x02–0x03) = {@code 0x002b}.
+ * - Fixed header size (aka {@code first_hdr_size}, 1 byte at 0x04) = {@code 0x22}.
+ * - The archive name and comment C-strings follow the fixed header and complete the basic header.
+ * - A 4-byte basic header CRC-32 follows the basic header.
+ *
+ *
+ * @param maxCount absolute truncation point (number of readable bytes from the start of the file)
+ */
+ @ParameterizedTest
+ @ValueSource(longs = {
+ // Empty file.
+ 0,
+ // Immediately after the 2-byte signature
+ 0x02,
+ // Inside / after the basic-header size (2 bytes at 0x02–0x03)
+ 0x03, 0x04,
+ // Just after the fixed-header size (1 byte at 0x04)
+ 0x05,
+ // End of fixed header (0x04 + first_hdr_size == 0x26)
+ 0x26,
+ // End of basic header after filename/comment (0x04 + basic_hdr_size == 0x2f)
+ 0x2f,
+ // Inside / after the basic-header CRC-32 (4 bytes)
+ 0x30, 0x33,
+ // Inside the extended-header length (2 bytes)
+ 0x34})
+ void testTruncatedMainHeader(final long maxCount) throws Exception {
+ try (InputStream input = BoundedInputStream.builder()
+ .setURI(getURI("bla.arj"))
+ .setMaxCount(maxCount)
+ .get()) {
+ assertThrows(EOFException.class, () -> ArjArchiveInputStream.builder().setInputStream(input).get());
+ }
+ }
}
diff --git a/src/test/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStreamTest.java b/src/test/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStreamTest.java
index 75cacb00f2b..166888ce27b 100644
--- a/src/test/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStreamTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStreamTest.java
@@ -29,23 +29,13 @@
import java.nio.charset.StandardCharsets;
import org.apache.commons.compress.AbstractTest;
-import org.apache.commons.compress.MemoryLimitException;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.Issue;
class CpioArchiveInputStreamTest extends AbstractTest {
- private long consumeEntries(final CpioArchiveInputStream in) throws IOException {
- long count = 0;
- CpioArchiveEntry entry;
- while ((entry = in.getNextEntry()) != null) {
- count++;
- assertNotNull(entry);
- }
- return count;
- }
-
@Test
void testCpioUnarchive() throws Exception {
final StringBuilder expected = new StringBuilder();
@@ -91,29 +81,15 @@ void testCpioUnarchiveMultibyteCharName() throws Exception {
}
@Test
- void testEndOfFileInEntry_c_namesize_0x7FFFFFFF() throws Exception {
- // CPIO header with c_namesize = 0x7FFFFFFF
- // @formatter:off
- final String header =
- "070701" + // c_magic
- "00000000" + // c_ino
- "000081A4" + // c_mode
- "00000000" + // c_uid
- "00000000" + // c_gid
- "00000001" + // c_nlink
- "00000000" + // c_mtime
- "00000000" + // c_filesize
- "00000000" + // c_devmajor
- "00000000" + // c_devminor
- "00000000" + // c_rdevmajor
- "00000000" + // c_rdevminor
- "7FFFFFFF" + // c_namesize
- "00000000"; // c_check
- // @formatter:on
- final byte[] data = new byte[header.getBytes(StandardCharsets.US_ASCII).length + 1];
- System.arraycopy(header.getBytes(), 0, data, 0, header.getBytes().length);
- try (CpioArchiveInputStream cpio = CpioArchiveInputStream.builder().setByteArray(data).get()) {
- assertThrows(MemoryLimitException.class, () -> cpio.getNextEntry());
+ @Issue("https://issues.apache.org/jira/browse/COMPRESS-711")
+ void testCrcVerification() throws Exception {
+ try (CpioArchiveInputStream archive = CpioArchiveInputStream.builder().setURI(getURI("bla.cpio")).get()) {
+ assertNotNull(archive.getNextEntry());
+ final byte[] buffer = new byte[1024];
+ // Read with an offset to test that the right bytes are checksummed
+ while (archive.read(buffer, 1, 1023) != -1) {
+ // noop
+ }
}
}
diff --git a/src/test/java/org/apache/commons/compress/archivers/dump/DumpArchiveInputStreamTest.java b/src/test/java/org/apache/commons/compress/archivers/dump/DumpArchiveInputStreamTest.java
index 01e79807512..98e575dfe8a 100644
--- a/src/test/java/org/apache/commons/compress/archivers/dump/DumpArchiveInputStreamTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/dump/DumpArchiveInputStreamTest.java
@@ -24,7 +24,6 @@
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
@@ -68,7 +67,6 @@ void checkUnsupportedRecordSizes(final int ntrec) throws Exception {
final ArchiveException ex = assertThrows(ArchiveException.class, () -> DumpArchiveInputStream.builder()
.setByteArray(createArchive(ntrec))
.get());
- assertInstanceOf(ArchiveException.class, ex.getCause());
assertTrue(ex.getMessage().contains(Integer.toString(ntrec)), "message should contain the invalid ntrec value");
}
@@ -122,11 +120,14 @@ void testGetNextEntry() throws IOException {
@Test
void testInvalidCompressType() {
- final ArchiveException ex = assertThrows(ArchiveException.class, () -> DumpArchiveInputStream.builder()
+ assertThrows(UnsupportedCompressionAlgorithmException.class, () -> DumpArchiveInputStream
+ // @formatter:off
+ .builder()
.setURI(getURI("org/apache/commons/compress/dump/invalid_compression_type-fail.dump"))
.get()
- .close());
- assertInstanceOf(UnsupportedCompressionAlgorithmException.class, ex.getCause());
+ .close()
+ // @formatter:on
+ );
}
@Test
@@ -176,26 +177,13 @@ void testMultiByteReadConsistentlyReturnsMinusOneAtEof() throws Exception {
@Test
void testNotADumpArchive() {
- final ArchiveException ex = assertThrows(
- ArchiveException.class,
- () -> DumpArchiveInputStream.builder()
- .setURI(getURI("bla.zip"))
- .get()
- .close(),
- "expected an exception");
- assertTrue(ex.getCause() instanceof ShortFileException);
+ assertThrows(ShortFileException.class, () -> DumpArchiveInputStream.builder().setURI(getURI("bla.zip")).get().close(), "expected an exception");
}
@Test
- void testNotADumpArchiveButBigEnough() throws Exception {
- final ArchiveException ex = assertThrows(
- ArchiveException.class,
- () -> DumpArchiveInputStream.builder()
- .setURI(getURI("zip64support.tar.bz2"))
- .get()
- .close(),
+ void testNotADumpArchiveButBigEnough() {
+ assertThrows(UnrecognizedFormatException.class, () -> DumpArchiveInputStream.builder().setURI(getURI("zip64support.tar.bz2")).get().close(),
"expected an exception");
- assertInstanceOf(UnrecognizedFormatException.class, ex.getCause());
}
@Test
diff --git a/src/test/java/org/apache/commons/compress/archivers/examples/ExpanderTest.java b/src/test/java/org/apache/commons/compress/archivers/examples/ExpanderTest.java
index a7ccedc665e..9c6eddbb2b6 100644
--- a/src/test/java/org/apache/commons/compress/archivers/examples/ExpanderTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/examples/ExpanderTest.java
@@ -174,7 +174,7 @@ private void setupZip(final String entry) throws IOException, ArchiveException {
@Test
void testCompress603Tar() throws IOException, ArchiveException {
setupTarForCompress603();
- try (TarFile f = new TarFile(archive)) {
+ try (TarFile f = TarFile.builder().setFile(archive).get()) {
new Expander().expand(f, tempResultDir);
}
verifyTargetDir();
@@ -261,7 +261,7 @@ void testSevenZTwoFileVersionWithAutoDetection() throws IOException, ArchiveExce
@Test
void testTarFileVersion() throws IOException, ArchiveException {
setupTar();
- try (TarFile f = new TarFile(archive)) {
+ try (TarFile f = TarFile.builder().setFile(archive).get()) {
new Expander().expand(f, tempResultDir);
}
verifyTargetDir();
diff --git a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFileTest.java b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFileTest.java
index 446a2ba6436..15349a29c49 100644
--- a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFileTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFileTest.java
@@ -33,6 +33,8 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
@@ -47,13 +49,15 @@
import java.util.List;
import java.util.Map;
import java.util.Random;
+import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.stream.Stream;
import javax.crypto.Cipher;
-import org.apache.commons.compress.AbstractTest;
import org.apache.commons.compress.MemoryLimitException;
import org.apache.commons.compress.PasswordRequiredException;
+import org.apache.commons.compress.archivers.AbstractArchiveFileTest;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.utils.MultiReadOnlySeekableByteChannel;
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
@@ -61,14 +65,182 @@
import org.apache.commons.io.input.ChecksumInputStream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
-class SevenZFileTest extends AbstractTest {
+class SevenZFileTest extends AbstractArchiveFileTest {
private static final String TEST2_CONTENT = "\r\n\r\n\r\n\t\r\n\n";
private static boolean isStrongCryptoAvailable() throws NoSuchAlgorithmException {
return Cipher.getMaxAllowedKeyLength("AES/ECB/PKCS5Padding") >= 256;
}
+ static Stream> testReadFolder_Unsupported() {
+ return Stream.of(
+ // Folder with no coders
+ buf -> writeFolder(buf, new Coder[0]),
+ // Folder with too many coders
+ buf -> {
+ final Coder[] coders = new Coder[65];
+ final Coder simpleCoder = new Coder(new byte[] { 0x03 }, 1, 1, null);
+ Arrays.fill(coders, simpleCoder);
+ writeFolder(buf, coders);
+ },
+ // Folder with too many input streams per coder
+ buf -> {
+ final Coder coder = new Coder(new byte[] { 0x03 }, 65, 1, null);
+ writeFolder(buf, new Coder[] { coder });
+ },
+ // Folder with more than one output stream per coder
+ buf -> {
+ final Coder coder = new Coder(new byte[] { 0x03 }, 1, 2, null);
+ writeFolder(buf, new Coder[] { coder });
+ },
+ // Folder with too many total input streams
+ buf -> {
+ final Coder coder = new Coder(new byte[] { 0x03 }, 2, 1, null);
+ final Coder[] coders = new Coder[33];
+ Arrays.fill(coders, coder);
+ writeFolder(buf, coders);
+ },
+ // Folder with more alternative methods (not supported yet)
+ buf -> writeFolder(buf, new Coder[]{new Coder(new byte[]{0x03}, 1, 1, null)},
+ true, false, false, false),
+ // Folder with unsupported bind pair in index
+ buf -> {
+ final Coder coder = new Coder(new byte[] { 0x03 }, 1, 1, null);
+ writeFolder(buf, new Coder[] { coder, coder }, false, true, false, false);
+ },
+ // Folder with unsupported bind pair out index
+ buf -> {
+ final Coder coder = new Coder(new byte[] { 0x03 }, 1, 1, null);
+ writeFolder(buf, new Coder[] { coder, coder }, false, false, true, false);
+ },
+ // Folder with unsupported packed stream index
+ buf -> {
+ final Coder coder = new Coder(new byte[]{0x03}, 2, 1, null);
+ writeFolder(buf, new Coder[]{ coder, coder }, false, false, false, true);
+ }
+ );
+ }
+
+ static Stream testReadRealUint64_Invalid() {
+ final byte m = (byte) 0xff;
+ return Stream.of(
+ new byte[] { (byte) 0b11111111, 0, 0, 0, 0, 0, 0, (byte) 0x80 },
+ new byte[] { (byte) 0b11111111, m, m, m, m, m, m, m }
+ );
+ }
+
+ static Stream testReadRealUint64_Valid() {
+ final byte m = (byte) 0xff;
+ return Stream.of(
+ Arguments.of(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }, 0x0706_0504_0302_0100L),
+ Arguments.of(new byte[] { m, m, m, m, m, m, m, Byte.MAX_VALUE }, 0x7FFF_FFFF_FFFF_FFFFL)
+ );
+ }
+
+ static Stream testReadUint32_Valid() {
+ final byte m = (byte) 0xff;
+ return Stream.of(
+ Arguments.of(new byte[] { 0, 1, 2, 3 }, 0x0302_0100L),
+ Arguments.of(new byte[] { m, m, m, Byte.MAX_VALUE }, 0x7FFF_FFFFL),
+ Arguments.of(new byte[] { m, m, m, m }, 0xFFFF_FFFFL)
+ );
+ }
+
+ static Stream testReadUint64_Overflow() {
+ final byte m = (byte) 0xff;
+ return Stream.of(
+ new byte[] { (byte) 0b11111111, 0, 0, 0, 0, 0, 0, 0, (byte) 0x80 },
+ new byte[] { (byte) 0b11111111, m, m, m, m, m, m, m, m }
+ );
+ }
+
+ static Stream testReadUint64_Valid() {
+ final byte m = (byte) 0xff;
+ return Stream.of(
+ Arguments.of(new byte[] { 0 }, 0L),
+ Arguments.of(new byte[] { Byte.MAX_VALUE }, 0x7FL),
+ Arguments.of(new byte[] { (byte) 0b10_000001, 2 }, 0x0102L),
+ Arguments.of(new byte[] { (byte) 0b10_111111, m }, 0x3FFFL),
+ Arguments.of(new byte[] { (byte) 0b110_00001, 3, 2 }, 0x01_0203L),
+ Arguments.of(new byte[] { (byte) 0b110_11111, m, m }, 0x1F_FFFFL),
+ Arguments.of(new byte[] { (byte) 0b1110_0001, 4, 3, 2 }, 0x0102_0304L),
+ Arguments.of(new byte[] { (byte) 0b1110_1111, m, m, m }, 0x0FFF_FFFFL),
+ Arguments.of(new byte[] { (byte) 0b11110_001, 5, 4, 3, 2 }, 0x01_0203_0405L),
+ Arguments.of(new byte[] { (byte) 0b11110_111, m, m, m, m }, 0x07_FFFF_FFFFL),
+ Arguments.of(new byte[] { (byte) 0b111110_01, 6, 5, 4, 3, 2 }, 0x0102_0304_0506L),
+ Arguments.of(new byte[] { (byte) 0b111110_11, m, m, m, m, m }, 0x03FF_FFFF_FFFFL),
+ Arguments.of(new byte[] { (byte) 0b1111110_1, 7, 6, 5, 4, 3, 2 }, 0x01_0203_0405_0607L),
+ Arguments.of(new byte[] { (byte) 0b1111110_1, m, m, m, m, m, m }, 0x01_FFFF_FFFF_FFFFL),
+ Arguments.of(new byte[] { (byte) 0b11111110, 7, 6, 5, 4, 3, 2, 1 }, 0x01_0203_0405_0607L),
+ Arguments.of(new byte[] { (byte) 0b11111110, m, m, m, m, m, m, m }, 0xFF_FFFF_FFFF_FFFFL),
+ Arguments.of(new byte[] { (byte) 0b11111111, 8, 7, 6, 5, 4, 3, 2, 1 }, 0x0102_0304_0506_0708L),
+ Arguments.of(new byte[] { (byte) 0b11111111, m, m, m, m, m, m, m, Byte.MAX_VALUE }, 0x7FFF_FFFF_FFFF_FFFFL)
+ );
+ }
+
+ private static void writeBindPair(final ByteBuffer buffer, final long inIndex, final long outIndex) {
+ writeUint64(buffer, inIndex);
+ writeUint64(buffer, outIndex);
+ }
+
+ private static void writeCoder(final ByteBuffer buffer, final byte[] methodId, final long numInStreams, final long numOutStreams,
+ final boolean moreAlternativeMethods) {
+ final boolean isComplex = numInStreams != 1 || numOutStreams != 1;
+ int flag = methodId.length;
+ if (isComplex) {
+ flag |= 0x10;
+ }
+ if (moreAlternativeMethods) {
+ flag |= 0x80;
+ }
+ // coder
+ buffer.put((byte) flag);
+ buffer.put(methodId);
+ if (isComplex) {
+ writeUint64(buffer, numInStreams);
+ writeUint64(buffer, numOutStreams);
+ }
+ }
+
+ private static void writeFolder(final ByteBuffer buffer, final Coder[] coders) {
+ writeFolder(buffer, coders, false, false, false, false);
+ }
+
+ private static void writeFolder(final ByteBuffer buffer, final Coder[] coders, final boolean moreAlternativeMethods, final boolean unsupportedBindPairIn,
+ final boolean unsupportedBindPairOut, final boolean unsupportedPackedStreams) {
+ writeUint64(buffer, coders.length);
+ long totalInStreams = 0;
+ long totalOutStreams = 0;
+ for (final Coder coder : coders) {
+ writeCoder(buffer, coder.decompressionMethodId, coder.numInStreams, coder.numOutStreams, moreAlternativeMethods);
+ totalInStreams += coder.numInStreams;
+ totalOutStreams += coder.numOutStreams;
+ }
+ long i = 0;
+ // Bind pairs: one less than number of total out streams
+ for (; i < totalOutStreams - 1; i++) {
+ final long inIndex = (unsupportedBindPairIn ? totalInStreams : 0) + i;
+ final long outIndex = (unsupportedBindPairOut ? totalOutStreams : 0) + i + 1;
+ writeBindPair(buffer, inIndex, outIndex);
+ }
+ // Packed streams: one per in stream that is not bound
+ if (totalInStreams > i + 1) {
+ for (; i < totalInStreams; i++) {
+ final long packedStreamIndex = (unsupportedPackedStreams ? totalInStreams : 0) + i;
+ writeUint64(buffer, packedStreamIndex);
+ }
+ }
+ }
+
+ private static void writeUint64(final ByteBuffer buffer, final long value) {
+ buffer.put((byte) 0b1111_1111);
+ buffer.putLong(value);
+ }
+
private void assertDate(final SevenZArchiveEntry entry, final String value, final Function hasValue,
final Function timeFunction, final Function dateFunction) {
if (value != null) {
@@ -113,6 +285,11 @@ private void checkHelloWorld(final String fileName) throws Exception {
}
}
+ @Override
+ protected SevenZFile getArchiveFile() throws IOException {
+ return SevenZFile.builder().setPath(getPath("bla.7z")).get();
+ }
+
private SevenZFile getSevenZFile(final String specialPath) throws IOException {
return SevenZFile.builder().setFile(getFile(specialPath)).get();
}
@@ -160,10 +337,6 @@ void test7zDeflateUnarchive() throws Exception {
@Test
void test7zMultiVolumeUnarchive() throws Exception {
- try (@SuppressWarnings("deprecation")
- SevenZFile sevenZFile = new SevenZFile(MultiReadOnlySeekableByteChannel.forFiles(getFile("bla-multi.7z.001"), getFile("bla-multi.7z.002")))) {
- test7zUnarchive(sevenZFile, SevenZMethod.LZMA2);
- }
try (SevenZFile sevenZFile = SevenZFile.builder()
.setSeekableByteChannel(MultiReadOnlySeekableByteChannel.forFiles(getFile("bla-multi.7z.001"), getFile("bla-multi.7z.002"))).get()) {
test7zUnarchive(sevenZFile, SevenZMethod.LZMA2);
@@ -184,10 +357,6 @@ private void test7zUnarchive(final File file, final SevenZMethod method, final b
}
private void test7zUnarchive(final File file, final SevenZMethod method, final byte[] password) throws Exception {
- try (@SuppressWarnings("deprecation")
- SevenZFile sevenZFile = new SevenZFile(file, password)) {
- test7zUnarchive(sevenZFile, method);
- }
try (SevenZFile sevenZFile = SevenZFile.builder().setFile(file).setPassword(password).get()) {
test7zUnarchive(sevenZFile, method);
}
@@ -198,11 +367,6 @@ private void test7zUnarchive(final File file, final SevenZMethod m, final char[]
}
private void test7zUnarchive(final File file, final SevenZMethod m, final char[] password, final boolean tryToRecoverBrokenArchives) throws Exception {
- try (@SuppressWarnings("deprecation")
- SevenZFile sevenZFile = new SevenZFile(file, password,
- SevenZFileOptions.builder().withTryToRecoverBrokenArchives(tryToRecoverBrokenArchives).build())) {
- test7zUnarchive(sevenZFile, m);
- }
try (SevenZFile sevenZFile = SevenZFile.builder().setFile(file).setPassword(password).setTryToRecoverBrokenArchives(tryToRecoverBrokenArchives).get()) {
test7zUnarchive(sevenZFile, m);
}
@@ -324,8 +488,7 @@ void testExtractSpecifiedFile() throws Exception {
@Test
void testExtractSpecifiedFileDeprecated() throws Exception {
- try (@SuppressWarnings("deprecation")
- SevenZFile sevenZFile = new SevenZFile(getFile("COMPRESS-256.7z"))) {
+ try (SevenZFile sevenZFile = SevenZFile.builder().setURI(getURI("COMPRESS-256.7z")).get()) {
// @formatter:off
final String testTxtContents =
"111111111111111111111111111000101011\n"
@@ -364,10 +527,6 @@ void testGetDefaultName() throws Exception {
try (SevenZFile sevenZFile = SevenZFile.builder().setSeekableByteChannel(Files.newByteChannel(getFile("bla.deflate64.7z").toPath())).get()) {
assertNull(sevenZFile.getDefaultName());
}
- try (@SuppressWarnings("deprecation")
- SevenZFile sevenZFile = new SevenZFile(Files.newByteChannel(getFile("bla.deflate64.7z").toPath()), "foo")) {
- assertEquals("foo~", sevenZFile.getDefaultName());
- }
try (SevenZFile sevenZFile = SevenZFile.builder().setSeekableByteChannel(Files.newByteChannel(getFile("bla.deflate64.7z").toPath()))
.setDefaultName("foo").get()) {
assertEquals("foo~", sevenZFile.getDefaultName());
@@ -407,16 +566,6 @@ void testGetEntriesOfUnarchiveTest() throws IOException {
@Test
void testGivenNameWinsOverDefaultName() throws Exception {
- try (@SuppressWarnings("deprecation")
- SevenZFile sevenZFile = new SevenZFile(getFile("bla.7z"), SevenZFileOptions.builder().withUseDefaultNameForUnnamedEntries(true).build())) {
- SevenZArchiveEntry ae = sevenZFile.getNextEntry();
- assertNotNull(ae);
- assertEquals("test1.xml", ae.getName());
- ae = sevenZFile.getNextEntry();
- assertNotNull(ae);
- assertEquals("test2.xml", ae.getName());
- assertNull(sevenZFile.getNextEntry());
- }
try (SevenZFile sevenZFile = SevenZFile.builder().setFile(getFile("bla.7z")).setUseDefaultNameForUnnamedEntries(true).get()) {
SevenZArchiveEntry ae = sevenZFile.getNextEntry();
assertNotNull(ae);
@@ -512,25 +661,12 @@ void testNoOOMOnCorruptedHeader() throws IOException {
testFiles.add(getPath("COMPRESS-542-endheadercorrupted.7z"));
testFiles.add(getPath("COMPRESS-542-endheadercorrupted2.7z"));
for (final Path file : testFiles) {
- {
- final IOException e = assertThrows(ArchiveException.class, () -> {
- try (@SuppressWarnings("deprecation")
- SevenZFile sevenZFile = new SevenZFile(Files.newByteChannel(file),
- SevenZFileOptions.builder().withTryToRecoverBrokenArchives(true).build())) {
- // do nothing
- }
- }, "Expected IOException: start header corrupt and unable to guess end header");
- assertEquals("Start header corrupt and unable to guess end header", e.getMessage());
- }
- {
- final IOException e = assertThrows(ArchiveException.class, () -> {
- try (SevenZFile sevenZFile = SevenZFile.builder().setSeekableByteChannel(Files.newByteChannel(file)).setTryToRecoverBrokenArchives(true)
- .get()) {
- // do nothing
- }
- }, "Expected IOException: start header corrupt and unable to guess end header");
- assertEquals("Start header corrupt and unable to guess end header", e.getMessage());
- }
+ final IOException e = assertThrows(ArchiveException.class, () -> {
+ try (SevenZFile sevenZFile = SevenZFile.builder().setPath(file).setTryToRecoverBrokenArchives(true).get()) {
+ // do nothing
+ }
+ }, "Expected IOException: start header corrupt and unable to guess end header");
+ assertEquals("7z archive: Start header corrupt and unable to guess end header", e.getMessage());
}
}
@@ -554,8 +690,6 @@ void testNumCyclesPower30() throws IOException {
.setPassword(password)
.setTryToRecoverBrokenArchives(true)
.get().close());
- assertThrows(ArchiveException.class,
- () -> new SevenZFile(new File(fixture), password).close());
// @formatter:on
}
@@ -876,6 +1010,21 @@ void testReadEntriesOfSize0() throws IOException {
}
}
+ @ParameterizedTest
+ @MethodSource
+ void testReadFolder_Unsupported(final Consumer folderWriter) throws IOException {
+ try (SevenZFile file = SevenZFile.builder().setURI(getURI("bla.7z")).get()) {
+ // Allocate a buffer large enough to hold the folder data
+ final ByteBuffer buffer = ByteBuffer.allocate(8192).order(ByteOrder.LITTLE_ENDIAN);
+ folderWriter.accept(buffer);
+ buffer.flip();
+ final ArchiveException e = assertThrows(ArchiveException.class, () -> {
+ file.readFolder(buffer);
+ });
+ assertTrue(e.getMessage().contains("7z archive: Unsupported"));
+ }
+ }
+
/**
* Test case for COMPRESS-681.
*/
@@ -937,6 +1086,21 @@ void testReadingBackLZMA2DictSize() throws Exception {
}
}
+ @ParameterizedTest
+ @MethodSource
+ void testReadRealUint64_Invalid(final byte[] input) throws IOException {
+ final ByteBuffer buf = ByteBuffer.wrap(input).order(ByteOrder.LITTLE_ENDIAN);
+ assertThrows(IOException.class, () -> SevenZFile.readRealUint64(buf));
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testReadRealUint64_Valid(final byte[] input, final long expected) throws IOException {
+ final ByteBuffer buf = ByteBuffer.wrap(input).order(ByteOrder.LITTLE_ENDIAN);
+ final long actual = SevenZFile.readRealUint64(buf);
+ assertEquals(expected, actual);
+ }
+
@Test
void testReadTimesFromFile() throws IOException {
try (SevenZFile sevenZFile = getSevenZFile("times.7z")) {
@@ -963,6 +1127,29 @@ void testReadTimesFromFile() throws IOException {
}
}
+ @ParameterizedTest
+ @MethodSource
+ void testReadUint32_Valid(final byte[] input, final long expected) throws IOException {
+ final ByteBuffer buf = ByteBuffer.wrap(input).order(ByteOrder.LITTLE_ENDIAN);
+ final long actual = SevenZFile.readUint32(buf);
+ assertEquals(expected, actual);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testReadUint64_Overflow(final byte[] bytes) {
+ final ByteBuffer buf = ByteBuffer.wrap(bytes);
+ final ArchiveException ex = assertThrows(ArchiveException.class, () -> SevenZFile.readUint64(buf));
+ assertTrue(ex.getMessage().contains("7z archive: Unsupported"), ex.getMessage());
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testReadUint64_Valid(final byte[] bytes, final long expected) throws IOException {
+ final ByteBuffer buf = ByteBuffer.wrap(bytes);
+ assertEquals(expected, SevenZFile.readUint64(buf));
+ }
+
@Test
void testRemainingBytesUnchangedAfterRead() throws Exception {
try (SevenZFile sevenZFile = getSevenZFile("COMPRESS-256.7z")) {
diff --git a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFolderTest.java b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFolderTest.java
index 72139550518..ed2755386b3 100644
--- a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFolderTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFolderTest.java
@@ -53,12 +53,12 @@ void testGetUnpackSizeForCoderOne() {
@Test
void testGetUnpackSizeOne() throws ArchiveException {
final Folder folder = new Folder();
- folder.totalOutputStreams = 266L;
+ folder.totalOutputStreams = 266;
final BindPair[] bindPairArray = new BindPair[1];
final BindPair bindPair = new BindPair(0, 0);
bindPairArray[0] = bindPair;
folder.bindPairs = bindPairArray;
- folder.totalOutputStreams = 1L;
+ folder.totalOutputStreams = 1;
assertEquals(0L, folder.getUnpackSize());
}
diff --git a/src/test/java/org/apache/commons/compress/archivers/tar/BigFilesIT.java b/src/test/java/org/apache/commons/compress/archivers/tar/BigFilesIT.java
index a941dbe0e6e..eab504cfe88 100644
--- a/src/test/java/org/apache/commons/compress/archivers/tar/BigFilesIT.java
+++ b/src/test/java/org/apache/commons/compress/archivers/tar/BigFilesIT.java
@@ -94,7 +94,7 @@ void testTarFileReadFileHeadersOfArchiveBiggerThan8GByte() throws Exception {
GzipCompressorInputStream gzin = new GzipCompressorInputStream(in)) {
Files.copy(gzin, output, StandardCopyOption.REPLACE_EXISTING);
}
- try (TarFile tarFile = new TarFile(output)) {
+ try (TarFile tarFile = TarFile.builder().setPath(output).get()) {
final List entries = tarFile.getEntries();
assertEquals(1, entries.size());
assertNotNull(entries.get(0));
diff --git a/src/test/java/org/apache/commons/compress/archivers/tar/SparseFilesTest.java b/src/test/java/org/apache/commons/compress/archivers/tar/SparseFilesTest.java
index 4d09fddcaa3..ce39e07a89f 100644
--- a/src/test/java/org/apache/commons/compress/archivers/tar/SparseFilesTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/tar/SparseFilesTest.java
@@ -23,6 +23,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
@@ -35,14 +36,27 @@
import java.util.List;
import org.apache.commons.compress.AbstractTest;
+import org.apache.commons.compress.archivers.TestArchiveGenerator;
import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
class SparseFilesTest extends AbstractTest {
+ @TempDir
+ private static Path tempDir;
+
+ @BeforeAll
+ static void setupAll() throws IOException {
+ TestArchiveGenerator.createSparseFileTestCases(tempDir);
+ }
+
private void assertPaxGNUEntry(final TarArchiveEntry entry, final String suffix) {
assertEquals("sparsefile-" + suffix, entry.getName());
assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
@@ -116,7 +130,7 @@ private String getTarBinaryHelp() throws IOException {
void testCompareTarArchiveInputStreamWithTarFile() throws IOException {
final Path file = getPath("oldgnu_sparse.tar");
try (TarArchiveInputStream tarIn = TarArchiveInputStream.builder().setPath(file).get();
- TarFile tarFile = new TarFile(file)) {
+ TarFile tarFile = TarFile.builder().setPath(file).get()) {
assertNotNull(tarIn.getNextTarEntry());
try (InputStream inputStream = tarFile.getInputStream(tarFile.getEntries().get(0))) {
assertArrayEquals(IOUtils.toByteArray(tarIn), IOUtils.toByteArray(inputStream));
@@ -245,6 +259,53 @@ void testExtractSparseTarsOnWindows() throws IOException {
}
}
+ @ParameterizedTest
+ @ValueSource(strings = {"old-gnu-sparse.tar" , "gnu-sparse-00.tar", "gnu-sparse-01.tar", "gnu-sparse-1.tar"})
+ void testMaximallyFragmentedTarFile(final String fileName) throws IOException {
+ final int expectedSize = 8192;
+ try (TarFile input = TarFile.builder().setPath(tempDir.resolve(fileName)).get()) {
+ final List entries = input.getEntries();
+ assertEquals(1, entries.size());
+ final TarArchiveEntry entry = entries.get(0);
+ assertNotNull(entry);
+ assertEquals("sparse-file.txt", entry.getName());
+
+ try (InputStream inputStream = input.getInputStream(entry)) {
+ // read the expected amount of data
+ final byte[] content = new byte[expectedSize];
+ assertEquals(expectedSize, IOUtils.read(inputStream, content));
+ // verify that the stream is at EOF
+ assertEquals(IOUtils.EOF, inputStream.read());
+ // check content
+ for (int i = 0; i < content.length; i++) {
+ assertEquals((byte) (i % 256), content[i], "at index " + i);
+ }
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"old-gnu-sparse.tar", "gnu-sparse-00.tar", "gnu-sparse-01.tar", "gnu-sparse-1.tar"})
+ void testMaximallyFragmentedTarStream(final String fileName) throws IOException {
+ final int expectedSize = 8192;
+ try (TarArchiveInputStream input = TarArchiveInputStream.builder().setPath(tempDir.resolve(fileName)).get()) {
+ final TarArchiveEntry entry = input.getNextEntry();
+ assertNotNull(entry);
+ assertEquals("sparse-file.txt", entry.getName());
+ // read the expected amount of data
+ final byte[] content = new byte[expectedSize];
+ assertEquals(expectedSize, IOUtils.read(input, content));
+ // verify that the stream is at EOF
+ assertEquals(IOUtils.EOF, input.read());
+ // check content
+ for (int i = 0; i < content.length; i++) {
+ assertEquals((byte) (i % 256), content[i], "at index " + i);
+ }
+ // check that there are no more entries
+ assertNull(input.getNextEntry());
+ }
+ }
+
@Test
void testOldGNU() throws Throwable {
try (TarArchiveInputStream tin = TarArchiveInputStream.builder()
@@ -303,7 +364,7 @@ void testPaxGNU() throws Throwable {
void testTarFileExtractExtendedOldGNU() throws IOException, InterruptedException {
final File file = getFile("oldgnu_extended_sparse.tar");
try (InputStream sparseFileInputStream = extractTarAndGetInputStream(file, "sparse6");
- TarFile tarFile = new TarFile(file)) {
+ TarFile tarFile = TarFile.builder().setFile(file).get()) {
final TarArchiveEntry ae = tarFile.getEntries().get(0);
try (InputStream tarInput = tarFile.getInputStream(ae)) {
@@ -341,7 +402,7 @@ void testTarFileExtractExtendedOldGNU() throws IOException, InterruptedException
void testTarFileExtractOldGNU() throws IOException, InterruptedException {
final File file = getFile("oldgnu_sparse.tar");
try (InputStream sparseFileInputStream = extractTarAndGetInputStream(file, "sparsefile");
- TarFile tarFile = new TarFile(file)) {
+ TarFile tarFile = TarFile.builder().setFile(file).get()) {
final TarArchiveEntry entry = tarFile.getEntries().get(0);
try (InputStream tarInput = tarFile.getInputStream(entry)) {
assertArrayEquals(IOUtils.toByteArray(tarInput), IOUtils.toByteArray(sparseFileInputStream));
@@ -358,7 +419,7 @@ void testTarFileExtractPaxGNU() throws IOException, InterruptedException {
assumeFalse(getTarBinaryHelp().startsWith("tar (GNU tar) 1.28"), "This test should be ignored if GNU tar is version 1.28");
final File file = getFile("pax_gnu_sparse.tar");
- try (TarFile paxGnu = new TarFile(file)) {
+ try (TarFile paxGnu = TarFile.builder().setFile(file).get()) {
final List entries = paxGnu.getEntries();
TarArchiveEntry entry = entries.get(0);
@@ -386,12 +447,12 @@ void testTarFileExtractPaxGNU() throws IOException, InterruptedException {
void testTarFileExtractSparseTarsOnWindows() throws IOException {
final File oldGNUSparseTar = getFile("oldgnu_sparse.tar");
final File paxGNUSparseTar = getFile("pax_gnu_sparse.tar");
- try (TarFile paxGnu = new TarFile(paxGNUSparseTar)) {
+ try (TarFile paxGnu = TarFile.builder().setFile(paxGNUSparseTar).get()) {
final List entries = paxGnu.getEntries();
// compare between old GNU and PAX 0.0
TarArchiveEntry paxGnuEntry = entries.get(0);
- try (TarFile oldGnu = new TarFile(oldGNUSparseTar)) {
+ try (TarFile oldGnu = TarFile.builder().setFile(oldGNUSparseTar).get()) {
final TarArchiveEntry oldGnuEntry = oldGnu.getEntries().get(0);
try (InputStream old = oldGnu.getInputStream(oldGnuEntry);
InputStream pax = paxGnu.getInputStream(paxGnuEntry)) {
@@ -401,7 +462,7 @@ void testTarFileExtractSparseTarsOnWindows() throws IOException {
// compare between old GNU and PAX 0.1
paxGnuEntry = entries.get(1);
- try (TarFile oldGnu = new TarFile(oldGNUSparseTar)) {
+ try (TarFile oldGnu = TarFile.builder().setFile(oldGNUSparseTar).get()) {
final TarArchiveEntry oldGnuEntry = oldGnu.getEntries().get(0);
try (InputStream old = oldGnu.getInputStream(oldGnuEntry);
InputStream pax = paxGnu.getInputStream(paxGnuEntry)) {
@@ -411,7 +472,7 @@ void testTarFileExtractSparseTarsOnWindows() throws IOException {
// compare between old GNU and PAX 1.0
paxGnuEntry = entries.get(2);
- try (TarFile oldGnu = new TarFile(oldGNUSparseTar)) {
+ try (TarFile oldGnu = TarFile.builder().setFile(oldGNUSparseTar).get()) {
final TarArchiveEntry oldGnuEntry = oldGnu.getEntries().get(0);
try (InputStream old = oldGnu.getInputStream(oldGnuEntry);
InputStream pax = paxGnu.getInputStream(paxGnuEntry)) {
@@ -424,7 +485,7 @@ void testTarFileExtractSparseTarsOnWindows() throws IOException {
@Test
void testTarFileOldGNU() throws Throwable {
final File file = getFile("oldgnu_sparse.tar");
- try (TarFile tarFile = new TarFile(file)) {
+ try (TarFile tarFile = TarFile.builder().setFile(file).get()) {
final TarArchiveEntry ae = tarFile.getEntries().get(0);
assertEquals("sparsefile", ae.getName());
assertEquals(TarConstants.LF_GNUTYPE_SPARSE, ae.getLinkFlag());
@@ -464,7 +525,7 @@ void testTarFileOldGNU() throws Throwable {
@Test
void testTarFilePaxGNU() throws IOException {
final File file = getFile("pax_gnu_sparse.tar");
- try (TarFile tarFile = new TarFile(file)) {
+ try (TarFile tarFile = TarFile.builder().setFile(file).get()) {
final List entries = tarFile.getEntries();
assertPaxGNUEntry(entries.get(0), "0.0");
assertPaxGNUEntry(entries.get(1), "0.1");
diff --git a/src/test/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStreamTest.java b/src/test/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStreamTest.java
index c823b0757a6..c293a53f626 100644
--- a/src/test/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStreamTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStreamTest.java
@@ -36,6 +36,7 @@
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -166,7 +167,7 @@ private void testCompress666(final int factor, final boolean bufferInputStream,
final ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
final List>> tasks = IntStream.range(0, 200).mapToObj(index -> executorService.submit(() -> {
- TarArchiveEntry tarEntry = null;
+ final TarArchiveEntry tarEntry = null;
try (InputStream inputStream = getClass().getResourceAsStream(localPath);
// @formatter:off
TarArchiveInputStream tarInputStream = TarArchiveInputStream.builder()
@@ -175,9 +176,7 @@ private void testCompress666(final int factor, final boolean bufferInputStream,
.setRecordSize(TarConstants.DEFAULT_RCDSIZE)
.get()) {
// @formatter:on
- while ((tarEntry = tarInputStream.getNextEntry()) != null) {
- assertNotNull(tarEntry);
- }
+ consumeEntries(tarInputStream);
} catch (final IOException e) {
fail(Objects.toString(tarEntry), e);
}
@@ -405,7 +404,8 @@ void testParseTarWithNonNumberPaxHeaders() throws IOException {
void testParseTarWithSpecialPaxHeaders() throws IOException {
try (TarArchiveInputStream archive = getTestStream("COMPRESS-530-fail.tar")) {
assertThrows(ArchiveException.class, () -> archive.getNextEntry());
- assertThrows(ArchiveException.class, () -> IOUtils.toByteArray(archive));
+ // The PAX header is truncated
+ assertThrows(EOFException.class, () -> IOUtils.toByteArray(archive));
}
}
@@ -501,7 +501,7 @@ void testShouldReadGNULongNameEntryWithWrongName() throws Exception {
void testShouldThrowAnExceptionOnTruncatedEntries() throws Exception {
final Path dir = createTempDirectory("COMPRESS-279");
try (TarArchiveInputStream is = getTestStream("COMPRESS-279-fail.tar")) {
- assertThrows(ArchiveException.class, () -> {
+ assertThrows(EOFException.class, () -> {
TarArchiveEntry entry = is.getNextTarEntry();
int count = 0;
while (entry != null) {
@@ -518,7 +518,7 @@ void testShouldThrowAnExceptionOnTruncatedStream() throws Exception {
final Path dir = createTempDirectory("COMPRESS-279");
try (TarArchiveInputStream is = getTestStream("COMPRESS-279-fail.tar")) {
final AtomicInteger count = new AtomicInteger();
- assertThrows(ArchiveException.class, () -> is.forEach(entry -> Files.copy(is, dir.resolve(String.valueOf(count.getAndIncrement())))));
+ assertThrows(EOFException.class, () -> is.forEach(entry -> Files.copy(is, dir.resolve(String.valueOf(count.getAndIncrement())))));
}
}
diff --git a/src/test/java/org/apache/commons/compress/archivers/tar/TarFileTest.java b/src/test/java/org/apache/commons/compress/archivers/tar/TarFileTest.java
index 5b8b9a18f3a..ee1a677f75a 100644
--- a/src/test/java/org/apache/commons/compress/archivers/tar/TarFileTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/tar/TarFileTest.java
@@ -32,6 +32,7 @@
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -40,18 +41,17 @@
import java.util.List;
import java.util.zip.GZIPInputStream;
-import org.apache.commons.compress.AbstractTest;
+import org.apache.commons.compress.archivers.AbstractArchiveFileTest;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.TimeZones;
import org.junit.jupiter.api.Test;
-import shaded.org.apache.commons.lang3.StringUtils;
-
-class TarFileTest extends AbstractTest {
+class TarFileTest extends AbstractArchiveFileTest {
private void datePriorToEpoch(final String archive) throws Exception {
- try (TarFile tarFile = new TarFile(getPath(archive))) {
+ try (TarFile tarFile = TarFile.builder().setURI(getURI(archive)).get()) {
final TarArchiveEntry entry = tarFile.getEntries().get(0);
assertEquals("foo", entry.getName());
assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
@@ -63,13 +63,18 @@ private void datePriorToEpoch(final String archive) throws Exception {
}
}
+ @Override
+ protected TarFile getArchiveFile() throws IOException {
+ return TarFile.builder().setPath(getPath("bla.tar")).get();
+ }
+
/**
* This test ensures the implementation is reading the padded last block if a tool has added one to an archive
*/
@Test
void testArchiveWithTrailer() throws IOException {
try (SeekableByteChannel channel = Files.newByteChannel(getPath("archive_with_trailer.tar"));
- TarFile tarfile = new TarFile(channel, TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, null, false)) {
+ TarFile tarfile = TarFile.builder().setChannel(channel).get()) {
final String tarAppendix = "Hello, world!\n";
final ByteBuffer buffer = ByteBuffer.allocate(tarAppendix.length());
channel.read(buffer);
@@ -81,7 +86,7 @@ void testArchiveWithTrailer() throws IOException {
void testBuilderSeekableByteChannel() throws IOException {
try (SeekableByteChannel channel = Files.newByteChannel(getPath("archive_with_trailer.tar"));
TarFile tarfile = TarFile.builder()
- .setSeekableByteChannel(channel)
+ .setChannel(channel)
.setBlockSize(TarConstants.DEFAULT_BLKSIZE)
.setRecordSize(TarConstants.DEFAULT_RCDSIZE)
.setLenient(false)
@@ -95,7 +100,7 @@ void testBuilderSeekableByteChannel() throws IOException {
@Test
void testCompress197() throws IOException {
- try (TarFile tarFile = new TarFile(getPath("COMPRESS-197.tar"))) {
+ try (TarFile tarFile = TarFile.builder().setURI(getURI("COMPRESS-197.tar")).get()) {
// noop
}
}
@@ -122,7 +127,7 @@ void testCompress558() throws IOException {
tos.closeArchiveEntry();
}
final byte[] data = bos.toByteArray();
- try (TarFile tarFile = new TarFile(data)) {
+ try (TarFile tarFile = TarFile.builder().setByteArray(data).get()) {
final List entries = tarFile.getEntries();
assertEquals(folderName, entries.get(0).getName());
assertEquals(TarConstants.LF_DIR, entries.get(0).getLinkFlag());
@@ -135,7 +140,7 @@ void testCompress558() throws IOException {
@Test
void testCompress657() throws IOException {
- try (TarFile tarFile = new TarFile(getPath("COMPRESS-657/orjson-3.7.8.tar"))) {
+ try (TarFile tarFile = TarFile.builder().setURI(getURI("COMPRESS-657/orjson-3.7.8.tar")).get()) {
for (final TarArchiveEntry entry : tarFile.getEntries()) {
if (entry.isDirectory()) {
// An entry cannot be a directory and a "normal file" at the same time.
@@ -184,7 +189,7 @@ void testDirectoryWithLongNameEndsWithSlash() throws IOException {
out.flush();
}
// untar these tars
- try (TarFile tarFile = new TarFile(tarF)) {
+ try (TarFile tarFile = TarFile.builder().setFile(tarF).get()) {
for (final TarArchiveEntry entry : tarFile.getEntries()) {
assertTrue(entry.getName().endsWith("/"), "Entry name: " + entry.getName());
}
@@ -195,7 +200,7 @@ void testDirectoryWithLongNameEndsWithSlash() throws IOException {
@Test
void testMultiByteReadConsistentlyReturnsMinusOneAtEof() throws Exception {
final byte[] buf = new byte[2];
- try (TarFile tarFile = new TarFile(getPath("bla.tar"));
+ try (TarFile tarFile = TarFile.builder().setURI(getURI("bla.tar")).get();
InputStream input = tarFile.getInputStream(tarFile.getEntries().get(0))) {
IOUtils.toByteArray(input);
assertEquals(-1, input.read(buf));
@@ -205,22 +210,24 @@ void testMultiByteReadConsistentlyReturnsMinusOneAtEof() throws Exception {
@Test
void testParseTarTruncatedInContent() {
- assertThrows(IOException.class, () -> new TarFile(getPath("COMPRESS-544_truncated_in_content.tar")));
+ assertThrows(IOException.class, () -> TarFile.builder().setURI(getURI("COMPRESS-544_truncated_in_content.tar")).get());
}
@Test
void testParseTarTruncatedInPadding() {
- assertThrows(IOException.class, () -> new TarFile(getPath("COMPRESS-544_truncated_in_padding.tar")));
+ assertThrows(IOException.class, () -> TarFile.builder().setURI(getURI("COMPRESS-544_truncated_in_padding.tar")).get());
}
@Test
void testParseTarWithNonNumberPaxHeaders() {
- assertThrows(ArchiveException.class, () -> new TarFile(getPath("COMPRESS-529-fail.tar")));
+ assertThrows(ArchiveException.class, () -> TarFile.builder().setURI(getURI("COMPRESS-529-fail.tar")).get());
}
@Test
void testParseTarWithSpecialPaxHeaders() {
- assertThrows(ArchiveException.class, () -> new TarFile(getPath("COMPRESS-530-fail.tar")));
+ final ArchiveException ex = assertThrows(ArchiveException.class, () -> TarFile.builder().setURI(getURI("COMPRESS-530-fail.tar")).get());
+ // Parsing fails since the data starts with null bytes
+ assertTrue(ex.getMessage().contains("non-number"));
}
@Test
@@ -230,7 +237,7 @@ void testReadsArchiveCompletely_COMPRESS245() {
try (GZIPInputStream gin = new GZIPInputStream(Files.newInputStream(getPath("COMPRESS-245.tar.gz")))) {
Files.copy(gin, tempTar);
}
- try (TarFile tarFile = new TarFile(tempTar)) {
+ try (TarFile tarFile = TarFile.builder().setPath(tempTar).get()) {
assertEquals(31, tarFile.getEntries().size());
}
} catch (final IOException e) {
@@ -240,7 +247,7 @@ void testReadsArchiveCompletely_COMPRESS245() {
@Test
void testRejectsArchivesWithNegativeSizes() throws Exception {
- assertThrows(ArchiveException.class, () -> new TarFile(getFile("COMPRESS-569-fail.tar")));
+ assertThrows(ArchiveException.class, () -> TarFile.builder().setURI(getURI("COMPRESS-569-fail.tar")).get());
}
@Test
@@ -256,7 +263,7 @@ void testShouldReadBigGid() throws Exception {
tos.closeArchiveEntry();
}
final byte[] data = bos.toByteArray();
- try (TarFile tarFile = new TarFile(data)) {
+ try (TarFile tarFile = TarFile.builder().setByteArray(data).get()) {
final List entries = tarFile.getEntries();
assertEquals(4294967294L, entries.get(0).getLongGroupId());
}
@@ -267,7 +274,7 @@ void testShouldReadBigGid() throws Exception {
*/
@Test
void testShouldReadGNULongNameEntryWithWrongName() throws Exception {
- try (TarFile tarFile = new TarFile(getPath("COMPRESS-324.tar"))) {
+ try (TarFile tarFile = TarFile.builder().setURI(getURI("COMPRESS-324.tar")).get()) {
final List entries = tarFile.getEntries();
assertEquals(
"1234567890123456789012345678901234567890123456789012345678901234567890"
@@ -280,15 +287,15 @@ void testShouldReadGNULongNameEntryWithWrongName() throws Exception {
@Test
void testShouldThrowAnExceptionOnTruncatedEntries() throws Exception {
createTempDirectory("COMPRESS-279");
- assertThrows(IOException.class, () -> new TarFile(getPath("COMPRESS-279.tar")));
+ assertThrows(IOException.class, () -> TarFile.builder().setURI(getURI("COMPRESS-279.tar")).get());
}
@Test
void testShouldUseSpecifiedEncodingWhenReadingGNULongNames() throws Exception {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
- final String encoding = StandardCharsets.UTF_16.name();
+ final Charset encoding = StandardCharsets.UTF_16;
final String name = "1234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890\u00e4";
- try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, encoding)) {
+ try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, encoding.name())) {
tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
final TarArchiveEntry t = new TarArchiveEntry(name);
t.setSize(1);
@@ -297,7 +304,7 @@ void testShouldUseSpecifiedEncodingWhenReadingGNULongNames() throws Exception {
tos.closeArchiveEntry();
}
final byte[] data = bos.toByteArray();
- try (TarFile tarFile = new TarFile(data, encoding)) {
+ try (TarFile tarFile = TarFile.builder().setByteArray(data).setCharset(encoding).get()) {
final List entries = tarFile.getEntries();
assertEquals(1, entries.size());
assertEquals(name, entries.get(0).getName());
@@ -306,7 +313,7 @@ void testShouldUseSpecifiedEncodingWhenReadingGNULongNames() throws Exception {
@Test
void testSingleByteReadConsistentlyReturnsMinusOneAtEof() throws Exception {
- try (TarFile tarFile = new TarFile(getPath("bla.tar"));
+ try (TarFile tarFile = TarFile.builder().setURI(getURI("bla.tar")).get();
InputStream input = tarFile.getInputStream(tarFile.getEntries().get(0))) {
IOUtils.toByteArray(input);
assertEquals(-1, input.read());
@@ -319,7 +326,7 @@ void testSingleByteReadConsistentlyReturnsMinusOneAtEof() throws Exception {
*/
@Test
void testSkipsDevNumbersWhenEntryIsNoDevice() throws Exception {
- try (TarFile tarFile = new TarFile(getPath("COMPRESS-417.tar"))) {
+ try (TarFile tarFile = TarFile.builder().setURI(getURI("COMPRESS-417.tar")).get()) {
final List entries = tarFile.getEntries();
assertEquals(2, entries.size());
assertEquals("test1.xml", entries.get(0).getName());
@@ -334,7 +341,7 @@ void testSkipsDevNumbersWhenEntryIsNoDevice() throws Exception {
*/
@Test
void testSurvivesBlankLinesInPaxHeader() throws Exception {
- try (TarFile tarFile = new TarFile(getPath("COMPRESS-355.tar"))) {
+ try (TarFile tarFile = TarFile.builder().setURI(getURI("COMPRESS-355.tar")).get()) {
final List entries = tarFile.getEntries();
assertEquals(1, entries.size());
assertEquals("package/package.json", entries.get(0).getName());
@@ -347,7 +354,7 @@ void testSurvivesBlankLinesInPaxHeader() throws Exception {
*/
@Test
void testSurvivesPaxHeaderWithNameEndingInSlash() throws Exception {
- try (TarFile tarFile = new TarFile(getPath("COMPRESS-356.tar"))) {
+ try (TarFile tarFile = TarFile.builder().setURI(getURI("COMPRESS-356.tar")).get()) {
final List entries = tarFile.getEntries();
assertEquals(1, entries.size());
assertEquals("package/package.json", entries.get(0).getName());
@@ -357,18 +364,18 @@ void testSurvivesPaxHeaderWithNameEndingInSlash() throws Exception {
@Test
void testThrowException() {
- assertThrows(ArchiveException.class, () -> new TarFile(getPath("COMPRESS-553-fail.tar")));
+ assertThrows(ArchiveException.class, () -> TarFile.builder().setURI(getURI("COMPRESS-553-fail.tar")).get());
}
@Test
void testThrowExceptionWithNullEntry() {
// Only on Windows: throws a UnmappableCharacterException
- assertThrows(IOException.class, () -> new TarFile(getPath("COMPRESS-554-fail.tar")));
+ assertThrows(IOException.class, () -> TarFile.builder().setURI(getURI("COMPRESS-554-fail.tar")).get());
}
@Test
void testWorkaroundForBrokenTimeHeader() throws IOException {
- try (TarFile tarFile = new TarFile(getPath("simple-aix-native-tar.tar"))) {
+ try (TarFile tarFile = TarFile.builder().setURI(getURI("simple-aix-native-tar.tar")).get()) {
final List entries = tarFile.getEntries();
assertEquals(3, entries.size());
final TarArchiveEntry entry = entries.get(1);
diff --git a/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java b/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java
index c9375513616..4369d66be84 100644
--- a/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java
@@ -20,6 +20,8 @@
package org.apache.commons.compress.archivers.tar;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptyMap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -29,12 +31,9 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
@@ -50,6 +49,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
class TarUtilsTest extends AbstractTest {
@@ -117,12 +117,6 @@ static Stream testReadLongNameHandlesLimits() {
Arguments.of("POSIX (padded)", posixLongName, paddedUtf8Bytes(posixLongName)));
}
- static Stream testReadLongNameThrowsOnTruncation() {
- return Stream.of(
- Arguments.of(Integer.MAX_VALUE, "truncated long name"),
- Arguments.of(Long.MAX_VALUE, "invalid long name"));
- }
-
private static byte[] utf8Bytes(final String s) {
return s.getBytes(UTF_8);
}
@@ -155,6 +149,11 @@ private void checkRoundTripOctalOrBinary(final long value, final int bufsize) {
assertEquals(value, parseValue);
}
+ private static Map parsePaxHeaders(final byte[] data, final List sparseHeaders,
+ final Map globalPaxHeaders) throws IOException {
+ return TarUtils.parsePaxHeaders(new ByteArrayInputStream(data), globalPaxHeaders, data.length, Short.MAX_VALUE, sparseHeaders);
+ }
+
@Test
void testName() {
byte[] buff = new byte[20];
@@ -291,7 +290,7 @@ void testParseOctalInvalid() {
@Test
void testParsePAX01SparseHeadersRejectsOddNumberOfEntries() {
final String map = "0,10,20,0,20";
- assertThrows(UncheckedIOException.class, () -> TarUtils.parsePAX01SparseHeaders(map));
+ assertThrows(ArchiveException.class, () -> TarUtils.parseFromPAX01SparseHeaders(map));
}
@Test
@@ -399,7 +398,8 @@ void testParseTarWithSpecialPaxHeaders() throws IOException {
@Test
void testPaxHeaderEntryWithEmptyValueRemovesKey() throws Exception {
- final Map headers = TarUtils.parsePaxHeaders(new ByteArrayInputStream("11 foo=bar\n7 foo=\n".getBytes(UTF_8)), null, new HashMap<>());
+ final byte[] bytes = "11 foo=bar\n7 foo=\n".getBytes(UTF_8);
+ final Map headers = parsePaxHeaders(bytes, emptyList(), emptyMap());
assertEquals(0, headers.size());
}
@@ -413,7 +413,7 @@ void testReadLongNameHandlesLimits(final String kind, final String expectedName,
Arrays.fill(dataWithGarbage, data.length, dataWithGarbage.length, (byte) 0xFF);
try (InputStream in = new ByteArrayInputStream(dataWithGarbage)) {
- final String actualName = TarUtils.readLongName(in, ZipEncodingHelper.getZipEncoding(UTF_8), entry);
+ final String actualName = TarUtils.readLongName(in, ZipEncodingHelper.getZipEncoding(UTF_8), Integer.MAX_VALUE, entry);
assertEquals(
expectedName,
actualName,
@@ -422,20 +422,15 @@ void testReadLongNameHandlesLimits(final String kind, final String expectedName,
}
@ParameterizedTest(name = "readLongName of {0} bytes throws ArchiveException")
- @MethodSource
- void testReadLongNameThrowsOnTruncation(final long size, final CharSequence expectedMessage) throws IOException {
+ @ValueSource(longs = { Integer.MAX_VALUE, Long.MAX_VALUE })
+ void testReadLongNameThrowsOnTruncation(final long size) throws IOException {
final TarArchiveEntry entry = new TarArchiveEntry("test");
entry.setSize(size); // absurdly large so any finite stream truncates
try (InputStream in = new NullInputStream()) {
- final ArchiveException ex = assertThrows(
- ArchiveException.class,
- () -> TarUtils.readLongName(in, TarUtils.DEFAULT_ENCODING, entry),
- "Expected ArchiveException due to truncated long name, but no exception was thrown");
+ final IOException ex = assertThrows(IOException.class, () -> TarUtils.readLongName(in, TarUtils.DEFAULT_ENCODING, Integer.MAX_VALUE, entry),
+ "Expected IOException due to out of range size for Java byte arrays");
final String actualMessage = StringUtils.toRootLowerCase(ex.getMessage());
assertNotNull(actualMessage, "Exception message should not be null");
- assertTrue(
- actualMessage.contains(expectedMessage),
- () -> "Expected exception message to contain '" + expectedMessage + "', but got: " + actualMessage);
assertTrue(
actualMessage.contains(String.format("%,d", size)),
() -> "Expected exception message to mention '" + size + "', but got: " + actualMessage);
@@ -459,17 +454,18 @@ void testReadNegativeBinary8Byte() {
void testReadNonAsciiPaxHeader() throws Exception {
final String ae = "\u00e4";
final String line = "11 path=" + ae + "\n";
- assertEquals(11, line.getBytes(UTF_8).length);
- final Map headers = TarUtils.parsePaxHeaders(new ByteArrayInputStream(line.getBytes(UTF_8)), null, new HashMap<>());
+ final byte[] bytes = line.getBytes(UTF_8);
+ assertEquals(11, bytes.length);
+ final Map headers = parsePaxHeaders(bytes, emptyList(), emptyMap());
assertEquals(1, headers.size());
assertEquals(ae, headers.get("path"));
}
@Test
void testReadPax00SparseHeader() throws Exception {
- final String header = "23 GNU.sparse.offset=0\n26 GNU.sparse.numbytes=10\n";
+ final byte[] header = "23 GNU.sparse.offset=0\n26 GNU.sparse.numbytes=10\n".getBytes(UTF_8);
final List sparseHeaders = new ArrayList<>();
- TarUtils.parsePaxHeaders(new ByteArrayInputStream(header.getBytes(UTF_8)), sparseHeaders, Collections.emptyMap());
+ parsePaxHeaders(header, sparseHeaders, emptyMap());
assertEquals(1, sparseHeaders.size());
assertEquals(0, sparseHeaders.get(0).getOffset());
assertEquals(10, sparseHeaders.get(0).getNumbytes());
@@ -477,9 +473,9 @@ void testReadPax00SparseHeader() throws Exception {
@Test
void testReadPax00SparseHeaderMakesNumbytesOptional() throws Exception {
- final String header = "23 GNU.sparse.offset=0\n24 GNU.sparse.offset=10\n";
+ final byte[] header = "23 GNU.sparse.offset=0\n24 GNU.sparse.offset=10\n".getBytes(UTF_8);
final List sparseHeaders = new ArrayList<>();
- TarUtils.parsePaxHeaders(new ByteArrayInputStream(header.getBytes(UTF_8)), sparseHeaders, Collections.emptyMap());
+ parsePaxHeaders(header, sparseHeaders, emptyMap());
assertEquals(2, sparseHeaders.size());
assertEquals(0, sparseHeaders.get(0).getOffset());
assertEquals(0, sparseHeaders.get(0).getNumbytes());
@@ -487,48 +483,48 @@ void testReadPax00SparseHeaderMakesNumbytesOptional() throws Exception {
assertEquals(0, sparseHeaders.get(1).getNumbytes());
}
- @Test
- void testReadPax00SparseHeaderRejectsNegativeNumbytes() throws Exception {
- final String header = "23 GNU.sparse.offset=0\n26 GNU.sparse.numbytes=-1\n";
- assertThrows(ArchiveException.class, () -> TarUtils.parsePaxHeaders(new ByteArrayInputStream(header.getBytes(UTF_8)), null, Collections.emptyMap()));
- }
-
- @Test
- void testReadPax00SparseHeaderRejectsNegativeOffset() throws Exception {
- final String header = "24 GNU.sparse.offset=-1\n26 GNU.sparse.numbytes=10\n";
- assertThrows(ArchiveException.class, () -> TarUtils.parsePaxHeaders(new ByteArrayInputStream(header.getBytes(UTF_8)), null, Collections.emptyMap()));
- }
-
- @Test
- void testReadPax00SparseHeaderRejectsNonNumericNumbytes() throws Exception {
- final String header = "23 GNU.sparse.offset=0\n26 GNU.sparse.numbytes=1a\n";
- assertThrows(IOException.class, () -> TarUtils.parsePaxHeaders(new ByteArrayInputStream(header.getBytes(UTF_8)), null, Collections.emptyMap()));
- }
-
- @Test
- void testReadPax00SparseHeaderRejectsNonNumericOffset() throws Exception {
- final String header = "23 GNU.sparse.offset=a\n26 GNU.sparse.numbytes=10\n";
- assertThrows(ArchiveException.class, () -> TarUtils.parsePaxHeaders(new ByteArrayInputStream(header.getBytes(UTF_8)), null, Collections.emptyMap()));
+ static Stream testReadPaxHeaderInvalidCases() {
+ return Stream.of(
+ Arguments.of(
+ "Negative numbytes in PAX 00 sparse header",
+ "23 GNU.sparse.offset=0\n26 GNU.sparse.numbytes=-1\n"),
+ Arguments.of(
+ "Negative offset in PAX 00 sparse header",
+ "24 GNU.sparse.offset=-1\n26 GNU.sparse.numbytes=10\n"),
+ Arguments.of(
+ "Non-numeric numbytes in PAX 00 sparse header",
+ "23 GNU.sparse.offset=0\n26 GNU.sparse.numbytes=1a\n"),
+ Arguments.of(
+ "Non-numeric offset in PAX 00 sparse header",
+ "23 GNU.sparse.offset=a\n26 GNU.sparse.numbytes=10\n"),
+ Arguments.of(
+ "Numbytes in PAX 00 sparse header without offset",
+ "26 GNU.sparse.numbytes=10\n"
+ ),
+ Arguments.of("Missing trailing newline in PAX header", "30 atime=1321711775.9720594634"));
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource
+ void testReadPaxHeaderInvalidCases(final String description, final String header) {
+ final byte[] bytes = header.getBytes(UTF_8);
+ assertThrows(
+ ArchiveException.class,
+ () -> parsePaxHeaders(bytes, emptyList(), emptyMap()));
}
@Test
void testReadPaxHeaderWithEmbeddedNewline() throws Exception {
- final Map headers = TarUtils.parsePaxHeaders(new ByteArrayInputStream("28 comment=line1\nline2\nand3\n".getBytes(UTF_8)), null,
- new HashMap<>());
+ final byte[] header = "28 comment=line1\nline2\nand3\n".getBytes(UTF_8);
+ final Map headers = parsePaxHeaders(header, emptyList(), emptyMap());
assertEquals(1, headers.size());
assertEquals("line1\nline2\nand3", headers.get("comment"));
}
- @Test
- void testReadPaxHeaderWithoutTrailingNewline() throws Exception {
- assertThrows(ArchiveException.class,
- () -> TarUtils.parsePaxHeaders(new ByteArrayInputStream("30 atime=1321711775.9720594634".getBytes(UTF_8)), null, Collections.emptyMap()));
- }
-
@Test
void testReadSimplePaxHeader() throws Exception {
- final Map headers = TarUtils.parsePaxHeaders(new ByteArrayInputStream("30 atime=1321711775.972059463\n".getBytes(UTF_8)), null,
- new HashMap<>());
+ final byte[] header = "30 atime=1321711775.972059463\n".getBytes(UTF_8);
+ final Map headers = parsePaxHeaders(header, emptyList(), emptyMap());
assertEquals(1, headers.size());
assertEquals("1321711775.972059463", headers.get("atime"));
}
@@ -643,8 +639,8 @@ void testRoundTripOctalOrBinary8_ValueTooBigForBinary() {
@Test
void testSecondEntryWinsWhenPaxHeaderContainsDuplicateKey() throws Exception {
- final Map headers = TarUtils.parsePaxHeaders(new ByteArrayInputStream("11 foo=bar\n11 foo=baz\n".getBytes(UTF_8)), null,
- new HashMap<>());
+ final byte[] header = "11 foo=bar\n11 foo=baz\n".getBytes(UTF_8);
+ final Map headers = parsePaxHeaders(header, emptyList(), emptyMap());
assertEquals(1, headers.size());
assertEquals("baz", headers.get("foo"));
}
diff --git a/src/test/java/org/apache/commons/compress/archivers/zip/ScatterSampleTest.java b/src/test/java/org/apache/commons/compress/archivers/zip/ScatterSampleTest.java
index 04533ee9846..d24df192367 100644
--- a/src/test/java/org/apache/commons/compress/archivers/zip/ScatterSampleTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/zip/ScatterSampleTest.java
@@ -30,7 +30,7 @@
import org.apache.commons.compress.AbstractTempDirTest;
import org.apache.commons.compress.parallel.InputStreamSupplier;
-import org.apache.commons.compress.utils.IOUtils;
+import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
class ScatterSampleTest extends AbstractTempDirTest {
@@ -41,7 +41,7 @@ private void checkFile(final File result) throws IOException {
assertEquals("test1.xml", archiveEntry1.getName());
try (InputStream inputStream = zipFile.getInputStream(archiveEntry1)) {
final byte[] b = new byte[6];
- final int i = IOUtils.readFully(inputStream, b);
+ final int i = IOUtils.read(inputStream, b);
assertEquals(5, i);
assertEquals('H', b[0]);
assertEquals('o', b[4]);
diff --git a/src/test/java/org/apache/commons/compress/archivers/zip/UTF8ZipFilesTest.java b/src/test/java/org/apache/commons/compress/archivers/zip/UTF8ZipFilesTest.java
index aceae288baf..c9dbe374d62 100644
--- a/src/test/java/org/apache/commons/compress/archivers/zip/UTF8ZipFilesTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/zip/UTF8ZipFilesTest.java
@@ -201,8 +201,7 @@ void testRawNameReadFromZipFile() throws IOException {
*/
@Test
void testRead7ZipArchive() throws IOException {
- final File archive = getFile("utf8-7zip-test.zip");
- try (ZipFile zf = new ZipFile(archive, CP437, false)) {
+ try (ZipFile zf = ZipFile.builder().setURI(getURI("utf8-7zip-test.zip")).setCharset(CP437).setUseUnicodeExtraFields(false).get()) {
assertNotNull(zf.getEntry(ASCII_TXT));
assertNotNull(zf.getEntry(EURO_FOR_DOLLAR_TXT));
assertNotNull(zf.getEntry(OIL_BARREL_TXT));
diff --git a/src/test/java/org/apache/commons/compress/archivers/zip/Zip64SupportIT.java b/src/test/java/org/apache/commons/compress/archivers/zip/Zip64SupportIT.java
index 6804f76a0aa..3f6db849979 100644
--- a/src/test/java/org/apache/commons/compress/archivers/zip/Zip64SupportIT.java
+++ b/src/test/java/org/apache/commons/compress/archivers/zip/Zip64SupportIT.java
@@ -42,14 +42,15 @@
import java.util.zip.ZipEntry;
import org.apache.commons.compress.AbstractTest;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.RandomAccessFileMode;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
-import shaded.org.apache.commons.io.IOUtils;
-
/**
* Tests {@link ZipFile} Zip64 support.
*/
+@Disabled
public class Zip64SupportIT {
interface ZipOutputTest {
diff --git a/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStreamTest.java b/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStreamTest.java
index b5f71797d93..3da6147aab2 100644
--- a/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStreamTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStreamTest.java
@@ -69,7 +69,7 @@ private static final class AirliftZipArchiveInputStream extends ZipArchiveInputS
private boolean used;
- private AirliftZipArchiveInputStream(final InputStream inputStream) {
+ private AirliftZipArchiveInputStream(final InputStream inputStream) throws IOException {
super(inputStream);
}
@@ -221,8 +221,7 @@ void testGetCompressedCountEmptyZip() throws IOException {
@Test
void testGetFirstEntryEmptyZip() throws IOException {
try (ZipArchiveInputStream zin = ZipArchiveInputStream.builder().setByteArray(ArrayUtils.EMPTY_BYTE_ARRAY).get()) {
- final ZipArchiveEntry entry = zin.getNextEntry();
- assertNull(entry);
+ assertNull(zin.getNextEntry());
}
}
diff --git a/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileIgnoringLocalFileHeaderTest.java b/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileIgnoringLocalFileHeaderTest.java
index 397a09723c7..3406dc78032 100644
--- a/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileIgnoringLocalFileHeaderTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileIgnoringLocalFileHeaderTest.java
@@ -25,7 +25,6 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Enumeration;
@@ -38,18 +37,12 @@ class ZipFileIgnoringLocalFileHeaderTest {
private static ZipFile openZipWithoutLocalFileHeader(final String fileName) throws IOException {
// @formatter:off
return ZipFile.builder()
- .setFile(AbstractTest.getFile(fileName))
- .setCharset(StandardCharsets.UTF_8.name())
- .setUseUnicodeExtraFields(true)
+ .setURI(AbstractTest.getURI(fileName))
.setIgnoreLocalFileHeader(true)
.get();
// @formatter:on
}
- private static ZipFile openZipWithoutLocalFileHeaderDeprecated(final String fileName) throws IOException {
- return new ZipFile(AbstractTest.getFile(fileName), StandardCharsets.UTF_8.name(), true, true);
- }
-
@TempDir
private File dir;
@@ -104,7 +97,7 @@ void testPhysicalOrder() throws IOException {
*/
@Test
void testZipUnarchive() throws Exception {
- try (ZipFile zipFile = openZipWithoutLocalFileHeaderDeprecated("bla.zip")) {
+ try (ZipFile zipFile = openZipWithoutLocalFileHeader("bla.zip")) {
zipFile.stream().forEach(entry -> {
try (InputStream inputStream = zipFile.getInputStream(entry)) {
Files.copy(inputStream, new File(dir, entry.getName()).toPath());
diff --git a/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java b/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java
index ba97587aaa9..7f297c3b084 100644
--- a/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java
@@ -56,15 +56,17 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
-import org.apache.commons.compress.AbstractTest;
+import org.apache.commons.compress.archivers.AbstractArchiveFileTest;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.channels.ByteArraySeekableByteChannel;
import org.apache.commons.io.function.IORunnable;
import org.apache.commons.lang3.ArrayFill;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.SystemUtils;
+import org.apache.commons.lang3.reflect.FieldUtils;
import org.junit.Assume;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
@@ -72,7 +74,7 @@
import io.airlift.compress.zstd.ZstdInputStream;
-class ZipFileTest extends AbstractTest {
+class ZipFileTest extends AbstractArchiveFileTest {
/**
* This Class simulates the case where the Zip File uses the aircompressors {@link ZstdInputStream}
@@ -109,7 +111,7 @@ private static void assertEntryName(final ArrayList entries, fi
}
private static void nameSource(final String archive, final String entry, final ZipArchiveEntry.NameSource expected) throws Exception {
- try (ZipFile zf = ZipFile.builder().setFile(getFile(archive)).get()) {
+ try (ZipFile zf = ZipFile.builder().setURI(getURI(archive)).get()) {
final ZipArchiveEntry ze = zf.getEntry(entry);
assertEquals(entry, ze.getName());
assertEquals(expected, ze.getNameSource());
@@ -190,6 +192,11 @@ private long calculateCrc32(final byte[] content) {
return crc.getValue();
}
+ @Override
+ protected ZipFile getArchiveFile() throws IOException {
+ return ZipFile.builder().setPath(getPath("bla.zip")).get();
+ }
+
private void multiByteReadConsistentlyReturnsMinusOneAtEof(final File file) throws Exception {
final byte[] buf = new byte[2];
try (ZipFile zipFile = ZipFile.builder().setFile(file).get()) {
@@ -211,7 +218,7 @@ private void multiByteReadConsistentlyReturnsMinusOneAtEof(final File file) thro
* The central directory has ZipFile and ZipUtil swapped so central directory order is different from entry data order.
*/
private void readOrderTest() throws Exception {
- zf = ZipFile.builder().setFile(getFile("ordertest.zip")).get();
+ zf = ZipFile.builder().setURI(getURI("ordertest.zip")).get();
}
/**
@@ -251,7 +258,7 @@ void testAlternativeZstdInputStream() throws Exception {
try (InputStream inputStream = zf.getInputStream(ze)) {
assertNotNull(inputStream);
assertFalse(zf.isUsed());
- final int bytesRead = org.apache.commons.compress.utils.IOUtils.readFully(inputStream, buffer);
+ final int bytesRead = IOUtils.read(inputStream, buffer);
assertEquals(6066, bytesRead);
assertTrue(zf.isUsed());
}
@@ -264,7 +271,7 @@ void testAlternativeZstdInputStream() throws Exception {
try (InputStream inputStream = builtZipFile.getInputStream(ze)) {
assertTrue(inputStream instanceof ZstdInputStream);
assertNotNull(inputStream);
- final int bytesRead = org.apache.commons.compress.utils.IOUtils.readFully(inputStream, buffer);
+ final int bytesRead = IOUtils.read(inputStream, buffer);
assertEquals(6066, bytesRead);
}
}
@@ -284,14 +291,14 @@ void testCDOrder() throws Exception {
@Test
void testCDOrderInMemory() throws Exception {
final byte[] data = readAllBytes("ordertest.zip");
- zf = ZipFile.builder().setByteArray(data).setCharset(StandardCharsets.UTF_8).get();
+ zf = ZipFile.builder().setByteArray(data).get();
testCDOrderInMemory(zf);
- try (SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(data)) {
- zf = ZipFile.builder().setSeekableByteChannel(channel).setCharset(StandardCharsets.UTF_8).get();
+ try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.wrap(data)) {
+ zf = ZipFile.builder().setChannel(channel).get();
testCDOrderInMemory(zf);
}
- try (SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(data)) {
- zf = new ZipFile(channel, StandardCharsets.UTF_8.name());
+ try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.wrap(data)) {
+ zf = ZipFile.builder().setChannel(channel).get();
testCDOrderInMemory(zf);
}
}
@@ -326,8 +333,7 @@ private void testCDOrderInMemory(final ZipFile zipFile) {
@Test
void testConcurrentReadFile() throws Exception {
// mixed.zip contains both inflated and stored files
- final File archive = getFile("mixed.zip");
- zf = new ZipFile(archive);
+ zf = ZipFile.builder().setURI(getURI("mixed.zip")).get();
final Map content = new HashMap<>();
zf.stream().forEach(entry -> {
try (InputStream inputStream = zf.getInputStream(entry)) {
@@ -356,7 +362,7 @@ void testConcurrentReadSeekable() throws Exception {
data = IOUtils.toByteArray(fis);
}
try (SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(data)) {
- zf = ZipFile.builder().setSeekableByteChannel(channel).setCharset(StandardCharsets.UTF_8).get();
+ zf = ZipFile.builder().setChannel(channel).setCharset(StandardCharsets.UTF_8).get();
final Map content = new HashMap<>();
zf.stream().forEach(entry -> {
try (InputStream inputStream = zf.getInputStream(entry)) {
@@ -439,8 +445,7 @@ void testDoubleClose() throws Exception {
*/
@Test
void testDuplicateEntry() throws Exception {
- final File archive = getFile("COMPRESS-227.zip");
- zf = new ZipFile(archive);
+ zf = ZipFile.builder().setURI(getURI("COMPRESS-227.zip")).get();
final ZipArchiveEntry ze = zf.getEntry("test1.txt");
assertNotNull(ze);
@@ -496,7 +501,8 @@ void testEntryAlignment() throws Exception {
}
- try (ZipFile zf = ZipFile.builder().setByteArray(Arrays.copyOfRange(zipContent.array(), 0, (int) zipContent.getSize())).get()) {
+ try (ZipFile zf = ZipFile.builder().setByteArray(Arrays.copyOfRange(zipContent.array(), 0, (int) FieldUtils.readDeclaredField(zipContent, "size",
+ true))).get()) {
final ZipArchiveEntry inflatedEntry = zf.getEntry("inflated.txt");
final ResourceAlignmentExtraField inflatedAlignmentEx = (ResourceAlignmentExtraField) inflatedEntry
.getExtraField(ResourceAlignmentExtraField.ID);
@@ -566,8 +572,7 @@ void testEntryAlignmentExceed() throws Exception {
*/
@Test
void testExcessDataInZip64ExtraField() throws Exception {
- final File archive = getFile("COMPRESS-228.zip");
- zf = new ZipFile(archive);
+ zf = ZipFile.builder().setURI(getURI("COMPRESS-228.zip")).get();
// actually, if we get here, the test already has passed
final ZipArchiveEntry ze = zf.getEntry("src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java");
@@ -578,7 +583,7 @@ void testExcessDataInZip64ExtraField() throws Exception {
void testExtractFileLiesAcrossSplitZipSegmentsCreatedByWinrar() throws Exception {
final File lastFile = getFile("COMPRESS-477/split_zip_created_by_winrar/split_zip_created_by_winrar.zip");
try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile)) {
- zf = ZipFile.builder().setSeekableByteChannel(channel).get();
+ zf = ZipFile.builder().setChannel(channel).get();
// the compressed content of ZipArchiveInputStream.java lies between .z01 and .z02
final ZipArchiveEntry zipEntry = zf.getEntry("commons-compress/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java");
@@ -591,7 +596,7 @@ void testExtractFileLiesAcrossSplitZipSegmentsCreatedByWinrar() throws Exception
void testExtractFileLiesAcrossSplitZipSegmentsCreatedByZip() throws Exception {
final File lastFile = getFile("COMPRESS-477/split_zip_created_by_zip/split_zip_created_by_zip.zip");
try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile)) {
- zf = new ZipFile(channel);
+ zf = ZipFile.builder().setChannel(channel).get();
// the compressed content of UnsupportedCompressionAlgorithmException.java lies between .z01 and .z02
ZipArchiveEntry zipEntry = zf
@@ -610,7 +615,7 @@ void testExtractFileLiesAcrossSplitZipSegmentsCreatedByZip() throws Exception {
void testExtractFileLiesAcrossSplitZipSegmentsCreatedByZipOfZip64() throws Exception {
final File lastFile = getFile("COMPRESS-477/split_zip_created_by_zip/split_zip_created_by_zip_zip64.zip");
try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile)) {
- zf = new ZipFile(channel);
+ zf = ZipFile.builder().setChannel(channel).get();
// the compressed content of UnsupportedCompressionAlgorithmException.java lies between .z01 and .z02
ZipArchiveEntry zipEntry = zf
@@ -628,8 +633,7 @@ void testExtractFileLiesAcrossSplitZipSegmentsCreatedByZipOfZip64() throws Excep
@Test
void testGetEntries() throws Exception {
// mixed.zip contains both inflated and stored files
- final File archive = getFile("mixed.zip");
- zf = new ZipFile(archive);
+ zf = ZipFile.builder().setURI(getURI("mixed.zip")).get();
final Map content = new HashMap<>();
for (final ZipArchiveEntry entry : Collections.list(zf.getEntries())) {
try (InputStream inputStream = zf.getInputStream(entry)) {
@@ -644,8 +648,7 @@ void testGetEntries() throws Exception {
@Test
void testGetEntriesInPhysicalOrder() throws Exception {
// mixed.zip contains both inflated and stored files
- final File archive = getFile("mixed.zip");
- zf = new ZipFile(archive);
+ zf = ZipFile.builder().setURI(getURI("mixed.zip")).get();
final Map content = new HashMap<>();
for (final ZipArchiveEntry entry : Collections.list(zf.getEntriesInPhysicalOrder())) {
try (InputStream inputStream = zf.getInputStream(entry)) {
@@ -716,8 +719,7 @@ void testNameSourceIsSetToUnicodeExtraField() throws Exception {
@Test
void testOffsets() throws Exception {
// mixed.zip contains both inflated and stored files
- final File archive = getFile("mixed.zip");
- try (ZipFile zf = new ZipFile(archive)) {
+ try (ZipFile zf = ZipFile.builder().setURI(getURI("mixed.zip")).get()) {
final ZipArchiveEntry inflatedEntry = zf.getEntry("inflated.txt");
assertEquals(0x0000, inflatedEntry.getLocalHeaderOffset());
assertEquals(0x0046, inflatedEntry.getDataOffset());
@@ -775,10 +777,8 @@ void testPhysicalOrderOfSpecificFile() throws Exception {
*/
@Test
void testReadDeflate64CompressedStream() throws Exception {
- final File input = getFile("COMPRESS-380/COMPRESS-380-input");
- final File archive = getFile("COMPRESS-380/COMPRESS-380.zip");
- try (InputStream in = Files.newInputStream(input.toPath());
- ZipFile zf = new ZipFile(archive)) {
+ try (InputStream in = Files.newInputStream(getPath("COMPRESS-380/COMPRESS-380-input"));
+ ZipFile zf = ZipFile.builder().setURI(getURI("COMPRESS-380/COMPRESS-380.zip")).get()) {
final byte[] orig = IOUtils.toByteArray(in);
final ZipArchiveEntry e = zf.getEntry("input2");
try (InputStream s = zf.getInputStream(e)) {
@@ -796,7 +796,7 @@ void testReadingOfExtraDataBeforeZip() throws IOException {
final byte[] fileHeader = "Before Zip file".getBytes(UTF_8);
final String entryName = "COMPRESS-621.txt";
final byte[] entryContent = "https://issues.apache.org/jira/browse/COMPRESS-621".getBytes(UTF_8);
- try (ZipFile archive = new ZipFile(getFile("COMPRESS-621.zip"))) {
+ try (ZipFile archive = ZipFile.builder().setURI(getURI("COMPRESS-621.zip")).get()) {
assertEquals(fileHeader.length, archive.getFirstLocalFileHeaderOffset());
try (InputStream input = archive.getContentBeforeFirstLocalFileHeader()) {
assertArrayEquals(fileHeader, IOUtils.toByteArray(input));
@@ -815,8 +815,7 @@ void testReadingOfExtraDataBeforeZip() throws IOException {
*/
@Test
void testReadingOfFirstStoredEntry() throws Exception {
- final File archive = getFile("COMPRESS-264.zip");
- zf = new ZipFile(archive);
+ zf = ZipFile.builder().setURI(getURI("COMPRESS-264.zip")).get();
final ZipArchiveEntry ze = zf.getEntry("test.txt");
assertEquals(5, ze.getSize());
try (InputStream inputStream = zf.getInputStream(ze)) {
@@ -839,7 +838,7 @@ void testReadingOfStoredEntry() throws Exception {
zo.closeArchiveEntry();
}
- zf = new ZipFile(file);
+ zf = ZipFile.builder().setFile(file).get();
ze = zf.getEntry("foo");
assertNotNull(ze);
try (InputStream i = zf.getInputStream(ze)) {
@@ -906,7 +905,7 @@ void testSelfExtractingZipUsingUnzipsfx() throws IOException, InterruptedExcepti
}
try (InputStream inputStream = Files.newInputStream(extractedFile.toPath())) {
- bytesRead = org.apache.commons.compress.utils.IOUtils.readFully(inputStream, buffer);
+ bytesRead = IOUtils.read(inputStream, buffer);
assertEquals(testData.length, bytesRead);
assertArrayEquals(testData, Arrays.copyOfRange(buffer, 0, bytesRead));
}
@@ -965,8 +964,7 @@ void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingUnshrink() throws Ex
*/
@Test
void testSkipsPK00Prefix() throws Exception {
- final File archive = getFile("COMPRESS-208.zip");
- zf = new ZipFile(archive);
+ zf = ZipFile.builder().setURI(getURI("COMPRESS-208.zip")).get();
assertNotNull(zf.getEntry("test1.xml"));
assertNotNull(zf.getEntry("test2.xml"));
}
@@ -998,8 +996,7 @@ void testUnixSymlinkSampleFile() throws Exception {
expectedVals.put(entryPrefix + "link6", "../COMPRESS-214_unix_symlinks/././a/b/");
// I looked into creating a test with hard links, but ZIP does not appear to
// support hard links, so nevermind.
- final File archive = getFile("COMPRESS-214_unix_symlinks.zip");
- zf = new ZipFile(archive);
+ zf = ZipFile.builder().setURI(getURI("COMPRESS-214_unix_symlinks.zip")).get();
zf.stream().forEach(zae -> {
final String link = zf.getUnixSymlink(zae);
if (zae.isUnixSymlink()) {
@@ -1015,7 +1012,7 @@ void testUnixSymlinkSampleFile() throws Exception {
@Test
void testUnshrinking() throws Exception {
- zf = new ZipFile(getFile("SHRUNK.ZIP"));
+ zf = ZipFile.builder().setURI(getURI("SHRUNK.ZIP")).get();
ZipArchiveEntry test = zf.getEntry("TEST1.XML");
try (InputStream original = newInputStream("test1.xml");
InputStream inputStream = zf.getInputStream(test)) {
@@ -1030,8 +1027,7 @@ void testUnshrinking() throws Exception {
@Test
void testUnzipBZip2CompressedEntry() throws Exception {
- final File archive = getFile("bzip2-zip.zip");
- zf = new ZipFile(archive);
+ zf = ZipFile.builder().setURI(getURI("bzip2-zip.zip")).get();
final ZipArchiveEntry ze = zf.getEntry("lots-of-as");
assertEquals(42, ze.getSize());
final byte[] expected = ArrayFill.fill(new byte[42], (byte) 'a');
@@ -1045,8 +1041,7 @@ void testUnzipBZip2CompressedEntry() throws Exception {
*/
@Test
void testWinzipBackSlashWorkaround() throws Exception {
- final File archive = getFile("test-winzip.zip");
- zf = new ZipFile(archive);
+ zf = ZipFile.builder().setURI(getURI("test-winzip.zip")).get();
assertNull(zf.getEntry("\u00e4\\\u00fc.txt"));
assertNotNull(zf.getEntry("\u00e4/\u00fc.txt"));
}
@@ -1096,5 +1091,4 @@ void testZstdInputStreamErrorCloseWhenGc() throws Exception {
}
}
}
-
}
diff --git a/src/test/java/org/apache/commons/compress/archivers/zip/ZipMemoryFileSystemTest.java b/src/test/java/org/apache/commons/compress/archivers/zip/ZipMemoryFileSystemTest.java
index 60c6fadbd0a..e44e01888ab 100644
--- a/src/test/java/org/apache/commons/compress/archivers/zip/ZipMemoryFileSystemTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/zip/ZipMemoryFileSystemTest.java
@@ -32,7 +32,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.SeekableByteChannel;
-import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -230,8 +229,7 @@ void testScatterFileWithCompressionAndTargetInMemory() throws IOException {
}
}
- try (ZipFile zf = new ZipFile(Files.newByteChannel(target, StandardOpenOption.READ), target.getFileName().toString(), StandardCharsets.UTF_8.name(),
- true)) {
+ try (ZipFile zf = ZipFile.builder().setPath(target).get()) {
final ZipArchiveEntry b_entry = zf.getEntries("b.txt").iterator().next();
assertEquals(8, b_entry.getSize());
try (InputStream inputStream = zf.getInputStream(b_entry)) {
@@ -245,8 +243,7 @@ void testScatterFileWithCompressionAndTargetInMemory() throws IOException {
}
}
- try (ZipFile zf = new ZipFile(Files.newByteChannel(target, StandardOpenOption.READ), target.getFileName().toString(), StandardCharsets.UTF_8.name(),
- true, false)) {
+ try (ZipFile zf = ZipFile.builder().setPath(target).get()) {
final ZipArchiveEntry b_entry = zf.getEntries("b.txt").iterator().next();
assertEquals(8, b_entry.getSize());
try (InputStream inputStream = zf.getInputStream(b_entry)) {
@@ -328,7 +325,7 @@ void testZipFileInMemory() throws IOException {
}
}
- try (ZipFile zf = new ZipFile(target)) {
+ try (ZipFile zf = ZipFile.builder().setPath(target).get()) {
final ZipArchiveEntry b_entry = zf.getEntries("b.txt").iterator().next();
assertEquals(8, b_entry.getSize());
try (InputStream inputStream = zf.getInputStream(b_entry)) {
diff --git a/src/test/java/org/apache/commons/compress/compressors/gzip/GzipCompressorOutputStreamTest.java b/src/test/java/org/apache/commons/compress/compressors/gzip/GzipCompressorOutputStreamTest.java
index 1d3595f5f68..f00a44bee31 100644
--- a/src/test/java/org/apache/commons/compress/compressors/gzip/GzipCompressorOutputStreamTest.java
+++ b/src/test/java/org/apache/commons/compress/compressors/gzip/GzipCompressorOutputStreamTest.java
@@ -45,13 +45,12 @@
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.compress.compressors.gzip.ExtraField.SubField;
import org.apache.commons.compress.compressors.gzip.GzipParameters.OS;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayFill;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
-import shaded.org.apache.commons.io.IOUtils;
-
/**
* Tests {@link GzipCompressorOutputStream}.
*/
diff --git a/src/test/resources/synthetic/long-name/bin-big-endian.cpio b/src/test/resources/synthetic/long-name/bin-big-endian.cpio
new file mode 100644
index 00000000000..322464e498c
Binary files /dev/null and b/src/test/resources/synthetic/long-name/bin-big-endian.cpio differ
diff --git a/src/test/resources/synthetic/long-name/bin-little-endian.cpio b/src/test/resources/synthetic/long-name/bin-little-endian.cpio
new file mode 100644
index 00000000000..91ae61d594b
Binary files /dev/null and b/src/test/resources/synthetic/long-name/bin-little-endian.cpio differ
diff --git a/src/test/resources/synthetic/long-name/bsd-fail.ar b/src/test/resources/synthetic/long-name/bsd-fail.ar
new file mode 100644
index 00000000000..f228574bc21
--- /dev/null
+++ b/src/test/resources/synthetic/long-name/bsd-fail.ar
@@ -0,0 +1,2 @@
+!
+#1/2147483639 0 0 0 100644 2147483639`
diff --git a/src/test/resources/synthetic/long-name/bsd-short-max-value.ar b/src/test/resources/synthetic/long-name/bsd-short-max-value.ar
new file mode 100644
index 00000000000..45061b3fbb7
--- /dev/null
+++ b/src/test/resources/synthetic/long-name/bsd-short-max-value.ar
@@ -0,0 +1,3 @@
+!
+#1/32767 0 0 0 100644 32767 `
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
diff --git a/src/test/resources/synthetic/long-name/crc-fail.cpio b/src/test/resources/synthetic/long-name/crc-fail.cpio
new file mode 100644
index 00000000000..f9563f87251
Binary files /dev/null and b/src/test/resources/synthetic/long-name/crc-fail.cpio differ
diff --git a/src/test/resources/synthetic/long-name/crc.cpio b/src/test/resources/synthetic/long-name/crc.cpio
new file mode 100644
index 00000000000..c4a35ef8ada
--- /dev/null
+++ b/src/test/resources/synthetic/long-name/crc.cpio
@@ -0,0 +1 @@
+07070200000000000081a40000000000000000000000010000000000000000000000000000000000000000000000000000800000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
\ No newline at end of file
diff --git a/src/test/resources/synthetic/long-name/gnu-fail.ar b/src/test/resources/synthetic/long-name/gnu-fail.ar
new file mode 100644
index 00000000000..623945a1196
--- /dev/null
+++ b/src/test/resources/synthetic/long-name/gnu-fail.ar
@@ -0,0 +1,2 @@
+!
+// 0 0 0 100644 2147483639`
diff --git a/src/test/resources/synthetic/long-name/gnu-fail.tar b/src/test/resources/synthetic/long-name/gnu-fail.tar
new file mode 100644
index 00000000000..76dc9923acf
Binary files /dev/null and b/src/test/resources/synthetic/long-name/gnu-fail.tar differ
diff --git a/src/test/resources/synthetic/long-name/gnu-short-max-value.ar b/src/test/resources/synthetic/long-name/gnu-short-max-value.ar
new file mode 100644
index 00000000000..4746343e42b
--- /dev/null
+++ b/src/test/resources/synthetic/long-name/gnu-short-max-value.ar
@@ -0,0 +1,4 @@
+!
+// 0 0 0 100644 32768 `
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+/0 0 0 0 100644 0 `
diff --git a/src/test/resources/synthetic/long-name/gnu.tar b/src/test/resources/synthetic/long-name/gnu.tar
new file mode 100644
index 00000000000..f789171a9f4
Binary files /dev/null and b/src/test/resources/synthetic/long-name/gnu.tar differ
diff --git a/src/test/resources/synthetic/long-name/long-name-reversed.dump b/src/test/resources/synthetic/long-name/long-name-reversed.dump
new file mode 100644
index 00000000000..be8e2b3aa35
Binary files /dev/null and b/src/test/resources/synthetic/long-name/long-name-reversed.dump differ
diff --git a/src/test/resources/synthetic/long-name/long-name.7z b/src/test/resources/synthetic/long-name/long-name.7z
new file mode 100644
index 00000000000..8a88f1f4d91
Binary files /dev/null and b/src/test/resources/synthetic/long-name/long-name.7z differ
diff --git a/src/test/resources/synthetic/long-name/long-name.arj b/src/test/resources/synthetic/long-name/long-name.arj
new file mode 100644
index 00000000000..27d3cec2c89
Binary files /dev/null and b/src/test/resources/synthetic/long-name/long-name.arj differ
diff --git a/src/test/resources/synthetic/long-name/long-name.dump b/src/test/resources/synthetic/long-name/long-name.dump
new file mode 100644
index 00000000000..561a47fae50
Binary files /dev/null and b/src/test/resources/synthetic/long-name/long-name.dump differ
diff --git a/src/test/resources/synthetic/long-name/long-name.zip b/src/test/resources/synthetic/long-name/long-name.zip
new file mode 100644
index 00000000000..64ddc64c59e
Binary files /dev/null and b/src/test/resources/synthetic/long-name/long-name.zip differ
diff --git a/src/test/resources/synthetic/long-name/newc-fail.cpio b/src/test/resources/synthetic/long-name/newc-fail.cpio
new file mode 100644
index 00000000000..feedcbd7868
Binary files /dev/null and b/src/test/resources/synthetic/long-name/newc-fail.cpio differ
diff --git a/src/test/resources/synthetic/long-name/newc.cpio b/src/test/resources/synthetic/long-name/newc.cpio
new file mode 100644
index 00000000000..e7b0f8cb47f
--- /dev/null
+++ b/src/test/resources/synthetic/long-name/newc.cpio
@@ -0,0 +1 @@
+07070100000000000081a40000000000000000000000010000000000000000000000000000000000000000000000000000800000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
\ No newline at end of file
diff --git a/src/test/resources/synthetic/long-name/odc-fail.cpio b/src/test/resources/synthetic/long-name/odc-fail.cpio
new file mode 100644
index 00000000000..4038c1e9045
Binary files /dev/null and b/src/test/resources/synthetic/long-name/odc-fail.cpio differ
diff --git a/src/test/resources/synthetic/long-name/odc.cpio b/src/test/resources/synthetic/long-name/odc.cpio
new file mode 100644
index 00000000000..d2a72984f58
--- /dev/null
+++ b/src/test/resources/synthetic/long-name/odc.cpio
@@ -0,0 +1 @@
+0707070000000000001006440000000000000000010000000000000000010000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
\ No newline at end of file
diff --git a/src/test/resources/synthetic/long-name/pax-fail.tar b/src/test/resources/synthetic/long-name/pax-fail.tar
new file mode 100644
index 00000000000..296c37056df
Binary files /dev/null and b/src/test/resources/synthetic/long-name/pax-fail.tar differ
diff --git a/src/test/resources/synthetic/long-name/pax.tar b/src/test/resources/synthetic/long-name/pax.tar
new file mode 100644
index 00000000000..2af1df4cac5
Binary files /dev/null and b/src/test/resources/synthetic/long-name/pax.tar differ