    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 * http://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.commons.compress.archivers.cpio;
    020
    021import java.io.EOFException;
    022import java.io.IOException;
    023import java.io.InputStream;
    024
    025import org.apache.commons.compress.archivers.ArchiveEntry;
    026import org.apache.commons.compress.archivers.ArchiveInputStream;
    027import org.apache.commons.compress.archivers.zip.ZipEncoding;
    028import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
    029import org.apache.commons.compress.utils.ArchiveUtils;
    030import org.apache.commons.compress.utils.CharsetNames;
    031import org.apache.commons.compress.utils.IOUtils;
    032
    033/**
    034 * CPIOArchiveInputStream is a stream for reading cpio streams. All formats of
    035 * cpio are supported (old ascii, old binary, new portable format and the new
    036 * portable format with crc).
    037 *
    038 * <p>
    039 * The stream can be read by extracting a cpio entry (containing all
    040 * informations about a entry) and afterwards reading from the stream the file
    041 * specified by the entry.
    042 * </p>
    043 * <pre>
    044 * CPIOArchiveInputStream cpioIn = new CPIOArchiveInputStream(
    045 *         new FileInputStream(new File(&quot;test.cpio&quot;)));
    046 * CPIOArchiveEntry cpioEntry;
    047 *
    048 * while ((cpioEntry = cpioIn.getNextEntry()) != null) {
    049 *     System.out.println(cpioEntry.getName());
    050 *     int tmp;
    051 *     StringBuilder buf = new StringBuilder();
    052 *     while ((tmp = cpIn.read()) != -1) {
    053 *         buf.append((char) tmp);
    054 *     }
    055 *     System.out.println(buf.toString());
    056 * }
    057 * cpioIn.close();
    058 * </pre>
    059 * <p>
    060 * Note: This implementation should be compatible to cpio 2.5
    061 *
    062 * <p>This class uses mutable fields and is not considered to be threadsafe.
    063 *
    064 * <p>Based on code from the jRPM project (jrpm.sourceforge.net)
    065 */
    066
    067public class CpioArchiveInputStream extends ArchiveInputStream implements
    068        CpioConstants {
    069
    070    private boolean closed = false;
    071
    072    private CpioArchiveEntry entry;
    073
    074    private long entryBytesRead = 0;
    075
    076    private boolean entryEOF = false;
    077
    078    private final byte tmpbuf[] = new byte[4096];
    079
    080    private long crc = 0;
    081
    082    private final InputStream in;
    083
    084    // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
    085    private final byte[] TWO_BYTES_BUF = new byte[2];
    086    private final byte[] FOUR_BYTES_BUF = new byte[4];
    087    private final byte[] SIX_BYTES_BUF = new byte[6];
    088
    089    private final int blockSize;
    090
    091    /**
    092     * The encoding to use for filenames and labels.
    093     */
    094    private final ZipEncoding zipEncoding;
    095
    096    // the provided encoding (for unit tests)
    097    final String encoding;
    098
    099    /**
    100     * Construct the cpio input stream with a blocksize of {@link
    101     * CpioConstants#BLOCK_SIZE BLOCK_SIZE} and expecting ASCII file
    102     * names.
    103     *
    104     * @param in
    105     *            The cpio stream
    106     */
    107    public CpioArchiveInputStream(final InputStream in) {
    108        this(in, BLOCK_SIZE, CharsetNames.US_ASCII);
    109    }
    110
    111    /**
    112     * Construct the cpio input stream with a blocksize of {@link
    113     * CpioConstants#BLOCK_SIZE BLOCK_SIZE}.
    114     *
    115     * @param in
    116     *            The cpio stream
    117     * @param encoding
    118     *            The encoding of file names to expect - use null for
    119     *            the platform's default.
    120     * @since 1.6
    121     */
    122    public CpioArchiveInputStream(final InputStream in, final String encoding) {
    123        this(in, BLOCK_SIZE, encoding);
    124    }
    125
    126    /**
    127     * Construct the cpio input stream with a blocksize of {@link
    128     * CpioConstants#BLOCK_SIZE BLOCK_SIZE} expecting ASCII file
    129     * names.
    130     *
    131     * @param in
    132     *            The cpio stream
    133     * @param blockSize
    134     *            The block size of the archive.
    135     * @since 1.5
    136     */
    137    public CpioArchiveInputStream(final InputStream in, final int blockSize) {
    138        this(in, blockSize, CharsetNames.US_ASCII);
    139    }
    140
    141    /**
    142     * Construct the cpio input stream with a blocksize of {@link CpioConstants#BLOCK_SIZE BLOCK_SIZE}.
    143     *
    144     * @param in
    145     *            The cpio stream
    146     * @param blockSize
    147     *            The block size of the archive.
    148     * @param encoding
    149     *            The encoding of file names to expect - use null for
    150     *            the platform's default.
    151     * @since 1.6
    152     */
    153    public CpioArchiveInputStream(final InputStream in, final int blockSize, final String encoding) {
    154        this.in = in;
    155        this.blockSize = blockSize;
    156        this.encoding = encoding;
    157        this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
    158    }
    159
    160    /**
    161     * Returns 0 after EOF has reached for the current entry data, otherwise
    162     * always return 1.
    163     * <p>
    164     * Programs should not count on this method to return the actual number of
    165     * bytes that could be read without blocking.
    166     *
    167     * @return 1 before EOF and 0 after EOF has reached for current entry.
    168     * @throws IOException
    169     *             if an I/O error has occurred or if a CPIO file error has
    170     *             occurred
    171     */
    172    @Override
    173    public int available() throws IOException {
    174        ensureOpen();
    175        if (this.entryEOF) {
    176            return 0;
    177        }
    178        return 1;
    179    }
    180
    181    /**
    182     * Closes the CPIO input stream.
    183     *
    184     * @throws IOException
    185     *             if an I/O error has occurred
    186     */
    187    @Override
    188    public void close() throws IOException {
    189        if (!this.closed) {
    190            in.close();
    191            this.closed = true;
    192        }
    193    }
    194
    195    /**
    196     * Closes the current CPIO entry and positions the stream for reading the
    197     * next entry.
    198     *
    199     * @throws IOException
    200     *             if an I/O error has occurred or if a CPIO file error has
    201     *             occurred
    202     */
    203    private void closeEntry() throws IOException {
    204        // the skip implementation of this class will not skip more
    205        // than Integer.MAX_VALUE bytes
    206        while (skip((long) Integer.MAX_VALUE) == Integer.MAX_VALUE) { // NOPMD
    207            // do nothing
    208        }
    209    }
    210
    211    /**
    212     * Check to make sure that this stream has not been closed
    213     *
    214     * @throws IOException
    215     *             if the stream is already closed
    216     */
    217    private void ensureOpen() throws IOException {
    218        if (this.closed) {
    219            throw new IOException("Stream closed");
    220        }
    221    }
    222
    223    /**
    224     * Reads the next CPIO file entry and positions stream at the beginning of
    225     * the entry data.
    226     *
    227     * @return the CPIOArchiveEntry just read
    228     * @throws IOException
    229     *             if an I/O error has occurred or if a CPIO file error has
    230     *             occurred
    231     */
    232    public CpioArchiveEntry getNextCPIOEntry() throws IOException {
    233        ensureOpen();
    234        if (this.entry != null) {
    235            closeEntry();
    236        }
    237        readFully(TWO_BYTES_BUF, 0, TWO_BYTES_BUF.length);
    238        if (CpioUtil.byteArray2long(TWO_BYTES_BUF, false) == MAGIC_OLD_BINARY) {
    239            this.entry = readOldBinaryEntry(false);
    240        } else if (CpioUtil.byteArray2long(TWO_BYTES_BUF, true)
    241                   == MAGIC_OLD_BINARY) {
    242            this.entry = readOldBinaryEntry(true);
    243        } else {
    244            System.arraycopy(TWO_BYTES_BUF, 0, SIX_BYTES_BUF, 0,
    245                             TWO_BYTES_BUF.length);
    246            readFully(SIX_BYTES_BUF, TWO_BYTES_BUF.length,
    247                      FOUR_BYTES_BUF.length);
    248            final String magicString = ArchiveUtils.toAsciiString(SIX_BYTES_BUF);
    249            if (magicString.equals(MAGIC_NEW)) {
    250                this.entry = readNewEntry(false);
    251            } else if (magicString.equals(MAGIC_NEW_CRC)) {
    252                this.entry = readNewEntry(true);
    253            } else if (magicString.equals(MAGIC_OLD_ASCII)) {
    254                this.entry = readOldAsciiEntry();
    255            } else {
    256                throw new IOException("Unknown magic [" + magicString + "]. Occured at byte: " + getBytesRead());
    257            }
    258        }
    259
    260        this.entryBytesRead = 0;
    261        this.entryEOF = false;
    262        this.crc = 0;
    263
    264        if (this.entry.getName().equals(CPIO_TRAILER)) {
    265            this.entryEOF = true;
    266            skipRemainderOfLastBlock();
    267            return null;
    268        }
    269        return this.entry;
    270    }
    271
    272    private void skip(final int bytes) throws IOException{
    273        // bytes cannot be more than 3 bytes
    274        if (bytes > 0) {
    275            readFully(FOUR_BYTES_BUF, 0, bytes);
    276        }
    277    }
    278
    279    /**
    280     * Reads from the current CPIO entry into an array of bytes. Blocks until
    281     * some input is available.
    282     *
    283     * @param b
    284     *            the buffer into which the data is read
    285     * @param off
    286     *            the start offset of the data
    287     * @param len
    288     *            the maximum number of bytes read
    289     * @return the actual number of bytes read, or -1 if the end of the entry is
    290     *         reached
    291     * @throws IOException
    292     *             if an I/O error has occurred or if a CPIO file error has
    293     *             occurred
    294     */
    295    @Override
    296    public int read(final byte[] b, final int off, final int len)
    297            throws IOException {
    298        ensureOpen();
    299        if (off < 0 || len < 0 || off > b.length - len) {
    300            throw new IndexOutOfBoundsException();
    301        } else if (len == 0) {
    302            return 0;
    303        }
    304
    305        if (this.entry == null || this.entryEOF) {
    306            return -1;
    307        }
    308        if (this.entryBytesRead == this.entry.getSize()) {
    309            skip(entry.getDataPadCount());
    310            this.entryEOF = true;
    311            if (this.entry.getFormat() == FORMAT_NEW_CRC
    312                && this.crc != this.entry.getChksum()) {
    313                throw new IOException("CRC Error. Occured at byte: "
    314                                      + getBytesRead());
    315            }
    316            return -1; // EOF for this entry
    317        }
    318        final int tmplength = (int) Math.min(len, this.entry.getSize()
    319                - this.entryBytesRead);
    320        if (tmplength < 0) {
    321            return -1;
    322        }
    323
    324        final int tmpread = readFully(b, off, tmplength);
    325        if (this.entry.getFormat() == FORMAT_NEW_CRC) {
    326            for (int pos = 0; pos < tmpread; pos++) {
    327                this.crc += b[pos] & 0xFF;
    328            }
    329        }
    330        this.entryBytesRead += tmpread;
    331
    332        return tmpread;
    333    }
    334
    335    private final int readFully(final byte[] b, final int off, final int len)
    336            throws IOException {
    337        final int count = IOUtils.readFully(in, b, off, len);
    338        count(count);
    339        if (count < len) {
    340            throw new EOFException();
    341        }
    342        return count;
    343    }
    344
    345    private long readBinaryLong(final int length, final boolean swapHalfWord)
    346            throws IOException {
    347        final byte tmp[] = new byte[length];
    348        readFully(tmp, 0, tmp.length);
    349        return CpioUtil.byteArray2long(tmp, swapHalfWord);
    350    }
    351
    352    private long readAsciiLong(final int length, final int radix)
    353            throws IOException {
    354        final byte tmpBuffer[] = new byte[length];
    355        readFully(tmpBuffer, 0, tmpBuffer.length);
    356        return Long.parseLong(ArchiveUtils.toAsciiString(tmpBuffer), radix);
    357    }
    358
    359    private CpioArchiveEntry readNewEntry(final boolean hasCrc)
    360            throws IOException {
    361        CpioArchiveEntry ret;
    362        if (hasCrc) {
    363            ret = new CpioArchiveEntry(FORMAT_NEW_CRC);
    364        } else {
    365            ret = new CpioArchiveEntry(FORMAT_NEW);
    366        }
    367
    368        ret.setInode(readAsciiLong(8, 16));
    369        final long mode = readAsciiLong(8, 16);
    370        if (CpioUtil.fileType(mode) != 0){ // mode is initialised to 0
    371            ret.setMode(mode);
    372        }
    373        ret.setUID(readAsciiLong(8, 16));
    374        ret.setGID(readAsciiLong(8, 16));
    375        ret.setNumberOfLinks(readAsciiLong(8, 16));
    376        ret.setTime(readAsciiLong(8, 16));
    377        ret.setSize(readAsciiLong(8, 16));
    378        ret.setDeviceMaj(readAsciiLong(8, 16));
    379        ret.setDeviceMin(readAsciiLong(8, 16));
    380        ret.setRemoteDeviceMaj(readAsciiLong(8, 16));
    381        ret.setRemoteDeviceMin(readAsciiLong(8, 16));
    382        final long namesize = readAsciiLong(8, 16);
    383        ret.setChksum(readAsciiLong(8, 16));
    384        final String name = readCString((int) namesize);
    385        ret.setName(name);
    386        if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)){
    387            throw new IOException("Mode 0 only allowed in the trailer. Found entry name: "
    388                                  + ArchiveUtils.sanitize(name)
    389                                  + " Occured at byte: " + getBytesRead());
    390        }
    391        skip(ret.getHeaderPadCount());
    392
    393        return ret;
    394    }
    395
    396    private CpioArchiveEntry readOldAsciiEntry() throws IOException {
    397        final CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_ASCII);
    398
    399        ret.setDevice(readAsciiLong(6, 8));
    400        ret.setInode(readAsciiLong(6, 8));
    401        final long mode = readAsciiLong(6, 8);
    402        if (CpioUtil.fileType(mode) != 0) {
    403            ret.setMode(mode);
    404        }
    405        ret.setUID(readAsciiLong(6, 8));
    406        ret.setGID(readAsciiLong(6, 8));
    407        ret.setNumberOfLinks(readAsciiLong(6, 8));
    408        ret.setRemoteDevice(readAsciiLong(6, 8));
    409        ret.setTime(readAsciiLong(11, 8));
    410        final long namesize = readAsciiLong(6, 8);
    411        ret.setSize(readAsciiLong(11, 8));
    412        final String name = readCString((int) namesize);
    413        ret.setName(name);
    414        if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)){
    415            throw new IOException("Mode 0 only allowed in the trailer. Found entry: "
    416                                  + ArchiveUtils.sanitize(name)
    417                                  + " Occured at byte: " + getBytesRead());
    418        }
    419
    420        return ret;
    421    }
    422
    423    private CpioArchiveEntry readOldBinaryEntry(final boolean swapHalfWord)
    424            throws IOException {
    425        final CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_BINARY);
    426
    427        ret.setDevice(readBinaryLong(2, swapHalfWord));
    428        ret.setInode(readBinaryLong(2, swapHalfWord));
    429        final long mode = readBinaryLong(2, swapHalfWord);
    430        if (CpioUtil.fileType(mode) != 0){
    431            ret.setMode(mode);
    432        }
    433        ret.setUID(readBinaryLong(2, swapHalfWord));
    434        ret.setGID(readBinaryLong(2, swapHalfWord));
    435        ret.setNumberOfLinks(readBinaryLong(2, swapHalfWord));
    436        ret.setRemoteDevice(readBinaryLong(2, swapHalfWord));
    437        ret.setTime(readBinaryLong(4, swapHalfWord));
    438        final long namesize = readBinaryLong(2, swapHalfWord);
    439        ret.setSize(readBinaryLong(4, swapHalfWord));
    440        final String name = readCString((int) namesize);
    441        ret.setName(name);
    442        if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)){
    443            throw new IOException("Mode 0 only allowed in the trailer. Found entry: "
    444                                  + ArchiveUtils.sanitize(name)
    445                                  + "Occured at byte: " + getBytesRead());
    446        }
    447        skip(ret.getHeaderPadCount());
    448
    449        return ret;
    450    }
    451
    452    private String readCString(final int length) throws IOException {
    453        // don't include trailing NUL in file name to decode
    454        final byte tmpBuffer[] = new byte[length - 1];
    455        readFully(tmpBuffer, 0, tmpBuffer.length);
    456        this.in.read();
    457        return zipEncoding.decode(tmpBuffer);
    458    }
    459
    460    /**
    461     * Skips specified number of bytes in the current CPIO entry.
    462     *
    463     * @param n
    464     *            the number of bytes to skip
    465     * @return the actual number of bytes skipped
    466     * @throws IOException
    467     *             if an I/O error has occurred
    468     * @throws IllegalArgumentException
    469     *             if n &lt; 0
    470     */
    471    @Override
    472    public long skip(final long n) throws IOException {
    473        if (n < 0) {
    474            throw new IllegalArgumentException("negative skip length");
    475        }
    476        ensureOpen();
    477        final int max = (int) Math.min(n, Integer.MAX_VALUE);
    478        int total = 0;
    479
    480        while (total < max) {
    481            int len = max - total;
    482            if (len > this.tmpbuf.length) {
    483                len = this.tmpbuf.length;
    484            }
    485            len = read(this.tmpbuf, 0, len);
    486            if (len == -1) {
    487                this.entryEOF = true;
    488                break;
    489            }
    490            total += len;
    491        }
    492        return total;
    493    }
    494
    495    @Override
    496    public ArchiveEntry getNextEntry() throws IOException {
    497        return getNextCPIOEntry();
    498    }
    499
    500    /**
    501     * Skips the padding zeros written after the TRAILER!!! entry.
    502     */
    503    private void skipRemainderOfLastBlock() throws IOException {
    504        final long readFromLastBlock = getBytesRead() % blockSize;
    505        long remainingBytes = readFromLastBlock == 0 ? 0
    506            : blockSize - readFromLastBlock;
    507        while (remainingBytes > 0) {
    508            final long skipped = skip(blockSize - readFromLastBlock);
    509            if (skipped <= 0) {
    510                break;
    511            }
    512            remainingBytes -= skipped;
    513        }
    514    }
    515
    516    /**
    517     * Checks if the signature matches one of the following magic values:
    518     *
    519     * Strings:
    520     *
    521     * "070701" - MAGIC_NEW
    522     * "070702" - MAGIC_NEW_CRC
    523     * "070707" - MAGIC_OLD_ASCII
    524     *
    525     * Octal Binary value:
    526     *
    527     * 070707 - MAGIC_OLD_BINARY (held as a short) = 0x71C7 or 0xC771
    528     * @param signature data to match
    529     * @param length length of data
    530     * @return whether the buffer seems to contain CPIO data
    531     */
    532    public static boolean matches(final byte[] signature, final int length) {
    533        if (length < 6) {
    534            return false;
    535        }
    536
    537        // Check binary values
    538        if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7) {
    539            return true;
    540        }
    541        if (signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) {
    542            return true;
    543        }
    544
    545        // Check Ascii (String) values
    546        // 3037 3037 30nn
    547        if (signature[0] != 0x30) {
    548            return false;
    549        }
    550        if (signature[1] != 0x37) {
    551            return false;
    552        }
    553        if (signature[2] != 0x30) {
    554            return false;
    555        }
    556        if (signature[3] != 0x37) {
    557            return false;
    558        }
    559        if (signature[4] != 0x30) {
    560            return false;
    561        }
    562        // Check last byte
    563        if (signature[5] == 0x31) {
    564            return true;
    565        }
    566        if (signature[5] == 0x32) {
    567            return true;
    568        }
    569        if (signature[5] == 0x37) {
    570            return true;
    571        }
    572
    573        return false;
    574    }
    575}