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

org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
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.cpio;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.compress.archivers.ArchiveEntry;
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.CharsetNames;
import org.apache.commons.compress.utils.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
 * portable format with crc).
 *
 * 

* The stream can be read by extracting a cpio entry (containing all * information about an entry) and afterwards reading from the stream the file * specified by the entry. *

*
 * CpioArchiveInputStream cpioIn = new CpioArchiveInputStream(
 *         Files.newInputStream(Paths.get("test.cpio")));
 * CpioArchiveEntry cpioEntry;
 *
 * while ((cpioEntry = cpioIn.getNextEntry()) != null) {
 *     System.out.println(cpioEntry.getName());
 *     int tmp;
 *     StringBuilder buf = new StringBuilder();
 *     while ((tmp = cpIn.read()) != -1) {
 *         buf.append((char) tmp);
 *     }
 *     System.out.println(buf.toString());
 * }
 * cpioIn.close();
 * 
*

* Note: This implementation should be compatible to cpio 2.5 * *

This class uses mutable fields and is not considered to be threadsafe. * *

Based on code from the jRPM project (jrpm.sourceforge.net) */ public class CpioArchiveInputStream extends ArchiveInputStream implements CpioConstants { /** * Checks if the signature matches one of the following magic values: * * Strings: * * "070701" - MAGIC_NEW * "070702" - MAGIC_NEW_CRC * "070707" - MAGIC_OLD_ASCII * * Octal Binary value: * * 070707 - MAGIC_OLD_BINARY (held as a short) = 0x71C7 or 0xC771 * @param signature data to match * @param length length of data * @return whether the buffer seems to contain CPIO data */ public static boolean matches(final byte[] signature, final int length) { if (length < 6) { return false; } // Check binary values if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7) { return true; } if (signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) { return true; } // Check Ascii (String) values // 3037 3037 30nn if (signature[0] != 0x30) { return false; } if (signature[1] != 0x37) { return false; } if (signature[2] != 0x30) { return false; } if (signature[3] != 0x37) { return false; } if (signature[4] != 0x30) { return false; } // Check last byte if (signature[5] == 0x31) { return true; } if (signature[5] == 0x32) { return true; } if (signature[5] == 0x37) { return true; } return false; } private boolean closed; private CpioArchiveEntry entry; private long entryBytesRead; private boolean entryEOF; private final byte[] tmpbuf = new byte[4096]; private long crc; private final InputStream in; // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection) private final byte[] twoBytesBuf = new byte[2]; private final byte[] fourBytesBuf = new byte[4]; private final byte[] sixBytesBuf = new byte[6]; private final int blockSize; /** * The encoding to use for file names and labels. */ private final ZipEncoding zipEncoding; // the provided encoding (for unit tests) final String encoding; /** * Construct the cpio input stream with a blocksize of {@link * CpioConstants#BLOCK_SIZE BLOCK_SIZE} and expecting ASCII file * names. * * @param in * The cpio stream */ public CpioArchiveInputStream(final InputStream in) { this(in, BLOCK_SIZE, CharsetNames.US_ASCII); } /** * Construct the cpio input stream with a blocksize of {@link * CpioConstants#BLOCK_SIZE BLOCK_SIZE} expecting ASCII file * names. * * @param in * The cpio stream * @param blockSize * The block size of the archive. * @since 1.5 */ public CpioArchiveInputStream(final InputStream in, final int blockSize) { this(in, blockSize, CharsetNames.US_ASCII); } /** * Construct the cpio input stream with a blocksize of {@link CpioConstants#BLOCK_SIZE BLOCK_SIZE}. * * @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 * @since 1.6 */ public CpioArchiveInputStream(final InputStream in, final int blockSize, final String encoding) { this.in = in; if (blockSize <= 0) { throw new IllegalArgumentException("blockSize must be bigger than 0"); } this.blockSize = blockSize; this.encoding = encoding; this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); } /** * Construct the cpio input stream with a blocksize of {@link * CpioConstants#BLOCK_SIZE BLOCK_SIZE}. * * @param in * The cpio stream * @param encoding * The encoding of file names to expect - use null for * the platform's default. * @since 1.6 */ public CpioArchiveInputStream(final InputStream in, final String encoding) { this(in, BLOCK_SIZE, encoding); } /** * Returns 0 after EOF has reached for the current entry data, otherwise * always return 1. *

* Programs should not count on this method to return the actual number of * bytes that could be read without blocking. * * @return 1 before EOF and 0 after EOF has reached for current entry. * @throws IOException * if an I/O error has occurred or if a CPIO file error has * occurred */ @Override public int available() throws IOException { ensureOpen(); if (this.entryEOF) { return 0; } return 1; } /** * Closes the CPIO input stream. * * @throws IOException * if an I/O error has occurred */ @Override public void close() throws IOException { if (!this.closed) { in.close(); this.closed = true; } } /** * 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 } } /** * Check to make sure that this stream has not been closed * * @throws IOException * if the stream is already closed */ private void ensureOpen() throws IOException { if (this.closed) { throw new IOException("Stream closed"); } } /** * Reads the next CPIO file entry and positions stream at the beginning of * the entry data. * * @return the CpioArchiveEntry just read * @throws IOException * if an I/O error has occurred or if a CPIO file error has * occurred */ public CpioArchiveEntry getNextCPIOEntry() throws IOException { ensureOpen(); if (this.entry != null) { closeEntry(); } readFully(twoBytesBuf, 0, twoBytesBuf.length); if (CpioUtil.byteArray2long(twoBytesBuf, false) == MAGIC_OLD_BINARY) { this.entry = readOldBinaryEntry(false); } else if (CpioUtil.byteArray2long(twoBytesBuf, true) == MAGIC_OLD_BINARY) { this.entry = readOldBinaryEntry(true); } else { System.arraycopy(twoBytesBuf, 0, sixBytesBuf, 0, twoBytesBuf.length); readFully(sixBytesBuf, twoBytesBuf.length, fourBytesBuf.length); final String magicString = ArchiveUtils.toAsciiString(sixBytesBuf); switch (magicString) { case MAGIC_NEW: this.entry = readNewEntry(false); break; case MAGIC_NEW_CRC: this.entry = readNewEntry(true); break; case MAGIC_OLD_ASCII: this.entry = readOldAsciiEntry(); break; default: throw new IOException("Unknown magic [" + magicString + "]. Occurred at byte: " + getBytesRead()); } } this.entryBytesRead = 0; this.entryEOF = false; this.crc = 0; if (this.entry.getName().equals(CPIO_TRAILER)) { this.entryEOF = true; skipRemainderOfLastBlock(); return null; } return this.entry; } @Override public ArchiveEntry getNextEntry() throws IOException { return getNextCPIOEntry(); } /** * Reads from the current CPIO entry into an array of bytes. Blocks until * some input is available. * * @param b * the buffer into which the data is read * @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 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 { ensureOpen(); if (off < 0 || len < 0 || off > b.length - len) { throw new IndexOutOfBoundsException(); } if (len == 0) { return 0; } if (this.entry == null || this.entryEOF) { return -1; } if (this.entryBytesRead == this.entry.getSize()) { skip(entry.getDataPadCount()); this.entryEOF = true; if (this.entry.getFormat() == FORMAT_NEW_CRC && this.crc != this.entry.getChksum()) { throw new IOException("CRC Error. Occurred at byte: " + getBytesRead()); } return -1; // EOF for this entry } final int tmplength = (int) Math.min(len, this.entry.getSize() - this.entryBytesRead); if (tmplength < 0) { return -1; } final int tmpread = readFully(b, off, tmplength); if (this.entry.getFormat() == FORMAT_NEW_CRC) { for (int pos = 0; pos < tmpread; pos++) { this.crc += b[pos] & 0xFF; this.crc &= 0xFFFFFFFFL; } } if (tmpread > 0) { this.entryBytesRead += tmpread; } return tmpread; } private long readAsciiLong(final int length, final int radix) throws IOException { final byte[] tmpBuffer = readRange(length); return Long.parseLong(ArchiveUtils.toAsciiString(tmpBuffer), radix); } private long readBinaryLong(final int length, final boolean swapHalfWord) throws IOException { final byte[] tmp = readRange(length); return CpioUtil.byteArray2long(tmp, swapHalfWord); } private String readCString(final int length) throws IOException { // don't include trailing NUL in file name to decode final byte[] tmpBuffer = readRange(length - 1); if (this.in.read() == -1) { throw new EOFException(); } return zipEncoding.decode(tmpBuffer); } private final int readFully(final byte[] b, final int off, final int len) throws IOException { final int count = IOUtils.readFully(in, b, off, len); count(count); if (count < len) { throw new EOFException(); } return count; } private CpioArchiveEntry readNewEntry(final boolean hasCrc) throws IOException { final CpioArchiveEntry ret; if (hasCrc) { ret = new CpioArchiveEntry(FORMAT_NEW_CRC); } else { ret = new CpioArchiveEntry(FORMAT_NEW); } ret.setInode(readAsciiLong(8, 16)); final long mode = readAsciiLong(8, 16); if (CpioUtil.fileType(mode) != 0){ // mode is initialized to 0 ret.setMode(mode); } ret.setUID(readAsciiLong(8, 16)); ret.setGID(readAsciiLong(8, 16)); ret.setNumberOfLinks(readAsciiLong(8, 16)); ret.setTime(readAsciiLong(8, 16)); ret.setSize(readAsciiLong(8, 16)); if (ret.getSize() < 0) { throw new IOException("Found illegal entry with negative length"); } ret.setDeviceMaj(readAsciiLong(8, 16)); ret.setDeviceMin(readAsciiLong(8, 16)); ret.setRemoteDeviceMaj(readAsciiLong(8, 16)); ret.setRemoteDeviceMin(readAsciiLong(8, 16)); final long namesize = readAsciiLong(8, 16); if (namesize < 0) { throw new IOException("Found illegal entry with negative name length"); } ret.setChksum(readAsciiLong(8, 16)); final String name = readCString((int) namesize); ret.setName(name); if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)){ throw new IOException("Mode 0 only allowed in the trailer. Found entry name: " + ArchiveUtils.sanitize(name) + " Occurred at byte: " + getBytesRead()); } skip(ret.getHeaderPadCount(namesize - 1)); return ret; } private CpioArchiveEntry readOldAsciiEntry() throws IOException { final CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_ASCII); ret.setDevice(readAsciiLong(6, 8)); ret.setInode(readAsciiLong(6, 8)); final long mode = readAsciiLong(6, 8); if (CpioUtil.fileType(mode) != 0) { ret.setMode(mode); } ret.setUID(readAsciiLong(6, 8)); ret.setGID(readAsciiLong(6, 8)); ret.setNumberOfLinks(readAsciiLong(6, 8)); ret.setRemoteDevice(readAsciiLong(6, 8)); ret.setTime(readAsciiLong(11, 8)); final long namesize = readAsciiLong(6, 8); if (namesize < 0) { throw new IOException("Found illegal entry with negative name length"); } ret.setSize(readAsciiLong(11, 8)); if (ret.getSize() < 0) { throw new IOException("Found illegal entry with negative length"); } final String name = readCString((int) namesize); ret.setName(name); if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)){ throw new IOException("Mode 0 only allowed in the trailer. Found entry: " + ArchiveUtils.sanitize(name) + " Occurred at byte: " + getBytesRead()); } return ret; } private CpioArchiveEntry readOldBinaryEntry(final boolean swapHalfWord) throws IOException { final CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_BINARY); ret.setDevice(readBinaryLong(2, swapHalfWord)); ret.setInode(readBinaryLong(2, swapHalfWord)); final long mode = readBinaryLong(2, swapHalfWord); if (CpioUtil.fileType(mode) != 0){ ret.setMode(mode); } ret.setUID(readBinaryLong(2, swapHalfWord)); ret.setGID(readBinaryLong(2, swapHalfWord)); ret.setNumberOfLinks(readBinaryLong(2, swapHalfWord)); ret.setRemoteDevice(readBinaryLong(2, swapHalfWord)); ret.setTime(readBinaryLong(4, swapHalfWord)); final long namesize = readBinaryLong(2, swapHalfWord); if (namesize < 0) { throw new IOException("Found illegal entry with negative name length"); } ret.setSize(readBinaryLong(4, swapHalfWord)); if (ret.getSize() < 0) { throw new IOException("Found illegal entry with negative length"); } final String name = readCString((int) namesize); ret.setName(name); if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)){ throw new IOException("Mode 0 only allowed in the trailer. Found entry: " + ArchiveUtils.sanitize(name) + "Occurred at byte: " + getBytesRead()); } skip(ret.getHeaderPadCount(namesize - 1)); return ret; } private final byte[] readRange(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 void skip(final int bytes) throws IOException{ // bytes cannot be more than 3 bytes if (bytes > 0) { readFully(fourBytesBuf, 0, bytes); } } /** * Skips specified number of bytes in the current CPIO entry. * * @param n * the number of bytes to skip * @return the actual number of bytes skipped * @throws IOException * if an I/O error has occurred * @throws IllegalArgumentException * if n < 0 */ @Override public long skip(final long n) throws IOException { if (n < 0) { throw new IllegalArgumentException("Negative skip length"); } ensureOpen(); final int max = (int) Math.min(n, Integer.MAX_VALUE); int total = 0; while (total < max) { int len = max - total; if (len > this.tmpbuf.length) { len = this.tmpbuf.length; } len = read(this.tmpbuf, 0, len); if (len == -1) { this.entryEOF = true; break; } total += len; } return total; } /** * Skips the padding zeros written after the TRAILER!!! entry. */ private void skipRemainderOfLastBlock() throws IOException { final long readFromLastBlock = getBytesRead() % blockSize; long remainingBytes = readFromLastBlock == 0 ? 0 : blockSize - readFromLastBlock; while (remainingBytes > 0) { final long skipped = skip(blockSize - readFromLastBlock); if (skipped <= 0) { break; } remainingBytes -= skipped; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy