![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.commons.compress.archivers.arj.ArjArchiveInputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-compress Show documentation
Show all versions of commons-compress Show documentation
Apache Commons Compress defines an API for working with
compression and archive formats. These include bzip2, gzip, pack200,
LZMA, XZ, Snappy, traditional Unix Compress, DEFLATE, DEFLATE64, LZ4,
Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.compress.archivers.arj;
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.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.utils.BoundedInputStream;
import org.apache.commons.compress.utils.CRC32VerifyingInputStream;
import org.apache.commons.compress.utils.IOUtils;
/**
* Implements the "arj" archive format as an InputStream.
*
* - Reference 1
* - Reference 2
*
*
* @NotThreadSafe
* @since 1.6
*/
public class ArjArchiveInputStream extends ArchiveInputStream {
private static final String ENCODING_NAME = "CP437";
private static final int ARJ_MAGIC_1 = 0x60;
private static final int ARJ_MAGIC_2 = 0xEA;
/**
* Checks if the signature matches what is expected for an arj file.
*
* @param signature the bytes to check
* @param length the number of bytes to check
* @return true, if this stream is an arj archive stream, false otherwise
*/
public static boolean matches(final byte[] signature, final int length) {
return length >= 2 && (0xff & signature[0]) == ARJ_MAGIC_1 && (0xff & signature[1]) == ARJ_MAGIC_2;
}
private final DataInputStream dis;
private final MainHeader mainHeader;
private LocalFileHeader currentLocalFileHeader;
private InputStream currentInputStream;
/**
* Constructs the ArjInputStream, taking ownership of the inputStream that is passed in, and using the CP437 character encoding.
*
* @param inputStream the underlying stream, whose ownership is taken
* @throws ArchiveException if an exception occurs while reading
*/
public ArjArchiveInputStream(final InputStream inputStream) throws ArchiveException {
this(inputStream, ENCODING_NAME);
}
/**
* Constructs the ArjInputStream, taking ownership of the inputStream that is passed in.
*
* @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
*/
public ArjArchiveInputStream(final InputStream inputStream, final String charsetName) throws ArchiveException {
super(inputStream, charsetName);
in = dis = new DataInputStream(inputStream);
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 ioException) {
throw new ArchiveException(ioException.getMessage(), ioException);
}
}
@Override
public boolean canReadEntryData(final ArchiveEntry ae) {
return ae instanceof ArjArchiveEntry && ((ArjArchiveEntry) ae).getMethod() == LocalFileHeader.Methods.STORED;
}
@Override
public void close() throws IOException {
dis.close();
}
/**
* Gets the archive's comment.
*
* @return the archive's comment
*/
public String getArchiveComment() {
return mainHeader.comment;
}
/**
* Gets the archive's recorded name.
*
* @return the archive's name
*/
public String getArchiveName() {
return mainHeader.name;
}
@Override
public ArjArchiveEntry getNextEntry() throws IOException {
if (currentInputStream != null) {
// return value ignored as IOUtils.skip ensures the stream is drained completely
final InputStream input = currentInputStream;
org.apache.commons.io.IOUtils.skip(input, Long.MAX_VALUE);
currentInputStream.close();
currentLocalFileHeader = null;
currentInputStream = null;
}
currentLocalFileHeader = readLocalFileHeader();
if (currentLocalFileHeader != null) {
currentInputStream = new BoundedInputStream(dis, currentLocalFileHeader.compressedSize);
if (currentLocalFileHeader.method == LocalFileHeader.Methods.STORED) {
currentInputStream = new CRC32VerifyingInputStream(currentInputStream, currentLocalFileHeader.originalSize,
currentLocalFileHeader.originalCrc32);
}
return new ArjArchiveEntry(currentLocalFileHeader);
}
currentInputStream = null;
return null;
}
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
if (len == 0) {
return 0;
}
if (currentLocalFileHeader == null) {
throw new IllegalStateException("No current arj entry");
}
if (currentLocalFileHeader.method != LocalFileHeader.Methods.STORED) {
throw new IOException("Unsupported compression method " + currentLocalFileHeader.method);
}
return currentInputStream.read(b, off, len);
}
private int read16(final DataInputStream dataIn) throws IOException {
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 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 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);
return basicHeaderBytes;
}
private LocalFileHeader readLocalFileHeader() throws IOException {
final byte[] basicHeaderBytes = readHeader();
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 IOException("Extended header CRC32 verification failure");
}
extendedHeaders.add(extendedHeaderBytes);
}
localFileHeader.extendedHeaders = extendedHeaders.toArray(new byte[0][]);
return localFileHeader;
}
}
}
private MainHeader readMainHeader() throws IOException {
final byte[] basicHeaderBytes = readHeader();
if (basicHeaderBytes == null) {
throw new IOException("Archive ends without any headers");
}
final DataInputStream basicHeader = new DataInputStream(new ByteArrayInputStream(basicHeaderBytes));
final int firstHeaderSize = basicHeader.readUnsignedByte();
final byte[] firstHeaderBytes = readRange(basicHeader, firstHeaderSize - 1);
pushedBackBytes(firstHeaderBytes.length);
final DataInputStream firstHeader = new DataInputStream(new ByteArrayInputStream(firstHeaderBytes));
final MainHeader hdr = new MainHeader();
hdr.archiverVersionNumber = firstHeader.readUnsignedByte();
hdr.minVersionToExtract = firstHeader.readUnsignedByte();
hdr.hostOS = firstHeader.readUnsignedByte();
hdr.arjFlags = firstHeader.readUnsignedByte();
hdr.securityVersion = firstHeader.readUnsignedByte();
hdr.fileType = firstHeader.readUnsignedByte();
hdr.reserved = firstHeader.readUnsignedByte();
hdr.dateTimeCreated = read32(firstHeader);
hdr.dateTimeModified = read32(firstHeader);
hdr.archiveSize = 0xffffFFFFL & read32(firstHeader);
hdr.securityEnvelopeFilePosition = read32(firstHeader);
hdr.fileSpecPosition = read16(firstHeader);
hdr.securityEnvelopeLength = read16(firstHeader);
pushedBackBytes(20); // count has already counted them via readRange
hdr.encryptionVersion = firstHeader.readUnsignedByte();
hdr.lastChapter = firstHeader.readUnsignedByte();
if (firstHeaderSize >= 33) {
hdr.arjProtectionFactor = firstHeader.readUnsignedByte();
hdr.arjFlags2 = firstHeader.readUnsignedByte();
firstHeader.readUnsignedByte();
firstHeader.readUnsignedByte();
}
hdr.name = readString(basicHeader);
hdr.comment = readString(basicHeader);
final int extendedHeaderSize = read16(dis);
if (extendedHeaderSize > 0) {
hdr.extendedHeaderBytes = readRange(dis, extendedHeaderSize);
final long extendedHeaderCrc32 = 0xffffFFFFL & read32(dis);
final CRC32 crc32 = new CRC32();
crc32.update(hdr.extendedHeaderBytes);
if (extendedHeaderCrc32 != crc32.getValue()) {
throw new IOException("Extended header CRC32 verification failure");
}
}
return hdr;
}
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 {
try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
int nextByte;
while ((nextByte = dataIn.readUnsignedByte()) != 0) {
buffer.write(nextByte);
}
return buffer.toString(getCharset().name());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy