All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.compress.archivers.arj.ArjArchiveInputStream Maven / Gradle / Ivy

Go to download

Apache Commons Compress software 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.

There is a newer version: 1.26.1
Show newest version
/*
 *  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.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 int ARJ_MAGIC_1 = 0x60; private static final int ARJ_MAGIC_2 = 0xEA; private final DataInputStream in; private final String charsetName; private final MainHeader mainHeader; private LocalFileHeader currentLocalFileHeader = null; private InputStream currentInputStream = null; /** * 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 { in = new DataInputStream(inputStream); this.charsetName = charsetName; 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); } } /** * Constructs the ArjInputStream, taking ownership of the inputStream that is passed in, * and using the CP437 character encoding. * @param inputStream the underlying stream, whose ownership is taken * @throws ArchiveException if an exception occurs while reading */ public ArjArchiveInputStream(final InputStream inputStream) throws ArchiveException { this(inputStream, "CP437"); } @Override public void close() throws IOException { in.close(); } private int read8(final DataInputStream dataIn) throws IOException { final int value = dataIn.readUnsignedByte(); count(1); return value; } 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 String readString(final DataInputStream dataIn) throws IOException { try (final ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { int nextByte; while ((nextByte = dataIn.readUnsignedByte()) != 0) { buffer.write(nextByte); } if (charsetName != null) { return new String(buffer.toByteArray(), charsetName); } // intentionally using the default encoding as that's the contract for a null charsetName return new String(buffer.toByteArray()); } } private void readFully(final DataInputStream dataIn, final byte[] b) throws IOException { dataIn.readFully(b); count(b.length); } private byte[] readHeader() throws IOException { boolean found = false; byte[] basicHeaderBytes = null; do { int first = 0; int second = read8(in); do { first = second; second = read8(in); } while (first != ARJ_MAGIC_1 && second != ARJ_MAGIC_2); final int basicHeaderSize = read16(in); if (basicHeaderSize == 0) { // end of archive return null; } if (basicHeaderSize <= 2600) { basicHeaderBytes = new byte[basicHeaderSize]; readFully(in, basicHeaderBytes); final long basicHeaderCrc32 = read32(in) & 0xFFFFFFFFL; final CRC32 crc32 = new CRC32(); crc32.update(basicHeaderBytes); if (basicHeaderCrc32 == crc32.getValue()) { found = true; } } } while (!found); return basicHeaderBytes; } private MainHeader readMainHeader() throws IOException { final byte[] basicHeaderBytes = readHeader(); if (basicHeaderBytes == null) { throw new IOException("Archive ends without any headers"); } final DataInputStream basicHeader = new DataInputStream( new ByteArrayInputStream(basicHeaderBytes)); final int firstHeaderSize = basicHeader.readUnsignedByte(); final byte[] firstHeaderBytes = new byte[firstHeaderSize - 1]; basicHeader.readFully(firstHeaderBytes); 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 readFully hdr.encryptionVersion = firstHeader.readUnsignedByte(); hdr.lastChapter = firstHeader.readUnsignedByte(); if (firstHeaderSize >= 33) { hdr.arjProtectionFactor = firstHeader.readUnsignedByte(); hdr.arjFlags2 = firstHeader.readUnsignedByte(); firstHeader.readUnsignedByte(); firstHeader.readUnsignedByte(); } hdr.name = readString(basicHeader); hdr.comment = readString(basicHeader); final int extendedHeaderSize = read16(in); if (extendedHeaderSize > 0) { hdr.extendedHeaderBytes = new byte[extendedHeaderSize]; readFully(in, hdr.extendedHeaderBytes); final long extendedHeaderCrc32 = 0xffffFFFFL & read32(in); final CRC32 crc32 = new CRC32(); crc32.update(hdr.extendedHeaderBytes); if (extendedHeaderCrc32 != crc32.getValue()) { throw new IOException("Extended header CRC32 verification failure"); } } return hdr; } private LocalFileHeader readLocalFileHeader() throws IOException { final byte[] basicHeaderBytes = readHeader(); if (basicHeaderBytes == null) { return null; } try (final DataInputStream basicHeader = new DataInputStream(new ByteArrayInputStream(basicHeaderBytes))) { final int firstHeaderSize = basicHeader.readUnsignedByte(); final byte[] firstHeaderBytes = new byte[firstHeaderSize - 1]; basicHeader.readFully(firstHeaderBytes); try (final 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(in)) > 0) { final byte[] extendedHeaderBytes = new byte[extendedHeaderSize]; readFully(in, extendedHeaderBytes); final long extendedHeaderCrc32 = 0xffffFFFFL & read32(in); 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 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); } } /** * 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; } /** * Gets the archive's recorded name. * @return the archive's name */ public String getArchiveName() { return mainHeader.name; } /** * Gets the archive's comment. * @return the archive's comment */ public String getArchiveComment() { return mainHeader.comment; } @Override public ArjArchiveEntry getNextEntry() throws IOException { if (currentInputStream != null) { // return value ignored as IOUtils.skip ensures the stream is drained completely IOUtils.skip(currentInputStream, Long.MAX_VALUE); currentInputStream.close(); currentLocalFileHeader = null; currentInputStream = null; } currentLocalFileHeader = readLocalFileHeader(); if (currentLocalFileHeader != null) { currentInputStream = new BoundedInputStream(in, currentLocalFileHeader.compressedSize); if (currentLocalFileHeader.method == LocalFileHeader.Methods.STORED) { currentInputStream = new CRC32VerifyingInputStream(currentInputStream, currentLocalFileHeader.originalSize, currentLocalFileHeader.originalCrc32); } return new ArjArchiveEntry(currentLocalFileHeader); } currentInputStream = null; return null; } @Override public boolean canReadEntryData(final ArchiveEntry ae) { return ae instanceof ArjArchiveEntry && ((ArjArchiveEntry) ae).getMethod() == LocalFileHeader.Methods.STORED; } @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); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy